1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine, Java Game Networking
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.network.serializing.serializers;
34
35import com.jme3.network.serializing.Serializer;
36import java.io.IOException;
37import java.lang.reflect.Array;
38import java.lang.reflect.Modifier;
39import java.nio.ByteBuffer;
40
41/**
42 * Array serializer
43 *
44 * @author Nathan Sweet
45 */
46@SuppressWarnings("unchecked")
47public class ArraySerializer extends Serializer {
48    private int[] getDimensions (Object array) {
49        int depth = 0;
50        Class nextClass = array.getClass().getComponentType();
51        while (nextClass != null) {
52            depth++;
53            nextClass = nextClass.getComponentType();
54        }
55        int[] dimensions = new int[depth];
56        dimensions[0] = Array.getLength(array);
57        if (depth > 1) collectDimensions(array, 1, dimensions);
58        return dimensions;
59    }
60
61    private void collectDimensions (Object array, int dimension, int[] dimensions) {
62        boolean elementsAreArrays = dimension < dimensions.length - 1;
63        for (int i = 0, s = Array.getLength(array); i < s; i++) {
64            Object element = Array.get(array, i);
65            if (element == null) continue;
66            dimensions[dimension] = Math.max(dimensions[dimension], Array.getLength(element));
67            if (elementsAreArrays) collectDimensions(element, dimension + 1, dimensions);
68        }
69    }
70
71    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
72        byte dimensionCount = data.get();
73        if (dimensionCount == 0)
74            return null;
75
76        int[] dimensions = new int[dimensionCount];
77        for (int i = 0; i < dimensionCount; i++)
78                dimensions[i] = data.getInt();
79
80        Serializer elementSerializer = null;
81
82        Class elementClass = c;
83        while (elementClass.getComponentType() != null)
84            elementClass = elementClass.getComponentType();
85
86        if (Modifier.isFinal(elementClass.getModifiers())) elementSerializer = Serializer.getSerializer(elementClass);
87        // Create array and read in the data.
88        T array = (T)Array.newInstance(elementClass, dimensions);
89        readArray(elementSerializer, elementClass, data, array, 0, dimensions);
90        return array;
91    }
92
93    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
94        if (object == null){
95            buffer.put((byte)0);
96            return;
97        }
98
99        int[] dimensions = getDimensions(object);
100        buffer.put((byte)dimensions.length);
101        for (int dimension : dimensions) buffer.putInt(dimension);
102        Serializer elementSerializer = null;
103
104        Class elementClass = object.getClass();
105        while (elementClass.getComponentType() != null) {
106            elementClass = elementClass.getComponentType();
107        }
108
109        if (Modifier.isFinal(elementClass.getModifiers())) elementSerializer = Serializer.getSerializer(elementClass);
110        writeArray(elementSerializer, buffer, object, 0, dimensions.length);
111    }
112
113    private void writeArray(Serializer elementSerializer, ByteBuffer buffer, Object array, int dimension, int dimensionCount) throws IOException {
114        int length = Array.getLength(array);
115        if (dimension > 0) {
116            buffer.putInt(length);
117        }
118        // Write array data.
119        boolean elementsAreArrays = dimension < dimensionCount - 1;
120        for (int i = 0; i < length; i++) {
121            Object element = Array.get(array, i);
122            if (elementsAreArrays) {
123                if (element != null) writeArray(elementSerializer, buffer, element, dimension + 1, dimensionCount);
124            } else if (elementSerializer != null) {
125                elementSerializer.writeObject(buffer, element);
126            } else {
127                // Each element could be a different type. Store the class with the object.
128                Serializer.writeClassAndObject(buffer, element);
129            }
130        }
131    }
132
133    private void readArray (Serializer elementSerializer, Class elementClass, ByteBuffer buffer, Object array, int dimension, int[] dimensions) throws IOException {
134        boolean elementsAreArrays = dimension < dimensions.length - 1;
135        int length;
136        if (dimension == 0) {
137            length = dimensions[0];
138        } else {
139            length = buffer.getInt();
140        }
141        for (int i = 0; i < length; i++) {
142            if (elementsAreArrays) {
143                // Nested array.
144                Object element = Array.get(array, i);
145                if (element != null) readArray(elementSerializer, elementClass, buffer, element, dimension + 1, dimensions);
146            } else if (elementSerializer != null) {
147                // Use same converter (and class) for all elements.
148                Array.set(array, i, elementSerializer.readObject(buffer, elementClass));
149            } else {
150                // Each element could be a different type. Look up the class with the object.
151                Array.set(array, i, Serializer.readClassAndObject(buffer));
152            }
153        }
154    }
155}
156