blob: db87b549b5ada507310a297b3dc93fa3fda83c95 [file] [log] [blame]
/*
* 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) { }
}