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 com.jme3.network.serializing.SerializerException;
37import java.io.IOException;
38import java.lang.reflect.Field;
39import java.lang.reflect.Modifier;
40import java.nio.BufferOverflowException;
41import java.nio.ByteBuffer;
42import java.util.*;
43import java.util.logging.Level;
44
45/**
46 * The field serializer is the default serializer used for custom class.
47 *
48 * @author Lars Wesselius, Nathan Sweet
49 */
50public class FieldSerializer extends Serializer {
51    private static Map<Class, SavedField[]> savedFields = new HashMap<Class, SavedField[]>();
52
53    protected void checkClass(Class clazz) {
54
55        // See if the class has a public no-arg constructor
56        try {
57            clazz.getConstructor();
58        } catch( NoSuchMethodException e ) {
59            throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz );
60        }
61    }
62
63    public void initialize(Class clazz) {
64
65        checkClass(clazz);
66
67        List<Field> fields = new ArrayList<Field>();
68
69        Class processingClass = clazz;
70        while (processingClass != Object.class ) {
71            Collections.addAll(fields, processingClass.getDeclaredFields());
72            processingClass = processingClass.getSuperclass();
73        }
74
75        List<SavedField> cachedFields = new ArrayList<SavedField>(fields.size());
76        for (Field field : fields) {
77            int modifiers = field.getModifiers();
78            if (Modifier.isTransient(modifiers)) continue;
79            if (Modifier.isFinal(modifiers)) continue;
80            if (Modifier.isStatic(modifiers)) continue;
81            if (field.isSynthetic()) continue;
82            field.setAccessible(true);
83
84            SavedField cachedField = new SavedField();
85            cachedField.field = field;
86
87            if (Modifier.isFinal(field.getType().getModifiers())) {
88                // The type of this field is implicit in the outer class
89                // definition and because the type is final, it can confidently
90                // be determined on the other end.
91                // Note: passing false to this method has the side-effect that field.getType()
92                // will be registered as a real class that can then be read/written
93                // directly as any other registered class.  It should be safe to take
94                // an ID like this because Serializer.initialize() is only called
95                // during registration... so this is like nested registration and
96                // doesn't have any ordering problems.
97                // ...well, as long as the order of fields is consistent from one
98                // end to the next.
99                cachedField.serializer = Serializer.getSerializer(field.getType(), false);
100            }
101
102            cachedFields.add(cachedField);
103        }
104
105        Collections.sort(cachedFields, new Comparator<SavedField>() {
106            public int compare (SavedField o1, SavedField o2) {
107                    return o1.field.getName().compareTo(o2.field.getName());
108            }
109        });
110        savedFields.put(clazz, cachedFields.toArray(new SavedField[cachedFields.size()]));
111
112
113    }
114
115    @SuppressWarnings("unchecked")
116    public <T> T readObject(ByteBuffer data, Class<T> c) throws IOException {
117
118        // Read the null/non-null marker
119        if (data.get() == 0x0)
120            return null;
121
122        SavedField[] fields = savedFields.get(c);
123
124        T object;
125        try {
126            object = c.newInstance();
127        } catch (Exception e) {
128            throw new SerializerException( "Error creating object of type:" + c, e );
129        }
130
131        for (SavedField savedField : fields) {
132            Field field = savedField.field;
133            Serializer serializer = savedField.serializer;
134            Object value;
135
136            if (serializer != null) {
137                value = serializer.readObject(data, field.getType());
138            } else {
139                value = Serializer.readClassAndObject(data);
140            }
141            try {
142                field.set(object, value);
143            } catch (IllegalAccessException e) {
144                throw new SerializerException( "Error reading object", e);
145            }
146        }
147        return object;
148    }
149
150    public void writeObject(ByteBuffer buffer, Object object) throws IOException {
151
152        // Add the null/non-null marker
153        buffer.put( (byte)(object != null ? 0x1 : 0x0) );
154        if (object == null) {
155            // Nothing left to do
156            return;
157        }
158
159        SavedField[] fields = savedFields.get(object.getClass());
160        if (fields == null)
161            throw new IOException("The " + object.getClass() + " is not registered"
162                                + " in the serializer!");
163
164        for (SavedField savedField : fields) {
165            Object val = null;
166            try {
167                val = savedField.field.get(object);
168            } catch (IllegalAccessException e) {
169                e.printStackTrace();
170            }
171            Serializer serializer = savedField.serializer;
172
173            try {
174                if (serializer != null) {
175                    serializer.writeObject(buffer, val);
176                } else {
177                    Serializer.writeClassAndObject(buffer, val);
178                }
179            } catch (BufferOverflowException boe) {
180                throw boe;
181            } catch (Exception e) {
182                throw new SerializerException( "Error writing object for field:" + savedField.field, e );
183            }
184        }
185    }
186
187    private final class SavedField {
188        public Field field;
189        public Serializer serializer;
190    }
191}
192