Home | History | Annotate | Download | only in serializing
      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 {
    100         registerClass(boolean.class,   new BooleanSerializer());
    101         registerClass(byte.class,      new ByteSerializer());
    102         registerClass(char.class,      new CharSerializer());
    103         registerClass(short.class,     new ShortSerializer());
    104         registerClass(int.class,       new IntSerializer());
    105         registerClass(long.class,      new LongSerializer());
    106         registerClass(float.class,     new FloatSerializer());
    107         registerClass(double.class,    new DoubleSerializer());
    108 
    109         registerClass(Boolean.class,   new BooleanSerializer());
    110         registerClass(Byte.class,      new ByteSerializer());
    111         registerClass(Character.class, new CharSerializer());
    112         registerClass(Short.class,     new ShortSerializer());
    113         registerClass(Integer.class,   new IntSerializer());
    114         registerClass(Long.class,      new LongSerializer());
    115         registerClass(Float.class,     new FloatSerializer());
    116         registerClass(Double.class,    new DoubleSerializer());
    117         registerClass(String.class,    new StringSerializer());
    118 
    119         registerClass(Vector3f.class,  new Vector3Serializer());
    120 
    121         registerClass(Date.class,      new DateSerializer());
    122 
    123         // all the Collection classes go here
    124         registerClass(AbstractCollection.class,         new CollectionSerializer());
    125         registerClass(AbstractList.class,               new CollectionSerializer());
    126         registerClass(AbstractSet.class,                new CollectionSerializer());
    127         registerClass(ArrayList.class,                  new CollectionSerializer());
    128         registerClass(BeanContextServicesSupport.class, new CollectionSerializer());
    129         registerClass(BeanContextSupport.class,         new CollectionSerializer());
    130         registerClass(HashSet.class,                    new CollectionSerializer());
    131         registerClass(LinkedHashSet.class,              new CollectionSerializer());
    132         registerClass(LinkedList.class,                 new CollectionSerializer());
    133         registerClass(TreeSet.class,                    new CollectionSerializer());
    134         registerClass(Vector.class,                     new CollectionSerializer());
    135 
    136         // All the Map classes go here
    137         registerClass(AbstractMap.class,                new MapSerializer());
    138         registerClass(Attributes.class,                 new MapSerializer());
    139         registerClass(HashMap.class,                    new MapSerializer());
    140         registerClass(Hashtable.class,                  new MapSerializer());
    141         registerClass(IdentityHashMap.class,            new MapSerializer());
    142         registerClass(TreeMap.class,                    new MapSerializer());
    143         registerClass(WeakHashMap.class,                new MapSerializer());
    144 
    145         registerClass(Enum.class,      new EnumSerializer());
    146         registerClass(GZIPCompressedMessage.class, new GZIPSerializer());
    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      */
    160     public static void setStrictRegistration( boolean b ) {
    161         strictRegistration = b;
    162     }
    163 
    164     public static SerializerRegistration registerClass(Class cls) {
    165         return registerClass(cls, true);
    166     }
    167 
    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")
    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
    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 
    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 
    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 
    280     public static Serializer getExactSerializer(Class cls) {
    281         return classRegistrations.get(cls).getSerializer();
    282     }
    283 
    284     public static Serializer getSerializer(Class cls) {
    285         return getSerializer(cls, true);
    286     }
    287 
    288     public static Serializer getSerializer(Class cls, boolean failOnMiss) {
    289         return getSerializerRegistration(cls, failOnMiss).getSerializer();
    290     }
    291 
    292     public static SerializerRegistration getExactSerializerRegistration(Class cls) {
    293         return classRegistrations.get(cls);
    294     }
    295 
    296     public static SerializerRegistration getSerializerRegistration(Class cls) {
    297         return getSerializerRegistration(cls, strictRegistration);
    298     }
    299 
    300     @SuppressWarnings("unchecked")
    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      */
    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")
    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      */
    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      */
    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      */
    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      */
    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      */
    422     public void initialize(Class clazz) { }
    423 }
    424