| /* |
| * 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<Short, SerializerRegistration> idRegistrations = new HashMap<Short, SerializerRegistration>(); |
| private static final Map<Class, SerializerRegistration> classRegistrations = new HashMap<Class, SerializerRegistration>(); |
| |
| 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<URL> resources = classLoader.getResources(path); |
| List<File> dirs = new ArrayList<File>(); |
| while (resources.hasMoreElements()) { |
| URL resource = resources.nextElement(); |
| dirs.add(new File(resource.getFile())); |
| } |
| ArrayList<Class> classes = new ArrayList<Class>(); |
| 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<Class> findClasses(File dir, String pkgName) throws ClassNotFoundException { |
| List<Class> classes = new ArrayList<Class>(); |
| 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<Class, SerializerRegistration> 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> T readObject(ByteBuffer data, Class<T> 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) { } |
| } |