package ecologylab.serialization; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import ecologylab.generic.HashMapArrayList; import ecologylab.generic.ReflectionTools; import ecologylab.platformspecifics.FundamentalPlatformSpecifics; import ecologylab.serialization.annotations.Hint; import ecologylab.serialization.annotations.bibtex_key; import ecologylab.serialization.annotations.bibtex_type; import ecologylab.serialization.annotations.simpl_collection; import ecologylab.serialization.annotations.simpl_composite; import ecologylab.serialization.annotations.simpl_descriptor_classes; import ecologylab.serialization.annotations.simpl_inherit; import ecologylab.serialization.annotations.simpl_map; import ecologylab.serialization.annotations.simpl_map_key_field; import ecologylab.serialization.annotations.simpl_nowrap; import ecologylab.serialization.annotations.simpl_other_tags; import ecologylab.serialization.annotations.simpl_scalar; import ecologylab.serialization.annotations.simpl_tag; import ecologylab.serialization.annotations.simpl_use_equals_equals; import ecologylab.serialization.formatenums.StringFormat; import ecologylab.serialization.types.CollectionType; import ecologylab.serialization.types.ScalarType; import ecologylab.serialization.types.TypeRegistry; import ecologylab.serialization.types.element.IMappable; /** * Cached object that holds all of the structures needed to optimize * translations to and from XML for a single subclass of ElementState. A * rootOptimizationsMap keeps track of these, using the XML tag as the key. * <p/> * This structure itself, as well as the structure within it, are created just * in time, by lazy evaluation. * * @author andruid */ @simpl_inherit public class ClassDescriptor<FD extends FieldDescriptor> extends DescriptorBase implements IMappable<String>, Iterable<FD> { public static interface FieldDescriptorsDerivedEventListener { void fieldDescriptorsDerived(Object... eventArgs); } private static final String PACKAGE_CLASS_SEP = "."; /** * Class object that we are describing. */ @simpl_scalar private Class<?> describedClass; @simpl_scalar private String describedClassSimpleName; @simpl_scalar private String describedClassPackageName; @simpl_composite private ClassDescriptor<? extends FieldDescriptor> superClass; @simpl_collection("interface") @simpl_other_tags("inerface") // handle spelling error that was here private ArrayList<String> interfaces; /** * Class object that we are describing. */ @simpl_scalar private Class<? extends ClassDescriptor> classDescriptorClass; /** * Class object that we are describing. */ @simpl_scalar private Class<? extends FieldDescriptor> fieldDescriptorClass; /** * This is a pseudo FieldDescriptor object, defined for the class, for cases * in which the tag for the root element or a field is determined by class * name, not by field name. */ private FieldDescriptor pseudoFieldDescriptor; /** * This flag prevents loops when creating descriptors for type graphs. */ private boolean isGetAndOrganizeComplete; /** * Map of FieldToXMLOptimizations, with field names as keys. * * Used to optimize translateToXML(). Also handy for providing functionality * like associative arrays in Perl, JavaScript, PHP, ..., but with less * overhead, because the hashtable is only maintained per class, not per * instance. */ private HashMapArrayList<String, FD> fieldDescriptorsByFieldName = new HashMapArrayList<String, FD>(); @simpl_nowrap @simpl_map("field_descriptor") @simpl_map_key_field("name") private HashMapArrayList<String, FD> declaredFieldDescriptorsByFieldName = new HashMapArrayList<String, FD>(); /** * This data structure is handy for translateFromXML(). There can be * multiple tags (keys in this map) for a single FieldDescriptor if @simpl_other_tags * is used. */ private HashMap<String, FD> allFieldDescriptorsByTagNames = new HashMap<String, FD>(); private HashMap<Integer, FD> allFieldDescriptorsByTLVIds = new HashMap<Integer, FD>(); private FD fieldDescriptorForBibTeXKey = null; private HashMap<String, FD> allFieldDescriptorsByBibTeXTag = new HashMap<String, FD>(); private ArrayList<FD> attributeFieldDescriptors = new ArrayList<FD>(); private ArrayList<FD> elementFieldDescriptors = new ArrayList<FD>();; private FD scalarValueFieldDescripotor = null; /** * Global map of all ClassDescriptors. Key is the full, qualified name of * the class == describedClassName. */ private static final HashMap<String, ClassDescriptor<? extends FieldDescriptor>> globalClassDescriptorsMap = new HashMap<String, ClassDescriptor<? extends FieldDescriptor>>(); private ArrayList<FD> unresolvedScopeAnnotationFDs; private ArrayList<FD> unresolvedClassesAnnotationFDs; private String bibtexType = ""; @simpl_collection("generic_type_variable") private ArrayList<String> genericTypeVariables = new ArrayList<String>(); /** * true if the class was annotated with @simpl_use_equals_equals, and thus * that test will be used during de/serialization to detect equivalent * objects */ @simpl_scalar private boolean strictObjectGraphRequired = false; public Class<?> fdClass; @simpl_collection("generic_type_var") private ArrayList<GenericTypeVar> genericTypeVars = null; private ArrayList<String> declaredGenericTypeVarNames = null; @simpl_collection("super_class_generic_type_var") private ArrayList<GenericTypeVar> superClassGenericTypeVars = null; @simpl_scalar private String explictObjectiveCTypeName; private boolean isCloned; private ClassDescriptor clonedFrom; private List<FieldDescriptorsDerivedEventListener> fieldDescriptorsDerivedEventListeners; static { TypeRegistry.init(); } /** * Default constructor only for use by translateFromXML(). */ public ClassDescriptor() { super(); } /** * Constructor typically used by this class for creating a ClassDescriptor * given its Java Class. * * @param thatClass */ protected ClassDescriptor(Class<?> thatClass) { super(XMLTools.getXmlTagName(thatClass, SimplTypesScope.STATE), thatClass.getName()); this.describedClass = thatClass; this.describedClassSimpleName = thatClass.getSimpleName(); this.describedClassPackageName = thatClass.getPackage().getName(); final simpl_descriptor_classes descriptorsClassesAnnotation = thatClass .getAnnotation(simpl_descriptor_classes.class); if (descriptorsClassesAnnotation != null) { classDescriptorClass = (Class<? extends ClassDescriptor>) descriptorsClassesAnnotation .value()[0]; fieldDescriptorClass = (Class<? extends FieldDescriptor>) descriptorsClassesAnnotation .value()[1]; } if (thatClass.isAnnotationPresent(simpl_inherit.class)) this.superClass = getClassDescriptor(thatClass.getSuperclass()); addGenericTypeVariables(); if (javaParser != null) { comment = javaParser.getJavaDocComment(thatClass); } if (thatClass.isAnnotationPresent(simpl_use_equals_equals.class)) { this.strictObjectGraphRequired = true; } } /** * Constructor used by Meta-Metadata Compiler. * * @param tagName * @param comment * @param describedClassPackageName * @param describedClassSimpleName * @param superClass * @param interfaces */ protected ClassDescriptor(String tagName, String comment, String describedClassPackageName, String describedClassSimpleName, ClassDescriptor<FD> superClass, ArrayList<String> interfaces) { super(tagName, describedClassPackageName + PACKAGE_CLASS_SEP + describedClassSimpleName, comment); this.describedClassPackageName = describedClassPackageName; this.describedClassSimpleName = describedClassSimpleName; this.superClass = superClass; this.interfaces = interfaces; } /** * Handles a text node. */ private FieldDescriptor scalarTextFD; private Object SCOPE_ANNOTATION_LOCK = new Object(); public FieldDescriptor getScalarTextFD() { return scalarTextFD; } void setScalarTextFD(FieldDescriptor scalarTextFD) { this.scalarTextFD = scalarTextFD; } /** * Determines if this Class Descriptor handles a text node. * @return Returns true if this node is a text node. */ public boolean hasScalarFD() { return scalarTextFD != null; } /** * Returns a list of interfaces that this class implements * @return */ public ArrayList<String> getInterfaceList() { return interfaces; } private void addGenericTypeVariables() { TypeVariable<?>[] typeVariables = describedClass.getTypeParameters(); if (typeVariables != null && typeVariables.length > 0) { for (TypeVariable<?> typeVariable : typeVariables) { String typeClassName = typeVariable.getName(); genericTypeVariables.add(typeClassName); } } } /** * lazy-evaluation method. * * @return */ public ArrayList<GenericTypeVar> getGenericTypeVars() { if (genericTypeVars == null) { synchronized (this) { if (genericTypeVars == null) { genericTypeVars = new ArrayList<GenericTypeVar>(); deriveGenericTypeVariables(); } } } return genericTypeVars; } /** * lazy-evaluation method. * * @return */ public ArrayList<GenericTypeVar> getSuperClassGenericTypeVars() { if (superClassGenericTypeVars == null) { synchronized (this) { if (superClassGenericTypeVars == null) { // superClassGenericTypeVars = new // ArrayList<GenericTypeVar>(); deriveSuperGenericTypeVariables(); } } } return superClassGenericTypeVars; } // added a setter to enable environment specific implementation -Fei public void setSuperClassGenericTypeVars( ArrayList<GenericTypeVar> derivedSuperClassGenericTypeVars) { synchronized (this) { superClassGenericTypeVars = derivedSuperClassGenericTypeVars; } } // This method is modified, refer to FundamentalPlatformSpecific package // -Fei private void deriveSuperGenericTypeVariables() { FundamentalPlatformSpecifics.get() .deriveSuperClassGenericTypeVars(this); } private void deriveGenericTypeVariables() { if (describedClass != null) // for generated descriptors, describedClass // == null { TypeVariable<?>[] typeVariables = describedClass .getTypeParameters(); if (typeVariables != null && typeVariables.length > 0) { for (TypeVariable<?> typeVariable : typeVariables) { GenericTypeVar g = GenericTypeVar.getGenericTypeVarDef( typeVariable, this.genericTypeVars); this.genericTypeVars.add(g); } } } } @Deprecated public ArrayList<String> getGenericTypeVariables() { return genericTypeVariables; } @Override public String getTagName() { return tagName; } public String getBibtexType() { if (this.bibtexType == null || this.bibtexType.equals("")) { return tagName; } return bibtexType; } /** * Obtain Optimizations object in the global scope of root Optimizations. * Uses just-in-time / lazy evaluation. The first time this is called for a * given ElementState class, it constructs a new Optimizations saves it in * our rootOptimizationsMap, and returns it. * <p/> * Subsequent calls merely pass back the already created object from the * rootOptimizationsMap. * * @param elementState * An ElementState object that we're looking up Optimizations * for. * @return */ public static ClassDescriptor<? extends FieldDescriptor> getClassDescriptor( Object object) { Class<? extends Object> thatClass = object.getClass(); return getClassDescriptor(thatClass); } static final Class<?>[] CONSTRUCTOR_ARGS = { Class.class }; /** * Obtain Optimizations object in the global scope of root Optimizations. * Uses just-in-time / lazy evaluation. The first time this is called for a * given ElementState class, it constructs a new Optimizations saves it in * our rootOptimizationsMap, and returns it. * <p/> * Subsequent calls merely pass back the already created object from the * rootOptimizationsMap. * * @param thatClass * @return */ public static ClassDescriptor<? extends FieldDescriptor> getClassDescriptor( final Class<?> thatClass) { String className = thatClass.getName(); // stay out of the synchronized block most of the time ClassDescriptor<? extends FieldDescriptor> result = globalClassDescriptorsMap .get(className); if (result == null || !result.isGetAndOrganizeComplete) { // but still be thread safe! synchronized (globalClassDescriptorsMap) { result = globalClassDescriptorsMap.get(className); if (result == null) { final simpl_descriptor_classes descriptorsClassesAnnotation = thatClass .getAnnotation(simpl_descriptor_classes.class); if (descriptorsClassesAnnotation == null) result = new ClassDescriptor<FieldDescriptor>(thatClass); else { Class<?> aClass = descriptorsClassesAnnotation.value()[0]; Object[] args = new Object[1]; args[0] = thatClass; result = (ClassDescriptor<? extends FieldDescriptor>) ReflectionTools .getInstance(aClass, CONSTRUCTOR_ARGS, args); } globalClassDescriptorsMap.put(className, result); ClassDescriptor<? extends FieldDescriptor> superCD = result .getSuperClass(); if (superCD == null || superCD.isGetAndOrganizeComplete) { // NB: this call was moved out of the constructor to // avoid recursion problems result.deriveAndOrganizeFieldsRecursive(thatClass); result.isGetAndOrganizeComplete = true; result.handleFieldDescriptorsDerivedEvent(); } else { final ClassDescriptor resultFinalCopy = result; FieldDescriptorsDerivedEventListener listener = new FieldDescriptorsDerivedEventListener() { @Override public void fieldDescriptorsDerived( Object... eventArgs) { resultFinalCopy .deriveAndOrganizeFieldsRecursive(thatClass); resultFinalCopy.isGetAndOrganizeComplete = true; resultFinalCopy .handleFieldDescriptorsDerivedEvent(); } }; superCD.addFieldDescriptorDerivedEventListener(listener); } // result.deriveAndOrganizeFieldsRecursive(thatClass); // result.isGetAndOrganizeComplete = true; } } } return result; } private List<FieldDescriptorsDerivedEventListener> fieldDescriptorsDerivedEventListeners() { if (fieldDescriptorsDerivedEventListeners == null) this.fieldDescriptorsDerivedEventListeners = new ArrayList<FieldDescriptorsDerivedEventListener>(); return fieldDescriptorsDerivedEventListeners; } private void addFieldDescriptorDerivedEventListener( FieldDescriptorsDerivedEventListener listener) { fieldDescriptorsDerivedEventListeners().add(listener); } private void handleFieldDescriptorsDerivedEvent() { if (fieldDescriptorsDerivedEventListeners != null) { for (FieldDescriptorsDerivedEventListener listener : fieldDescriptorsDerivedEventListeners) { listener.fieldDescriptorsDerived(); } fieldDescriptorsDerivedEventListeners.clear(); } } /** * Form a pseudo-FieldDescriptor-object for a root element. We say pseudo, * because there is no Field corresponding to this element. The * pseudo-FieldDescriptor-object still guides the translation process. * * @return */ public FieldDescriptor pseudoFieldDescriptor() { FieldDescriptor result = pseudoFieldDescriptor; if (result == null) { synchronized (this) { result = pseudoFieldDescriptor; if (result == null) { result = new FieldDescriptor(this); pseudoFieldDescriptor = result; } } } return result; } public ArrayList<FD> allFieldDescriptors() { ArrayList<FD> allFieldDescriptors = new ArrayList<FD>(); if (attributeFieldDescriptors != null) allFieldDescriptors.addAll(attributeFieldDescriptors); if (elementFieldDescriptors != null) allFieldDescriptors.addAll(elementFieldDescriptors); return allFieldDescriptors; } public ArrayList<FD> attributeFieldDescriptors() { return attributeFieldDescriptors; } public ArrayList<FD> elementFieldDescriptors() { return elementFieldDescriptors; } public FD getFieldDescriptorByTag(String tag, SimplTypesScope tScope, Object context) { if (unresolvedScopeAnnotationFDs != null) resolveUnresolvedScopeAnnotationFDs(); if (unresolvedClassesAnnotationFDs != null) resolveUnresolvedClassesAnnotationFDs(); return allFieldDescriptorsByTagNames.get(tag); } public FD getFieldDescriptorByTag(String tag, SimplTypesScope tScope) { return getFieldDescriptorByTag(tag, tScope, null); } public FD getFieldDescriptorByTLVId(int tlvId) { if (unresolvedScopeAnnotationFDs != null) resolveUnresolvedScopeAnnotationFDs(); if (unresolvedClassesAnnotationFDs != null) resolveUnresolvedClassesAnnotationFDs(); return allFieldDescriptorsByTLVIds.get(tlvId); } public FD getFieldDescriptorForBibTeXKey() { return fieldDescriptorForBibTeXKey; } public FD getFieldDescriptorByBibTeXTag(String bibTeXTag) { return allFieldDescriptorsByBibTeXTag.get(bibTeXTag); } public FD getFieldDescriptorByFieldName(String fieldName) { return fieldDescriptorsByFieldName.get(fieldName); } @Override public Iterator<FD> iterator() { return fieldDescriptorsByFieldName.iterator(); } /** * Build and return an ArrayList with Field objects for all the annotated * fields in this class. * * @param fieldDescriptorClass * The Class to use for instantiating each FieldDescriptor. The * default is FieldDescriptor, but class objects may be passed in * that extend that class. * * @return HashMapArrayList of Field objects, using the XML tag name for * each field (not its Java field name!) as the keys. Could be * empty. Never null. */ private Class<FieldDescriptor> fieldDescriptorAnnotationValue( Class<? extends Object> thatClass) { final simpl_descriptor_classes fieldDescriptorsClassAnnotation = thatClass .getAnnotation(simpl_descriptor_classes.class); Class<FieldDescriptor> result = null; if (fieldDescriptorsClassAnnotation != null) { Class<?> annotatedFieldDescriptorClass = fieldDescriptorsClassAnnotation .value()[1]; if (annotatedFieldDescriptorClass != null && FieldDescriptor.class .isAssignableFrom(annotatedFieldDescriptorClass)) result = (Class<FieldDescriptor>) annotatedFieldDescriptorClass; } return result; } /** * Recursive method to create optimized data structures needed for * translation to and from XML, and also for efficient reflection-based * access to field (descriptors) at run-time, with field name as a variable. * <p/> * Recurses up the chain of inherited Java classes, when @xml_inherit is * specified. * * @param fdc * @return */ private synchronized void deriveAndOrganizeFieldsRecursive( Class<? extends Object> classWithFields) { if (classWithFields.isAnnotationPresent(simpl_inherit.class)) { ClassDescriptor<FD> superClassDescriptor = (ClassDescriptor<FD>) ClassDescriptor .getClassDescriptor(classWithFields.getSuperclass()); referFieldDescriptorsFrom(superClassDescriptor); } if (classWithFields.isAnnotationPresent(bibtex_type.class)) { bibtex_type bibtexTypeAnnotation = classWithFields .getAnnotation(bibtex_type.class); bibtexType = bibtexTypeAnnotation.value(); } debug(classWithFields.toString()); Field[] fields = classWithFields.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field thatField = fields[i]; // skip static fields, since we're saving instances, // and inclusion w each instance would be redundant. if ((thatField.getModifiers() & Modifier.STATIC) == Modifier.STATIC) { // debug("Skipping " + thatField + " because its static!"); continue; } FieldType fieldType = FieldType.UNSET_TYPE; if (XMLTools.isScalar(thatField)) { fieldType = FieldType.SCALAR; } else if (XMLTools.representAsComposite(thatField)) { fieldType = FieldType.COMPOSITE_ELEMENT; } else if (XMLTools.representAsCollection(thatField)) { // WORK AROUND TO HANDLE ENUM COLLECTIONS. // THIS WILL NOT BE A PERMANENT SOLUTION. if (XMLTools.isEnumCollection(thatField)) { // Enums are scalars at the moment. fieldType = FieldType.COLLECTION_ELEMENT; } else { fieldType = FieldType.COLLECTION_ELEMENT; } } else if (XMLTools.representAsMap(thatField)) { fieldType = FieldType.MAP_ELEMENT; } if (fieldType == FieldType.UNSET_TYPE) continue; // not a simpl serialization annotated field FD fieldDescriptor = newFieldDescriptor(thatField, fieldType, (Class<FD>) fieldDescriptorClass); if (fieldDescriptor != null) { fieldDescriptor.genericTypeVarsContextCD = this; } else { int isfdl = 1+3; isfdl ++; } // create indexes for serialize if (fieldDescriptor.getType() == FieldType.SCALAR) { Hint xmlHint = fieldDescriptor.getXmlHint(); switch (xmlHint) { case XML_ATTRIBUTE: attributeFieldDescriptors.add(fieldDescriptor); break; case XML_TEXT: case XML_TEXT_CDATA: break; case XML_LEAF: case XML_LEAF_CDATA: elementFieldDescriptors.add(fieldDescriptor); break; } } else { elementFieldDescriptors.add(fieldDescriptor); } if (XMLTools.isCompositeAsScalarvalue(thatField)) { scalarValueFieldDescripotor = fieldDescriptor; } // generate a warning message if a mapping is being overridden fieldDescriptorsByFieldName.put(thatField.getName(), fieldDescriptor); if (classWithFields == describedClass) { declaredFieldDescriptorsByFieldName.put(thatField.getName(), fieldDescriptor); } if (fieldDescriptor.isMarshallOnly()) { continue; // not translated from XML, so don't add those // mappings } // find the field descriptor for bibtex citation key bibtex_key keyAnnotation = thatField .getAnnotation(bibtex_key.class); if (keyAnnotation != null) { fieldDescriptorForBibTeXKey = fieldDescriptor; } // create mappings for translateFromBibTeX() --> // allFieldDescriptorsByBibTeXTag final String bibTeXTag = fieldDescriptor.getBibtexTagName(); allFieldDescriptorsByBibTeXTag.put(bibTeXTag, fieldDescriptor); // create mappings for translateFromXML() --> // allFieldDescriptorsByTagNames final String fieldTagName = fieldDescriptor.getTagName(); if (fieldDescriptor.isWrapped()) { FD wrapper = newFieldDescriptor(fieldDescriptor, fieldTagName, (Class<FD>) fieldDescriptorClass); mapTagToFdForDeserialize(fieldTagName, wrapper); mapOtherTagsToFdForDeserialize(wrapper, fieldDescriptor.otherTags()); } else if (!fieldDescriptor.isPolymorphic()) // tag(s) from field, // not from class // :-) { String tag = null; if (fieldDescriptor.isCollection()) { tag = fieldDescriptor.getCollectionOrMapTagName(); } else { tag = fieldTagName; } if(tag == null) { throw new RuntimeException("Tag should never be null! Fix it!"); } mapTagToFdForDeserialize(tag, fieldDescriptor); mapOtherTagsToFdForDeserialize(fieldDescriptor, fieldDescriptor.otherTags()); } else { mapPolymorphicClassDescriptors(fieldDescriptor); } thatField.setAccessible(true); // else -- ignore non-annotated // fields } // end for all fields } private void referFieldDescriptorsFrom( ClassDescriptor<FD> superClassDescriptor) { initDeclaredGenericTypeVarNames(); Map<FieldDescriptor, FieldDescriptor> bookkeeper = new HashMap<FieldDescriptor, FieldDescriptor>(); for (Entry<String, FD> fieldDescriptorEntry : superClassDescriptor .getFieldDescriptorsByFieldName().entrySet()) { fieldDescriptorsByFieldName.put( fieldDescriptorEntry.getKey(), perhapsCloneGenericField(fieldDescriptorEntry.getValue(), bookkeeper)); } for (Entry<String, FD> fieldDescriptorEntry : superClassDescriptor .getDeclaredFieldDescriptorsByFieldName().entrySet()) { declaredFieldDescriptorsByFieldName.put( fieldDescriptorEntry.getKey(), perhapsCloneGenericField(fieldDescriptorEntry.getValue(), bookkeeper)); } for (Entry<String, FD> fieldDescriptorEntry : superClassDescriptor .getAllFieldDescriptorsByTagNames().entrySet()) { allFieldDescriptorsByTagNames.put( fieldDescriptorEntry.getKey(), perhapsCloneGenericField(fieldDescriptorEntry.getValue(), bookkeeper)); } for (Entry<Integer, FD> fieldDescriptorEntry : superClassDescriptor .getAllFieldDescriptorsByTLVIds().entrySet()) { allFieldDescriptorsByTLVIds.put( fieldDescriptorEntry.getKey(), perhapsCloneGenericField(fieldDescriptorEntry.getValue(), bookkeeper)); } for (Entry<String, FD> fieldDescriptorEntry : superClassDescriptor .getAllFieldDescriptorsByBibTeXTag().entrySet()) { allFieldDescriptorsByBibTeXTag.put( fieldDescriptorEntry.getKey(), perhapsCloneGenericField(fieldDescriptorEntry.getValue(), bookkeeper)); } for (FD fieldDescriptor : superClassDescriptor .attributeFieldDescriptors()) { attributeFieldDescriptors.add(perhapsCloneGenericField( fieldDescriptor, bookkeeper)); } for (FD fieldDescriptor : superClassDescriptor .elementFieldDescriptors()) { elementFieldDescriptors.add(perhapsCloneGenericField( fieldDescriptor, bookkeeper)); } FieldDescriptor scalarTextFD = superClassDescriptor.getScalarTextFD(); if (scalarTextFD != null) { // added by Zach -- doesn't seem to be // covered otherwise this.setScalarTextFD(perhapsCloneGenericField(scalarTextFD, bookkeeper)); } if (superClassDescriptor.getUnresolvedScopeAnnotationFDs() != null) { for (FD fd : superClassDescriptor.getUnresolvedScopeAnnotationFDs()) { this.registerUnresolvedScopeAnnotationFD(perhapsCloneGenericField( fd, bookkeeper)); } } if (superClassDescriptor.getUnresolvedClassesAnnotationFDs() != null) { for (FD fd : superClassDescriptor .getUnresolvedClassesAnnotationFDs()) { this.registerUnresolvedClassesAnnotationFD(perhapsCloneGenericField( fd, bookkeeper)); } } } private void initDeclaredGenericTypeVarNames() { if (declaredGenericTypeVarNames == null && describedClass != null) { ArrayList<String> result = new ArrayList<String>(); TypeVariable<?>[] typeParams = describedClass.getTypeParameters(); if (typeParams != null && typeParams.length > 0) { for (TypeVariable<?> typeParam : typeParams) result.add(typeParam.getName()); } if (result.size() > 0) declaredGenericTypeVarNames = result; } } private <FDT extends FieldDescriptor> FDT perhapsCloneGenericField(FDT fd, Map<FieldDescriptor, FieldDescriptor> bookkeeper) { if (declaredGenericTypeVarNames == null || fd.field == null) { return fd; } if (bookkeeper.containsKey(fd)) { return (FDT) bookkeeper.get(fd); } FDT result = fd; Type genericType = fd.field.getGenericType(); if (isTypeUsingGenericNames(genericType, declaredGenericTypeVarNames)) { result = (FDT) fd.clone(); result.setGenericTypeVars(null); result.genericTypeVarsContextCD = this; } bookkeeper.put(fd, result); return result; } private boolean isTypeUsingGenericNames(Type genericType, ArrayList<String> names) { if (genericType != null) { if (genericType instanceof TypeVariable) { TypeVariable tv = (TypeVariable) genericType; if (names.contains(tv.getName()) || tv.getBounds().length > 0 && isTypeUsingGenericNames(tv.getBounds()[0], names)) { return true; } } else if (genericType instanceof WildcardType) { WildcardType wt = (WildcardType) genericType; if (wt.getUpperBounds().length > 0 && isTypeUsingGenericNames(wt.getUpperBounds()[0], names)) { return true; } } else if (genericType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) genericType; Type[] args = pt.getActualTypeArguments(); for (Type arg : args) { if (isTypeUsingGenericNames(arg, names)) { return true; } } } } return false; } protected void mapOtherTagsToFdForDeserialize(FD fieldDescriptor, ArrayList<String> otherTags) { if (otherTags != null) { for (String otherTag : otherTags) { mapTagToFdForDeserialize(otherTag, fieldDescriptor); } } } /** * @param fieldDescriptor */ void mapPolymorphicClassDescriptors(FD fieldDescriptor) { Collection<String> tagClassDescriptors = fieldDescriptor .getPolymorphicTags(); if (tagClassDescriptors != null) { for (String tagName : tagClassDescriptors) { mapTagToFdForDeserialize(tagName, fieldDescriptor); } } mapTagToFdForDeserialize(fieldDescriptor.getTagName(), fieldDescriptor); } static final Class[] FIELD_DESCRIPTOR_ARGS = { ClassDescriptor.class, Field.class, FieldType.class }; /** * @param thatField * @param fieldDescriptorClass * @return */ private FD newFieldDescriptor(Field thatField, FieldType annotationType, Class<FD> fieldDescriptorClass) { if (fieldDescriptorClass == null) { return (FD) new FieldDescriptor(this, thatField, annotationType); } Object args[] = new Object[3]; args[0] = this; args[1] = thatField; args[2] = annotationType; return ReflectionTools.getInstance(fieldDescriptorClass, FIELD_DESCRIPTOR_ARGS, args); } static final Class[] WRAPPER_FIELD_DESCRIPTOR_ARGS = { ClassDescriptor.class, FieldDescriptor.class, String.class }; private FD newFieldDescriptor(FD wrappedFD, String wrapperTag, Class<FD> fieldDescriptorClass) { if (fieldDescriptorClass == null) { return (FD) new FieldDescriptor(this, wrappedFD, wrapperTag); } Object args[] = new Object[3]; args[0] = this; args[1] = wrappedFD; args[2] = wrapperTag; return ReflectionTools.getInstance(fieldDescriptorClass, WRAPPER_FIELD_DESCRIPTOR_ARGS, args); } /** * Map the tag to the FieldDescriptor for use in translateFromXML() for * elements of this class type. * * @param tagName * @param fdToMap */ private void mapTagToFdForDeserialize(String tagName, FD fdToMap) { if (!fdToMap.isWrapped()) { FD previousMapping = allFieldDescriptorsByTagNames.put(tagName, fdToMap); allFieldDescriptorsByTLVIds.put(tagName.hashCode(), fdToMap); if (previousMapping != null && previousMapping != fdToMap) { warning(" tag <" + tagName + ">:\tfield[" + fdToMap.getName() + "] overrides field[" + previousMapping.getName() + "]"); } } } /** * Add an entry to our map of Field objects, using the field's name as the * key. Used, for example, for ignored fields. * * @param fieldDescriptor */ void addFieldDescriptorMapping(FD fieldDescriptor) { String tagName = fieldDescriptor.getTagName(); if (tagName != null) { mapTagToFdForDeserialize(tagName, fieldDescriptor); } } /** * (used by the compiler) * * @param fieldDescriptor */ protected void addFieldDescriptor(FD fieldDescriptor) { declaredFieldDescriptorsByFieldName.put(fieldDescriptor.getName(), fieldDescriptor); } @Override public String toString() { return getClassSimpleName() + "[" + this.name + "]"; } public Class<?> getDescribedClass() { return describedClass; } /** * * @return true if this is an empty entry, for a tag that we do not parse. * No class is associated with such an entry. */ public boolean isEmpty() { return describedClass == null; } public String getDescribedClassSimpleName() { return describedClassSimpleName; } public String getDescribedClassPackageName() { return describedClassPackageName; } /** * Get the full name of the class that this describes. Use the Class to get * this, if there is one; else use de/serialize fields that describe this. * * @return */ public String getDescribedClassName() { return getName(); } /** * @return The full, qualified name of the class that this describes. */ @Override public String getJavaTypeName() { return getDescribedClassName(); } @Override public String getCSharpTypeName() { return getDescribedClassName(); } @Override public String getCSharpNamespace() { String csTypeName = this.getCSharpTypeName(); if (csTypeName != null) { int pos = csTypeName.lastIndexOf('.'); return pos > 0 ? csTypeName.substring(0, pos) : CSHARP_PRIMITIVE_NAMESPACE; } else { return null; } } @Override public String getObjectiveCTypeName() { return explictObjectiveCTypeName != null ? explictObjectiveCTypeName : this.getDescribedClassSimpleName(); } @Override public String getDbTypeName() { return null; } public Object getInstance() throws SIMPLTranslationException { return XMLTools.getInstance(describedClass); } public int numFields() { return allFieldDescriptorsByTagNames.size(); } /** * The tagName. */ @Override public String key() { return tagName; } public HashMapArrayList<String, FD> getFieldDescriptorsByFieldName() { return fieldDescriptorsByFieldName; } public HashMapArrayList<String, FD> getDeclaredFieldDescriptorsByFieldName() { return declaredFieldDescriptorsByFieldName; } public HashMap<String, FD> getAllFieldDescriptorsByTagNames() { return allFieldDescriptorsByTagNames; } public HashMap<Integer, FD> getAllFieldDescriptorsByTLVIds() { return allFieldDescriptorsByTLVIds; } public HashMap<String, FD> getAllFieldDescriptorsByBibTeXTag() { return allFieldDescriptorsByBibTeXTag; } public ArrayList<FD> getUnresolvedScopeAnnotationFDs() { return this.unresolvedScopeAnnotationFDs; } public ArrayList<FD> getUnresolvedClassesAnnotationFDs() { return this.unresolvedClassesAnnotationFDs; } public String getSuperClassName() { return XMLTools.getClassSimpleName(describedClass.getSuperclass()); } public static void main(String[] s) { SimplTypesScope mostBasicTranslations = SimplTypesScope.get( "most_basic", ClassDescriptor.class, FieldDescriptor.class, SimplTypesScope.class); try { SimplTypesScope.serialize(mostBasicTranslations, System.out, StringFormat.XML); } catch (SIMPLTranslationException e) { e.printStackTrace(); } } /** * Keep track of any FieldDescriptors with unresolved @serial_scope * declarations so we can try to resolve them later when there is use. * * @param fd */ void registerUnresolvedScopeAnnotationFD(FD fd) { if (unresolvedScopeAnnotationFDs == null) { synchronized (this) { if (unresolvedScopeAnnotationFDs == null) unresolvedScopeAnnotationFDs = new ArrayList<FD>(); } } unresolvedScopeAnnotationFDs.add(fd); } void registerUnresolvedClassesAnnotationFD(FD fd) { if (unresolvedClassesAnnotationFDs == null) { synchronized (this) { if (unresolvedClassesAnnotationFDs == null) unresolvedClassesAnnotationFDs = new ArrayList<FD>(); } } unresolvedClassesAnnotationFDs.add(fd); } /** * Late evaluation of @serial_scope, if it failed the first time around. */ public void resolvePolymorphicAnnotations() { resolveUnresolvedScopeAnnotationFDs(); resolveUnresolvedClassesAnnotationFDs(); } public void resolveUnresolvedScopeAnnotationFDs() { if (unresolvedScopeAnnotationFDs != null) { synchronized (SCOPE_ANNOTATION_LOCK) { if (unresolvedScopeAnnotationFDs != null) { for (int i = unresolvedScopeAnnotationFDs.size() - 1; i >= 0; i--) { FieldDescriptor fd = unresolvedScopeAnnotationFDs .remove(i); fd.resolveUnresolvedScopeAnnotation(); this.mapPolymorphicClassDescriptors((FD) fd); } unresolvedScopeAnnotationFDs = null; } } } } /** * Late evaluation of @serial_scope, if it failed the first time around. */ public void resolveUnresolvedClassesAnnotationFDs() { if (unresolvedClassesAnnotationFDs != null) { for (int i = unresolvedClassesAnnotationFDs.size() - 1; i >= 0; i--) { FieldDescriptor fd = unresolvedClassesAnnotationFDs.remove(i); fd.resolveUnresolvedClassesAnnotation(); this.mapPolymorphicClassDescriptors((FD) fd); this.mapPolymorphicClassDescriptors((FD) fd); } } unresolvedClassesAnnotationFDs = null; } /** * Use the @simpl_other_tags annotation to obtain an array of alternative * (old) tags for this class. * * @return The array of old tags, or null, if there is no @simpl_other_tags * annotation. */ @Override public ArrayList<String> otherTags() { ArrayList<String> result = this.otherTags; if (result == null) { result = new ArrayList<String>(); Class<?> thisClass = getDescribedClass(); if (thisClass != null) { final simpl_other_tags otherTagsAnnotation = thisClass .getAnnotation(simpl_other_tags.class); // commented out since getAnnotation also includes inherited // annotations // ElementState.xml_other_tags otherTagsAnnotation = // thisClass.getAnnotation(ElementState.xml_other_tags.class); if (otherTagsAnnotation != null) for (String otherTag : otherTagsAnnotation.value()) result.add(otherTag); } this.otherTags = result; } return result; } public FD getScalarValueFieldDescripotor() { return scalarValueFieldDescripotor; } public ClassDescriptor<? extends FieldDescriptor> getSuperClass() { return superClass; } /** * method returns whether a strict object graph is required * * @return true if the class was annotated with @simpl_use_equals_equals, * and thus that test will be used during de/serialization to detect * equivalent objects */ public boolean getStrictObjectGraphRequired() { return this.strictObjectGraphRequired; } /** * Find all the Collection fields in this. Assemble a Set of them, in order * to generate import statements. * * @return */ public Set<CollectionType> deriveCollectionDependencies() { HashSet<CollectionType> result = new HashSet<CollectionType>(); for (FieldDescriptor fd : declaredFieldDescriptorsByFieldName) { if (fd.isCollection()) result.add(fd.getCollectionType()); } return result; } /** * Find all the Composite fields in this. Assemble a Set of them, in order * to generate import statements. * * @return */ public Set<ClassDescriptor> deriveCompositeDependencies() { HashSet<ClassDescriptor> result = new HashSet<ClassDescriptor>(); for (FieldDescriptor fd : declaredFieldDescriptorsByFieldName) { if (fd.isNested() || (fd.isCollection())) { ClassDescriptor elementClassDescriptor = fd .getElementClassDescriptor(); if (elementClassDescriptor != null && TypeRegistry .getScalarTypeByName(elementClassDescriptor .getDescribedClassName()) == null) result.add(elementClassDescriptor); Collection<ClassDescriptor> polyClassDescriptors = fd .getPolymorphicClassDescriptors(); if (polyClassDescriptors != null) for (ClassDescriptor polyCd : polyClassDescriptors) result.add(polyCd); } } if (superClass != null) { result.add(superClass); } return result; } /** * Find all the Scalar fields in this. Assemble a Set of them, in order to * generate import statements. * * @return */ public Set<ScalarType> deriveScalarDependencies() { HashSet<ScalarType> result = new HashSet<ScalarType>(); for (FieldDescriptor fd : declaredFieldDescriptorsByFieldName) { if (fd.isScalar()) { ScalarType<?> scalarType = fd.getScalarType(); if (!scalarType.isPrimitive()) { result.add(scalarType); ScalarType<?> operativeScalarType = scalarType .operativeScalarType(); if (!scalarType.equals(operativeScalarType)) result.add(operativeScalarType); } } } /* * for (String genericTypeName: genericTypeVariables) { ScalarType * scalarType = TypeRegistry.getType(genericTypeName); if (scalarType != * null) result.add(scalarType); } */ return result; } @Override public void deserializationPreHook(TranslationContext translationContext) { synchronized (globalClassDescriptorsMap) { String name = this.getName(); if (name != null) { if (globalClassDescriptorsMap.containsKey(name)) error("Already a ClassDescriptor for " + name); else { globalClassDescriptorsMap.put(name, this); } } } } /** * @return The list of meta-information (annotations, attributes, etc.) for * this class. */ public List<MetaInformation> getMetaInformation() { if (metaInfo == null) { metaInfo = new ArrayList<MetaInformation>(); // @simpl_inherit if (superClass != null) metaInfo.add(new MetaInformation(simpl_inherit.class)); // @simpl_tag String autoTagName = XMLTools.getXmlTagName( getDescribedClassSimpleName(), null); if (tagName != null && !tagName.equals("") && !tagName.equals(autoTagName)) metaInfo.add(new MetaInformation(simpl_tag.class, false, tagName)); // @simpl_other_tags ArrayList<String> otherTags = otherTags(); if (otherTags != null && otherTags.size() > 0) metaInfo.add(new MetaInformation(simpl_other_tags.class, true, otherTags.toArray())); } return metaInfo; } @Override public Object clone() { ClassDescriptor cloned = null; try { cloned = (ClassDescriptor) super.clone(); cloned.isCloned = true; cloned.clonedFrom = this; } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return cloned; } public boolean isCloned() { return isCloned; } public ClassDescriptor getClonedFrom() { return clonedFrom; } public void setDescribedClassSimpleName(String describedClassSimpleName) { this.describedClassSimpleName = describedClassSimpleName; this.tagName = XMLTools.getXmlTagName(describedClassSimpleName, null); } public void setDescribedClassPackageName(String describedClassPackageName) { this.describedClassPackageName = describedClassPackageName; } /** * If this class is a generic class, such as MyClass<T>. * * Currently this is not implemented. Please update this javadoc when you * implement it. * * @return */ public boolean isGenericClass() { // TODO Auto-generated method stub // NOT YET IMPLEMENTED! return false; } public void replace(FD oldFD, FD newFD) { // for deserialization: if (oldFD != null) getAllFieldDescriptorsByTagNames().remove(oldFD.getTagName()); getAllFieldDescriptorsByTagNames().put(newFD.getTagName(), newFD); // for serialization: if (oldFD != null) { replace(attributeFieldDescriptors, oldFD, newFD); replace(elementFieldDescriptors, oldFD, newFD); } } private static <T> void replace(List<T> list, T oldVal, T newVal) { if (list == null) return; int i = list.indexOf(oldVal); if (i >= 0 && i < list.size()) { list.set(i, newVal); } } }