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