1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
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 
33 package com.jme3.network.serializing;
34 
35 import com.jme3.math.Vector3f;
36 import com.jme3.network.message.ChannelInfoMessage;
37 import com.jme3.network.message.ClientRegistrationMessage;
38 import com.jme3.network.message.DisconnectMessage;
39 import com.jme3.network.message.GZIPCompressedMessage;
40 import com.jme3.network.message.ZIPCompressedMessage;
41 import com.jme3.network.serializing.serializers.*;
42 import java.beans.beancontext.BeanContextServicesSupport;
43 import java.beans.beancontext.BeanContextSupport;
44 import java.io.File;
45 import java.io.IOException;
46 import java.net.URL;
47 import java.nio.ByteBuffer;
48 import java.util.*;
49 import java.util.jar.Attributes;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52 
53 /**
54  * The main serializer class, which will serialize objects such that
55  *  they can be sent across the network. Serializing classes should extend
56  *  this to provide their own serialization.
57  *
58  * @author Lars Wesselius
59  */
60 public abstract class Serializer {
61     protected static final Logger log = Logger.getLogger(Serializer.class.getName());
62 
63     private static final SerializerRegistration NULL_CLASS = new SerializerRegistration( null, Void.class, (short)-1 );
64 
65     private static final Map<Short, SerializerRegistration> idRegistrations         = new HashMap<Short, SerializerRegistration>();
66     private static final Map<Class, SerializerRegistration> classRegistrations      = new HashMap<Class, SerializerRegistration>();
67 
68     private static final Serializer                         fieldSerializer         = new FieldSerializer();
69     private static final Serializer                         serializableSerializer  = new SerializableSerializer();
70     private static final Serializer                         arraySerializer         = new ArraySerializer();
71 
72     private static short                                    nextId                  = -1;
73 
74     private static boolean strictRegistration = true;
75 
76     /****************************************************************
77      ****************************************************************
78      ****************************************************************
79 
80         READ THIS BEFORE CHANGING ANYTHING BELOW
81 
82         If a registration is moved or removed before the
83         ClientRegistrationMessage then it screws up the application's
84         ability to gracefully warn users about bad versions.
85 
86         There really needs to be a version rolled into the protocol
87         and I intend to do that very soon.  In the mean time, don't
88         edit the static registrations without decrementing nextId
89         appropriately.
90 
91         Yes, that's how fragile this is.  Live and learn.
92 
93      ****************************************************************
94      ****************************************************************
95      ****************************************************************/
96 
97 
98     // Registers the classes we already have serializers for.
99     static {
registerClass(boolean.class, new BooleanSerializer())100         registerClass(boolean.class,   new BooleanSerializer());
registerClass(byte.class, new ByteSerializer())101         registerClass(byte.class,      new ByteSerializer());
registerClass(char.class, new CharSerializer())102         registerClass(char.class,      new CharSerializer());
registerClass(short.class, new ShortSerializer())103         registerClass(short.class,     new ShortSerializer());
registerClass(int.class, new IntSerializer())104         registerClass(int.class,       new IntSerializer());
registerClass(long.class, new LongSerializer())105         registerClass(long.class,      new LongSerializer());
registerClass(float.class, new FloatSerializer())106         registerClass(float.class,     new FloatSerializer());
registerClass(double.class, new DoubleSerializer())107         registerClass(double.class,    new DoubleSerializer());
108 
registerClass(Boolean.class, new BooleanSerializer())109         registerClass(Boolean.class,   new BooleanSerializer());
registerClass(Byte.class, new ByteSerializer())110         registerClass(Byte.class,      new ByteSerializer());
registerClass(Character.class, new CharSerializer())111         registerClass(Character.class, new CharSerializer());
registerClass(Short.class, new ShortSerializer())112         registerClass(Short.class,     new ShortSerializer());
registerClass(Integer.class, new IntSerializer())113         registerClass(Integer.class,   new IntSerializer());
registerClass(Long.class, new LongSerializer())114         registerClass(Long.class,      new LongSerializer());
registerClass(Float.class, new FloatSerializer())115         registerClass(Float.class,     new FloatSerializer());
registerClass(Double.class, new DoubleSerializer())116         registerClass(Double.class,    new DoubleSerializer());
registerClass(String.class, new StringSerializer())117         registerClass(String.class,    new StringSerializer());
118 
registerClass(Vector3f.class, new Vector3Serializer())119         registerClass(Vector3f.class,  new Vector3Serializer());
120 
registerClass(Date.class, new DateSerializer())121         registerClass(Date.class,      new DateSerializer());
122 
123         // all the Collection classes go here
registerClass(AbstractCollection.class, new CollectionSerializer())124         registerClass(AbstractCollection.class,         new CollectionSerializer());
registerClass(AbstractList.class, new CollectionSerializer())125         registerClass(AbstractList.class,               new CollectionSerializer());
registerClass(AbstractSet.class, new CollectionSerializer())126         registerClass(AbstractSet.class,                new CollectionSerializer());
registerClass(ArrayList.class, new CollectionSerializer())127         registerClass(ArrayList.class,                  new CollectionSerializer());
registerClass(BeanContextServicesSupport.class, new CollectionSerializer())128         registerClass(BeanContextServicesSupport.class, new CollectionSerializer());
registerClass(BeanContextSupport.class, new CollectionSerializer())129         registerClass(BeanContextSupport.class,         new CollectionSerializer());
registerClass(HashSet.class, new CollectionSerializer())130         registerClass(HashSet.class,                    new CollectionSerializer());
registerClass(LinkedHashSet.class, new CollectionSerializer())131         registerClass(LinkedHashSet.class,              new CollectionSerializer());
registerClass(LinkedList.class, new CollectionSerializer())132         registerClass(LinkedList.class,                 new CollectionSerializer());
registerClass(TreeSet.class, new CollectionSerializer())133         registerClass(TreeSet.class,                    new CollectionSerializer());
registerClass(Vector.class, new CollectionSerializer())134         registerClass(Vector.class,                     new CollectionSerializer());
135 
136         // All the Map classes go here
registerClass(AbstractMap.class, new MapSerializer())137         registerClass(AbstractMap.class,                new MapSerializer());
registerClass(Attributes.class, new MapSerializer())138         registerClass(Attributes.class,                 new MapSerializer());
registerClass(HashMap.class, new MapSerializer())139         registerClass(HashMap.class,                    new MapSerializer());
registerClass(Hashtable.class, new MapSerializer())140         registerClass(Hashtable.class,                  new MapSerializer());
registerClass(IdentityHashMap.class, new MapSerializer())141         registerClass(IdentityHashMap.class,            new MapSerializer());
registerClass(TreeMap.class, new MapSerializer())142         registerClass(TreeMap.class,                    new MapSerializer());
registerClass(WeakHashMap.class, new MapSerializer())143         registerClass(WeakHashMap.class,                new MapSerializer());
144 
registerClass(Enum.class, new EnumSerializer())145         registerClass(Enum.class,      new EnumSerializer());
registerClass(GZIPCompressedMessage.class, new GZIPSerializer())146         registerClass(GZIPCompressedMessage.class, new GZIPSerializer());
registerClass(ZIPCompressedMessage.class, new ZIPSerializer())147         registerClass(ZIPCompressedMessage.class, new ZIPSerializer());
148 
149         registerClass(DisconnectMessage.class);
150         registerClass(ClientRegistrationMessage.class);
151         registerClass(ChannelInfoMessage.class);
152     }
153 
154     /**
155      *  When set to true, classes that do not have intrinsic IDs in their
156      *  @Serializable will not be auto-registered during write.  Defaults
157      *  to true since this is almost never desired behavior with the way
158      *  this code works.  Set to false to get the old permissive behavior.
159      */
setStrictRegistration( boolean b )160     public static void setStrictRegistration( boolean b ) {
161         strictRegistration = b;
162     }
163 
registerClass(Class cls)164     public static SerializerRegistration registerClass(Class cls) {
165         return registerClass(cls, true);
166     }
167 
registerClasses(Class... classes)168     public static void registerClasses(Class... classes) {
169         for( Class c : classes ) {
170             registerClass(c);
171         }
172     }
173 
174     /**
175      *  Registers the specified class. The failOnMiss flag controls whether or
176      *  not this method returns null for failed registration or throws an exception.
177      */
178     @SuppressWarnings("unchecked")
registerClass(Class cls, boolean failOnMiss)179     public static SerializerRegistration registerClass(Class cls, boolean failOnMiss) {
180         if (cls.isAnnotationPresent(Serializable.class)) {
181             Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class);
182 
183             Class serializerClass = serializable.serializer();
184             short classId = serializable.id();
185             if (classId == 0) classId = --nextId;
186 
187             Serializer serializer = getSerializer(serializerClass, false);
188 
189             if (serializer == null) serializer = fieldSerializer;
190 
191             SerializerRegistration existingReg = getExactSerializerRegistration(cls);
192 
193             if (existingReg != null) classId = existingReg.getId();
194             SerializerRegistration reg = new SerializerRegistration(serializer, cls, classId);
195 
196             idRegistrations.put(classId, reg);
197             classRegistrations.put(cls, reg);
198 
199             serializer.initialize(cls);
200 
201             log.log( Level.INFO, "Registered class[" + classId + "]:{0}.", cls );
202             return reg;
203         }
204         if (failOnMiss) {
205             throw new IllegalArgumentException( "Class is not marked @Serializable:" + cls );
206         }
207         return null;
208     }
209 
210     /**
211      *  @deprecated This cannot be implemented in a reasonable way that works in
212      *              all deployment methods.
213      */
214     @Deprecated
registerPackage(String pkgName)215     public static SerializerRegistration[] registerPackage(String pkgName) {
216         try {
217             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
218             String path = pkgName.replace('.', '/');
219             Enumeration<URL> resources = classLoader.getResources(path);
220             List<File> dirs = new ArrayList<File>();
221             while (resources.hasMoreElements()) {
222                 URL resource = resources.nextElement();
223                 dirs.add(new File(resource.getFile()));
224             }
225             ArrayList<Class> classes = new ArrayList<Class>();
226             for (File directory : dirs) {
227                 classes.addAll(findClasses(directory, pkgName));
228             }
229 
230             SerializerRegistration[] registeredClasses = new SerializerRegistration[classes.size()];
231             for (int i = 0; i != classes.size(); ++i) {
232                 Class clz = classes.get(i);
233                 registeredClasses[i] = registerClass(clz, false);
234             }
235             return registeredClasses;
236         } catch (Exception e) {
237             e.printStackTrace();
238         }
239         return new SerializerRegistration[0];
240     }
241 
findClasses(File dir, String pkgName)242     private static List<Class> findClasses(File dir, String pkgName) throws ClassNotFoundException {
243         List<Class> classes = new ArrayList<Class>();
244         if (!dir.exists()) {
245             return classes;
246         }
247         File[] files = dir.listFiles();
248         for (File file : files) {
249             if (file.isDirectory()) {
250                 assert !file.getName().contains(".");
251                 classes.addAll(findClasses(file, pkgName + "." + file.getName()));
252             } else if (file.getName().endsWith(".class")) {
253                 classes.add(Class.forName(pkgName + '.' + file.getName().substring(0, file.getName().length() - 6)));
254             }
255         }
256         return classes;
257     }
258 
registerClass(Class cls, Serializer serializer)259     public static SerializerRegistration registerClass(Class cls, Serializer serializer) {
260         SerializerRegistration existingReg = getExactSerializerRegistration(cls);
261 
262         short id;
263         if (existingReg != null) {
264             id = existingReg.getId();
265         } else {
266             id = --nextId;
267         }
268         SerializerRegistration reg = new SerializerRegistration(serializer, cls, id);
269 
270         idRegistrations.put(id, reg);
271         classRegistrations.put(cls, reg);
272 
273         log.log( Level.INFO, "Registered class[" + id + "]:{0} to:" + serializer, cls );
274 
275         serializer.initialize(cls);
276 
277         return reg;
278     }
279 
getExactSerializer(Class cls)280     public static Serializer getExactSerializer(Class cls) {
281         return classRegistrations.get(cls).getSerializer();
282     }
283 
getSerializer(Class cls)284     public static Serializer getSerializer(Class cls) {
285         return getSerializer(cls, true);
286     }
287 
getSerializer(Class cls, boolean failOnMiss)288     public static Serializer getSerializer(Class cls, boolean failOnMiss) {
289         return getSerializerRegistration(cls, failOnMiss).getSerializer();
290     }
291 
getExactSerializerRegistration(Class cls)292     public static SerializerRegistration getExactSerializerRegistration(Class cls) {
293         return classRegistrations.get(cls);
294     }
295 
getSerializerRegistration(Class cls)296     public static SerializerRegistration getSerializerRegistration(Class cls) {
297         return getSerializerRegistration(cls, strictRegistration);
298     }
299 
300     @SuppressWarnings("unchecked")
getSerializerRegistration(Class cls, boolean failOnMiss)301     public static SerializerRegistration getSerializerRegistration(Class cls, boolean failOnMiss) {
302         SerializerRegistration reg = classRegistrations.get(cls);
303 
304         if (reg != null) return reg;
305 
306         for (Map.Entry<Class, SerializerRegistration> entry : classRegistrations.entrySet()) {
307             if (entry.getKey().isAssignableFrom(Serializable.class)) continue;
308             if (entry.getKey().isAssignableFrom(cls)) return entry.getValue();
309         }
310 
311         if (cls.isArray()) return registerClass(cls, arraySerializer);
312 
313         if (Serializable.class.isAssignableFrom(cls)) {
314             return getExactSerializerRegistration(java.io.Serializable.class);
315         }
316 
317         // See if the class could be safely auto-registered
318         if (cls.isAnnotationPresent(Serializable.class)) {
319             Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class);
320             short classId = serializable.id();
321             if( classId != 0 ) {
322                 // No reason to fail because the ID is fixed
323                 failOnMiss = false;
324             }
325         }
326 
327         if( failOnMiss ) {
328             throw new IllegalArgumentException( "Class has not been registered:" + cls );
329         }
330         return registerClass(cls, fieldSerializer);
331     }
332 
333 
334     ///////////////////////////////////////////////////////////////////////////////////
335 
336 
337     /**
338      * Read the class from given buffer and return its SerializerRegistration.
339      *
340      * @param buffer The buffer to read from.
341      * @return The SerializerRegistration, or null if non-existent.
342      */
readClass(ByteBuffer buffer)343     public static SerializerRegistration readClass(ByteBuffer buffer) {
344         short classID = buffer.getShort();
345         if (classID == -1) return NULL_CLASS;
346         return idRegistrations.get(classID);
347     }
348 
349     /**
350      * Read the class and the object.
351      *
352      * @param buffer Buffer to read from.
353      * @return The Object that was read.
354      * @throws IOException If serialization failed.
355      */
356     @SuppressWarnings("unchecked")
readClassAndObject(ByteBuffer buffer)357     public static Object readClassAndObject(ByteBuffer buffer) throws IOException {
358         SerializerRegistration reg = readClass(buffer);
359         if (reg == NULL_CLASS) return null;
360         if (reg == null) throw new SerializerException( "Class not found for buffer data." );
361         return reg.getSerializer().readObject(buffer, reg.getType());
362     }
363 
364     /**
365      * Write a class and return its SerializerRegistration.
366      *
367      * @param buffer The buffer to write the given class to.
368      * @param type The class to write.
369      * @return The SerializerRegistration that's registered to the class.
370      */
writeClass(ByteBuffer buffer, Class type)371     public static SerializerRegistration writeClass(ByteBuffer buffer, Class type) throws IOException {
372         SerializerRegistration reg = getSerializerRegistration(type);
373         if (reg == null) {
374             throw new SerializerException( "Class not registered:" + type );
375         }
376         buffer.putShort(reg.getId());
377         return reg;
378     }
379 
380     /**
381      * Write the class and object.
382      *
383      * @param buffer The buffer to write to.
384      * @param object The object to write.
385      * @throws IOException If serializing fails.
386      */
writeClassAndObject(ByteBuffer buffer, Object object)387     public static void writeClassAndObject(ByteBuffer buffer, Object object) throws IOException {
388         if (object == null) {
389             buffer.putShort((short)-1);
390             return;
391         }
392         SerializerRegistration reg = writeClass(buffer, object.getClass());
393         reg.getSerializer().writeObject(buffer, object);
394     }
395 
396     /**
397      * Read an object from the buffer, effectively deserializing it.
398      *
399      * @param data The buffer to read from.
400      * @param c The class of the object.
401      * @return The object read.
402      * @throws IOException If deserializing fails.
403      */
readObject(ByteBuffer data, Class<T> c)404     public abstract <T> T readObject(ByteBuffer data, Class<T> c) throws IOException;
405 
406     /**
407      * Write an object to the buffer, effectively serializing it.
408      *
409      * @param buffer The buffer to write to.
410      * @param object The object to serialize.
411      * @throws IOException If serializing fails.
412      */
writeObject(ByteBuffer buffer, Object object)413     public abstract void writeObject(ByteBuffer buffer, Object object) throws IOException;
414 
415     /**
416      * Registration for when a serializer may need to cache something.
417      *
418      * Override to use.
419      *
420      * @param clazz The class that has been registered to the serializer.
421      */
initialize(Class clazz)422     public void initialize(Class clazz) { }
423 }
424