/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.network.serializing; import com.jme3.math.Vector3f; import com.jme3.network.message.ChannelInfoMessage; import com.jme3.network.message.ClientRegistrationMessage; import com.jme3.network.message.DisconnectMessage; import com.jme3.network.message.GZIPCompressedMessage; import com.jme3.network.message.ZIPCompressedMessage; import com.jme3.network.serializing.serializers.*; import java.beans.beancontext.BeanContextServicesSupport; import java.beans.beancontext.BeanContextSupport; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.util.*; import java.util.jar.Attributes; import java.util.logging.Level; import java.util.logging.Logger; /** * The main serializer class, which will serialize objects such that * they can be sent across the network. Serializing classes should extend * this to provide their own serialization. * * @author Lars Wesselius */ public abstract class Serializer { protected static final Logger log = Logger.getLogger(Serializer.class.getName()); private static final SerializerRegistration NULL_CLASS = new SerializerRegistration( null, Void.class, (short)-1 ); private static final Map idRegistrations = new HashMap(); private static final Map classRegistrations = new HashMap(); private static final Serializer fieldSerializer = new FieldSerializer(); private static final Serializer serializableSerializer = new SerializableSerializer(); private static final Serializer arraySerializer = new ArraySerializer(); private static short nextId = -1; private static boolean strictRegistration = true; /**************************************************************** **************************************************************** **************************************************************** READ THIS BEFORE CHANGING ANYTHING BELOW If a registration is moved or removed before the ClientRegistrationMessage then it screws up the application's ability to gracefully warn users about bad versions. There really needs to be a version rolled into the protocol and I intend to do that very soon. In the mean time, don't edit the static registrations without decrementing nextId appropriately. Yes, that's how fragile this is. Live and learn. **************************************************************** **************************************************************** ****************************************************************/ // Registers the classes we already have serializers for. static { registerClass(boolean.class, new BooleanSerializer()); registerClass(byte.class, new ByteSerializer()); registerClass(char.class, new CharSerializer()); registerClass(short.class, new ShortSerializer()); registerClass(int.class, new IntSerializer()); registerClass(long.class, new LongSerializer()); registerClass(float.class, new FloatSerializer()); registerClass(double.class, new DoubleSerializer()); registerClass(Boolean.class, new BooleanSerializer()); registerClass(Byte.class, new ByteSerializer()); registerClass(Character.class, new CharSerializer()); registerClass(Short.class, new ShortSerializer()); registerClass(Integer.class, new IntSerializer()); registerClass(Long.class, new LongSerializer()); registerClass(Float.class, new FloatSerializer()); registerClass(Double.class, new DoubleSerializer()); registerClass(String.class, new StringSerializer()); registerClass(Vector3f.class, new Vector3Serializer()); registerClass(Date.class, new DateSerializer()); // all the Collection classes go here registerClass(AbstractCollection.class, new CollectionSerializer()); registerClass(AbstractList.class, new CollectionSerializer()); registerClass(AbstractSet.class, new CollectionSerializer()); registerClass(ArrayList.class, new CollectionSerializer()); registerClass(BeanContextServicesSupport.class, new CollectionSerializer()); registerClass(BeanContextSupport.class, new CollectionSerializer()); registerClass(HashSet.class, new CollectionSerializer()); registerClass(LinkedHashSet.class, new CollectionSerializer()); registerClass(LinkedList.class, new CollectionSerializer()); registerClass(TreeSet.class, new CollectionSerializer()); registerClass(Vector.class, new CollectionSerializer()); // All the Map classes go here registerClass(AbstractMap.class, new MapSerializer()); registerClass(Attributes.class, new MapSerializer()); registerClass(HashMap.class, new MapSerializer()); registerClass(Hashtable.class, new MapSerializer()); registerClass(IdentityHashMap.class, new MapSerializer()); registerClass(TreeMap.class, new MapSerializer()); registerClass(WeakHashMap.class, new MapSerializer()); registerClass(Enum.class, new EnumSerializer()); registerClass(GZIPCompressedMessage.class, new GZIPSerializer()); registerClass(ZIPCompressedMessage.class, new ZIPSerializer()); registerClass(DisconnectMessage.class); registerClass(ClientRegistrationMessage.class); registerClass(ChannelInfoMessage.class); } /** * When set to true, classes that do not have intrinsic IDs in their * @Serializable will not be auto-registered during write. Defaults * to true since this is almost never desired behavior with the way * this code works. Set to false to get the old permissive behavior. */ public static void setStrictRegistration( boolean b ) { strictRegistration = b; } public static SerializerRegistration registerClass(Class cls) { return registerClass(cls, true); } public static void registerClasses(Class... classes) { for( Class c : classes ) { registerClass(c); } } /** * Registers the specified class. The failOnMiss flag controls whether or * not this method returns null for failed registration or throws an exception. */ @SuppressWarnings("unchecked") public static SerializerRegistration registerClass(Class cls, boolean failOnMiss) { if (cls.isAnnotationPresent(Serializable.class)) { Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class); Class serializerClass = serializable.serializer(); short classId = serializable.id(); if (classId == 0) classId = --nextId; Serializer serializer = getSerializer(serializerClass, false); if (serializer == null) serializer = fieldSerializer; SerializerRegistration existingReg = getExactSerializerRegistration(cls); if (existingReg != null) classId = existingReg.getId(); SerializerRegistration reg = new SerializerRegistration(serializer, cls, classId); idRegistrations.put(classId, reg); classRegistrations.put(cls, reg); serializer.initialize(cls); log.log( Level.INFO, "Registered class[" + classId + "]:{0}.", cls ); return reg; } if (failOnMiss) { throw new IllegalArgumentException( "Class is not marked @Serializable:" + cls ); } return null; } /** * @deprecated This cannot be implemented in a reasonable way that works in * all deployment methods. */ @Deprecated public static SerializerRegistration[] registerPackage(String pkgName) { try { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); String path = pkgName.replace('.', '/'); Enumeration resources = classLoader.getResources(path); List dirs = new ArrayList(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } ArrayList classes = new ArrayList(); for (File directory : dirs) { classes.addAll(findClasses(directory, pkgName)); } SerializerRegistration[] registeredClasses = new SerializerRegistration[classes.size()]; for (int i = 0; i != classes.size(); ++i) { Class clz = classes.get(i); registeredClasses[i] = registerClass(clz, false); } return registeredClasses; } catch (Exception e) { e.printStackTrace(); } return new SerializerRegistration[0]; } private static List findClasses(File dir, String pkgName) throws ClassNotFoundException { List classes = new ArrayList(); if (!dir.exists()) { return classes; } File[] files = dir.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains("."); classes.addAll(findClasses(file, pkgName + "." + file.getName())); } else if (file.getName().endsWith(".class")) { classes.add(Class.forName(pkgName + '.' + file.getName().substring(0, file.getName().length() - 6))); } } return classes; } public static SerializerRegistration registerClass(Class cls, Serializer serializer) { SerializerRegistration existingReg = getExactSerializerRegistration(cls); short id; if (existingReg != null) { id = existingReg.getId(); } else { id = --nextId; } SerializerRegistration reg = new SerializerRegistration(serializer, cls, id); idRegistrations.put(id, reg); classRegistrations.put(cls, reg); log.log( Level.INFO, "Registered class[" + id + "]:{0} to:" + serializer, cls ); serializer.initialize(cls); return reg; } public static Serializer getExactSerializer(Class cls) { return classRegistrations.get(cls).getSerializer(); } public static Serializer getSerializer(Class cls) { return getSerializer(cls, true); } public static Serializer getSerializer(Class cls, boolean failOnMiss) { return getSerializerRegistration(cls, failOnMiss).getSerializer(); } public static SerializerRegistration getExactSerializerRegistration(Class cls) { return classRegistrations.get(cls); } public static SerializerRegistration getSerializerRegistration(Class cls) { return getSerializerRegistration(cls, strictRegistration); } @SuppressWarnings("unchecked") public static SerializerRegistration getSerializerRegistration(Class cls, boolean failOnMiss) { SerializerRegistration reg = classRegistrations.get(cls); if (reg != null) return reg; for (Map.Entry entry : classRegistrations.entrySet()) { if (entry.getKey().isAssignableFrom(Serializable.class)) continue; if (entry.getKey().isAssignableFrom(cls)) return entry.getValue(); } if (cls.isArray()) return registerClass(cls, arraySerializer); if (Serializable.class.isAssignableFrom(cls)) { return getExactSerializerRegistration(java.io.Serializable.class); } // See if the class could be safely auto-registered if (cls.isAnnotationPresent(Serializable.class)) { Serializable serializable = (Serializable)cls.getAnnotation(Serializable.class); short classId = serializable.id(); if( classId != 0 ) { // No reason to fail because the ID is fixed failOnMiss = false; } } if( failOnMiss ) { throw new IllegalArgumentException( "Class has not been registered:" + cls ); } return registerClass(cls, fieldSerializer); } /////////////////////////////////////////////////////////////////////////////////// /** * Read the class from given buffer and return its SerializerRegistration. * * @param buffer The buffer to read from. * @return The SerializerRegistration, or null if non-existent. */ public static SerializerRegistration readClass(ByteBuffer buffer) { short classID = buffer.getShort(); if (classID == -1) return NULL_CLASS; return idRegistrations.get(classID); } /** * Read the class and the object. * * @param buffer Buffer to read from. * @return The Object that was read. * @throws IOException If serialization failed. */ @SuppressWarnings("unchecked") public static Object readClassAndObject(ByteBuffer buffer) throws IOException { SerializerRegistration reg = readClass(buffer); if (reg == NULL_CLASS) return null; if (reg == null) throw new SerializerException( "Class not found for buffer data." ); return reg.getSerializer().readObject(buffer, reg.getType()); } /** * Write a class and return its SerializerRegistration. * * @param buffer The buffer to write the given class to. * @param type The class to write. * @return The SerializerRegistration that's registered to the class. */ public static SerializerRegistration writeClass(ByteBuffer buffer, Class type) throws IOException { SerializerRegistration reg = getSerializerRegistration(type); if (reg == null) { throw new SerializerException( "Class not registered:" + type ); } buffer.putShort(reg.getId()); return reg; } /** * Write the class and object. * * @param buffer The buffer to write to. * @param object The object to write. * @throws IOException If serializing fails. */ public static void writeClassAndObject(ByteBuffer buffer, Object object) throws IOException { if (object == null) { buffer.putShort((short)-1); return; } SerializerRegistration reg = writeClass(buffer, object.getClass()); reg.getSerializer().writeObject(buffer, object); } /** * Read an object from the buffer, effectively deserializing it. * * @param data The buffer to read from. * @param c The class of the object. * @return The object read. * @throws IOException If deserializing fails. */ public abstract T readObject(ByteBuffer data, Class c) throws IOException; /** * Write an object to the buffer, effectively serializing it. * * @param buffer The buffer to write to. * @param object The object to serialize. * @throws IOException If serializing fails. */ public abstract void writeObject(ByteBuffer buffer, Object object) throws IOException; /** * Registration for when a serializer may need to cache something. * * Override to use. * * @param clazz The class that has been registered to the serializer. */ public void initialize(Class clazz) { } }