| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package flex.messaging.io.amf.translator.decoder; |
| |
| import flex.messaging.io.TypeMarshallingContext; |
| import flex.messaging.io.amf.ASObject; |
| import flex.messaging.io.amf.translator.TranslationException; |
| |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Map; |
| |
| /** |
| * Utility class that tries to find an ActionScriptDecoder that will be able |
| * to convert the encoded object into an instance of the desired class. |
| * |
| * @see ActionScriptDecoder |
| * |
| * |
| */ |
| public class DecoderFactory |
| { |
| // The identity transform |
| private static final NativeDecoder nativeDecoder = new NativeDecoder(); |
| |
| // Special null transform, always returns null |
| private static final NullDecoder nullDecoder = new NullDecoder(); |
| |
| // Simple types (do not have the concept of a creating a placeholder or 'shell') |
| private static final NumberDecoder numberDecoder = new NumberDecoder(); |
| private static final StringDecoder stringDecoder = new StringDecoder(); |
| private static final BooleanDecoder booleanDecoder = new BooleanDecoder(); |
| private static final CharacterDecoder characterDecoder = new CharacterDecoder(); |
| |
| // Historically dates are simple types, but they are now considered complex |
| // via AMF 3 (though they can not have empty placeholders or 'shells') |
| private static final DateDecoder dateDecoder = new DateDecoder(); |
| private static final CalendarDecoder calendarDecoder = new CalendarDecoder(); |
| |
| // Complex types (can also be used to create empty placeholders to be populated at decode time) |
| private static final ArrayDecoder arrayDecoder = new ArrayDecoder(); |
| private static EnumDecoder enumDecoder = new EnumDecoder(); |
| private static final MapDecoder mapDecoder = new MapDecoder(); |
| private static final CollectionDecoder collectionDecoder = new CollectionDecoder(); |
| private static final TypedObjectDecoder typedObjectDecoder = new TypedObjectDecoder(); |
| |
| // If we require references to be tracked and restored, we use deep, recursive decoders |
| // however these are very expensive in terms of processing time |
| private static final ArrayDecoder deepArrayDecoder = new ReferenceAwareArrayDecoder(); |
| private static final MapDecoder deepMapDecoder = new ReferenceAwareMapDecoder(); |
| private static final CollectionDecoder deepCollectionDecoder = new ReferenceAwareCollectionDecoder(); |
| private static final TypedObjectDecoder deepTypedObjectDecoder = new ReferenceAwareTypedObjectDecoder(); |
| |
| |
| /** |
| * A simple method for obtaining a placeholder or 'shell' object that will be subsequently |
| * populated by potentially some other deserializer. Decoders contain special logic to implement well |
| * known Collections interfaces such as java.util.Map, java.util.Set, etc |
| * and also contain utilities to create native Arrays, so this functionality has been |
| * made available to other classes besides decoders. |
| * |
| * @param desiredClass the desire class for the decoded object |
| * @return The <tt>ActionScriptDecoder</tt> to use for instances of the desired class. |
| */ |
| public static ActionScriptDecoder getDecoderForShell(Class desiredClass) |
| { |
| if (desiredClass == null) |
| return nullDecoder; |
| |
| if (Collection.class.isAssignableFrom(desiredClass)) |
| return collectionDecoder; |
| |
| if (Map.class.isAssignableFrom(desiredClass)) |
| return mapDecoder; |
| |
| if (desiredClass.isArray()) |
| return arrayDecoder; |
| |
| if (desiredClass.isEnum()) |
| return enumDecoder; |
| |
| return nativeDecoder; |
| } |
| |
| /** |
| * This is a faster implementation than getReferenceAwareDecoder as it doesn't track |
| * or care about restoring references in the event that an instance was converted to |
| * a new type. Since the all MessageDeserializes now convert directly to strongly |
| * typed instances it is less likely that references will need to be tracked. |
| * |
| * A case where a reference may have needed to be tracked would be that a Class has |
| * several properties of a subtype of Object[] (i.e. Array), Collection or Map but these |
| * properties were not of the default type returned by the MessageDeserializer, that |
| * is ArrayList or HashMap, and that each property had the potential to point to the same |
| * instance. If we didn't track the reference, then the conversion to the subtype |
| * (say HashMap -> TreeMap) would effectively clone these instances. |
| * |
| * @param encodedObject the encoded object |
| * @param desiredClass the desire class for the decoded object |
| * @return The <tt>ActionScriptDecoder</tt> to use for instances of the desired class. |
| */ |
| public static ActionScriptDecoder getDecoder(Object encodedObject, Class desiredClass) |
| { |
| if (encodedObject != null) |
| { |
| // If we already have a suitable instance, return immediately! |
| if (desiredClass.isAssignableFrom(encodedObject.getClass())) |
| return nativeDecoder; |
| |
| if (String.class.equals(desiredClass)) |
| return stringDecoder; |
| |
| // We check Number and Boolean here as well as the encodedObejct == null case |
| // as they're very common property types... |
| if (isNumber(desiredClass)) |
| return numberDecoder; |
| |
| if (isBoolean(desiredClass)) |
| return booleanDecoder; |
| |
| if (Collection.class.isAssignableFrom(desiredClass)) |
| return collectionDecoder; |
| |
| if (Map.class.isAssignableFrom(desiredClass)) |
| return mapDecoder; |
| |
| if (desiredClass.isArray()) |
| return arrayDecoder; |
| |
| // Special Case - we have a typed ASObject and we're expecting it to |
| // be converted into a new class... this would be an usual situation |
| // for Mistral, however, since we now create strongly typed instances |
| // from a stream |
| if (isTypedObject(encodedObject)) |
| return typedObjectDecoder; |
| |
| if (Date.class.isAssignableFrom(desiredClass)) |
| return dateDecoder; |
| |
| if (Calendar.class.isAssignableFrom(desiredClass)) |
| return calendarDecoder; |
| } |
| |
| // Null may have been sent to a primitive Java type, in which case |
| // we create a default value, such as new Integer(0) for int rather |
| // than create a null Integer() instance... |
| if (isNumber(desiredClass)) |
| return numberDecoder; |
| |
| if (isBoolean(desiredClass)) |
| return booleanDecoder; |
| |
| if (isCharacter(desiredClass)) |
| return characterDecoder; |
| |
| if (encodedObject == null) |
| return nullDecoder; |
| |
| if (desiredClass.isEnum()) |
| return enumDecoder; |
| |
| DecoderFactory.invalidType(encodedObject, desiredClass); |
| |
| // Never reached... |
| return nativeDecoder; |
| } |
| |
| /** |
| * A considerably slower entry point for decoders as it both changes the |
| * assumptions we can make about type translation and also keeps track of |
| * a lot of information when a complex type is converted. |
| * |
| * @param encodedObject the encoded object |
| * @param desiredClass the desire class for the decoded object |
| * @return The <tt>ActionScriptDecoder</tt> to use for instances of the desired class. |
| */ |
| public static ActionScriptDecoder getReferenceAwareDecoder(Object encodedObject, Class desiredClass) |
| { |
| if (encodedObject != null) |
| { |
| if (String.class.equals(desiredClass)) |
| return stringDecoder; |
| |
| // We check Number and Boolean here as well as the encodedObejct == null case |
| // as they're very common property types... |
| if (isNumber(desiredClass)) |
| return numberDecoder; |
| |
| if (isBoolean(desiredClass)) |
| return booleanDecoder; |
| |
| if (Collection.class.isAssignableFrom(desiredClass)) |
| return deepCollectionDecoder; |
| |
| if (Map.class.isAssignableFrom(desiredClass)) |
| return deepMapDecoder; |
| |
| if (desiredClass.isArray()) |
| return deepArrayDecoder; |
| |
| // Special Case - we have a typed ASObject and we're expecting it to |
| // be converted into a new class... this would be an usual situation |
| // for Mistral, however, since we now create strongly typed instances |
| // from a stream. |
| if (isTypedObject(encodedObject)) |
| return deepTypedObjectDecoder; |
| |
| if (Date.class.isAssignableFrom(desiredClass)) |
| return dateDecoder; |
| |
| if (Calendar.class.isAssignableFrom(desiredClass)) |
| return calendarDecoder; |
| |
| if (isCharacter(desiredClass)) |
| return characterDecoder; |
| |
| // Last resort, just try and return the object undecoded if it's the right type |
| // We do this last because at this stage if it is a complex object we won't catch |
| // any Typed Object translations for properties on this type... |
| if (desiredClass.isAssignableFrom(encodedObject.getClass())) |
| return nativeDecoder; |
| } |
| |
| // Null may have been sent to a primitive Java type, in which case |
| // we create a default value, such as new Integer(0) for int rather than create |
| // a null Integer() instance... |
| if (isNumber(desiredClass)) |
| return numberDecoder; |
| |
| if (isBoolean(desiredClass)) |
| return booleanDecoder; |
| |
| if (isCharacter(desiredClass)) |
| return characterDecoder; |
| |
| if (encodedObject == null) |
| return nullDecoder; |
| |
| DecoderFactory.invalidType(encodedObject, desiredClass); |
| |
| // Never reached... |
| return nativeDecoder; |
| } |
| |
| public static boolean isNumber(Class desiredClass) |
| { |
| boolean isNum = false; |
| |
| if (desiredClass.isPrimitive()) |
| { |
| if (desiredClass.equals(Integer.TYPE) |
| || desiredClass.equals(Double.TYPE) |
| || desiredClass.equals(Long.TYPE) |
| || desiredClass.equals(Float.TYPE) |
| || desiredClass.equals(Short.TYPE) |
| || desiredClass.equals(Byte.TYPE)) |
| { |
| isNum = true; |
| } |
| } |
| else if (Number.class.isAssignableFrom(desiredClass)) |
| { |
| isNum = true; |
| } |
| |
| return isNum; |
| } |
| |
| public static boolean isCharacter(Class desiredClass) |
| { |
| boolean isChar = false; |
| |
| if (desiredClass.isPrimitive() && desiredClass.equals(Character.TYPE)) |
| { |
| isChar = true; |
| } |
| else if (desiredClass.equals(Character.class)) |
| { |
| isChar = true; |
| } |
| |
| return isChar; |
| } |
| |
| public static boolean isBoolean(Class desiredClass) |
| { |
| boolean isBool = false; |
| |
| if (desiredClass.isPrimitive() && desiredClass.equals(Boolean.TYPE)) |
| { |
| isBool = true; |
| } |
| else if (desiredClass.equals(Boolean.class)) |
| { |
| isBool = true; |
| } |
| |
| return isBool; |
| } |
| |
| public static boolean isCharArray(Class desiredClass) |
| { |
| boolean isCharArray = false; |
| |
| if (desiredClass.isArray()) |
| { |
| Class type = desiredClass.getComponentType(); |
| if (type != null && type.equals(Character.TYPE)) |
| { |
| isCharArray = true; |
| } |
| } |
| |
| return isCharArray; |
| } |
| |
| public static boolean isTypedObject(Object encodedObject) |
| { |
| boolean typed = false; |
| |
| if (encodedObject instanceof ASObject) |
| { |
| typed = TypeMarshallingContext.getType(encodedObject) != null; |
| } |
| |
| return typed; |
| } |
| |
| public static void invalidType(Object object, Class desiredClass) |
| { |
| String inputType = null; |
| |
| if (object != null) |
| { |
| inputType = object.getClass().getName(); |
| } |
| |
| StringBuffer message = new StringBuffer("Cannot convert "); |
| if (inputType != null) |
| { |
| message.append("type ").append(inputType).append(" "); |
| } |
| |
| if (object != null && (object instanceof String |
| || object instanceof Number |
| || object instanceof Boolean |
| || object instanceof Date)) |
| { |
| message.append("with value '").append(object.toString()).append("' "); |
| } |
| else if (object instanceof ASObject) |
| { |
| ASObject aso = (ASObject)object; |
| message.append("with remote type specified as '").append(aso.getType()).append("' "); |
| } |
| |
| message.append("to an instance of ").append(desiredClass.toString()); |
| |
| TranslationException ex = new TranslationException(message.toString()); |
| ex.setCode("Client.Message.Deserialize.InvalidType"); |
| throw ex; |
| } |
| } |