package ecologylab.bigsemantics.metametadata; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ecologylab.bigsemantics.metametadata.exceptions.MetaMetadataException; import ecologylab.generic.HashMapArrayList; import ecologylab.generic.ReflectionTools; import ecologylab.generic.StringBuilderBaseUtils; import ecologylab.serialization.ClassDescriptor; import ecologylab.serialization.FieldType; import ecologylab.serialization.types.ScalarType; /** * All inheritance magic happens here! * * @author quyin */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class InheritanceHandler { private static final Logger logger; static { logger = LoggerFactory.getLogger(InheritanceHandler.class); } private ArrayList<MetaMetadataField> stack = new ArrayList<MetaMetadataField>(); public void push(MetaMetadataField field) { stack.add(field); } public MetaMetadataField pop() { return stack.remove(stack.size() - 1); } public MetaMetadataField top() { return stack.get(stack.size() - 1); } public int search(MetaMetadataField field) { for (int i = 0; i < stack.size(); ++i) { if (field == stack.get(i)) { return i; } } return -1; } /** * @param fieldToInclude * If this is not null, the result string will include this field as if it was pushed to * the top of the stack. Nothing actually pushed though. * @return A string representing the current content of the stack. */ public String getFieldStackTrace(MetaMetadataField fieldToInclude) { StringBuilder sb = StringBuilderBaseUtils.acquire(); for (int i = 0; i <= stack.size(); ++i) { // virtually push fieldToInclude to the stack MetaMetadataField field = (i == stack.size()) ? fieldToInclude : stack.get(i); if (field instanceof MetaMetadataCompositeField) { // skip the element composite in a collection field for printing the stack. MetaMetadataCollectionField enclosingCollectionField = ((MetaMetadataCompositeField) field).getEnclosingCollectionField(); if (enclosingCollectionField != null && enclosingCollectionField == stack.get(i - 1)) { continue; } } if (field != null) { sb.append(i == 0 ? "" : ".").append(field.getName()); } } String result = sb.toString(); StringBuilderBaseUtils.release(sb); return result; } public void handleMmdRepository(MetaMetadataRepository repository) { List<MetaMetadata> mmds = new ArrayList<MetaMetadata>(repository.getMetaMetadataCollection()); MmdScope repoScope = new MmdScope("repo"); for (MetaMetadata mmd : mmds) { repoScope.put(mmd.getName(), mmd); mmd.scope().addAncestor(repoScope); mmd.setRepository(repository); } RepositoryOrdering ordering = new RepositoryOrderingByGeneration(); mmds = ordering.orderMetaMetadataForInheritance(mmds); for (MetaMetadata mmd : mmds) { handleMmd(mmd); } } public void handleMmd(MetaMetadata mmd) { if (mmd != null && !mmd.isInheritDone() && search(mmd) < 0) { logger.debug("{}: Handling mmd {}", mmd, mmd); push(mmd); try { MetaMetadata superMmd = findSuperMmd(mmd); inheritScope(mmd, superMmd, true); if (superMmd == null) { if (!MetaMetadata.isRootMetaMetadata(mmd)) { throw new MetaMetadataException("Can't find super type for " + mmd); } // mmd is the root mmd.setNewMetadataClass(true); mergeChildren(mmd, null); } else { // mmd is not the root handleMmd(superMmd); mergeAttributes(mmd, superMmd); mergeChildren(mmd, superMmd); } mmd.setInheritDone(true); logger.debug("{}: Inheritance done", mmd); } finally { pop(); } } } /** * Merge attributes from superField to field. * * Attributes include not only scalar values (e.g. hide, label) but also some collections (such as * xpaths). If an attribute is a collection, it is expected that its elements have a by-content * equals() defined. * * Attributes explicitly specified on field will take precedence. * * @param field * @param superField */ private void mergeAttributes(MetaMetadataField field, MetaMetadataField superField) { logger.debug("{}: Merging attributes from {}...", field, superField); MetaMetadataClassDescriptor superClassDescriptor = (MetaMetadataClassDescriptor) ClassDescriptor.getClassDescriptor(superField); MetaMetadataClassDescriptor classDescriptor = (MetaMetadataClassDescriptor) ClassDescriptor.getClassDescriptor(field); for (MetaMetadataFieldDescriptor attributeFieldDescriptor : superClassDescriptor) { String attributeName = attributeFieldDescriptor.getName(); if (classDescriptor.getFieldDescriptorByFieldName(attributeName) == null) { continue; } if (attributeFieldDescriptor.isInheritable()) { try { mergeAttributeHelper(field, superField, attributeFieldDescriptor); } catch (Exception e) { String msg = String.format("%s.%s: Attribute inheritance failed from %s", field, attributeName, superField); logger.error(msg, e); } } } } /** * Merge one attribute. * * @param field * @param superField * @param attributeFieldDescriptor */ private void mergeAttributeHelper(MetaMetadataField field, MetaMetadataField superField, MetaMetadataFieldDescriptor attributeFieldDescriptor) { String attributeName = attributeFieldDescriptor.getName(); Object superValue = attributeFieldDescriptor.getValue(superField); Object localValue = attributeFieldDescriptor.getValue(field); boolean attributeInherited = false; if (attributeFieldDescriptor.isCollection()) { // Append elements from inheritFrom to this field's list List superList = (List) superValue; if (superList != null && superList.size() > 0) { List localList = (List) localValue; if (localList == null) { localList = new ArrayList(); attributeFieldDescriptor.setField(field, localList); } for (Object element : superList) { // List.contains() uses equals() to compare, so element should have its own equals() // defined, otherwise there can be problems! if (!localList.contains(element)) { localList.add(element); attributeInherited = true; } } } } else { // For a scalar field, scalarType will be the scalarType for that field. // For a composite field or a collection-of-composite field, scalarType will be null. // For a collection-of-scalar field, scalarType will be the child scalarType for that // field. ScalarType scalarType = attributeFieldDescriptor.getScalarType(); if (scalarType != null && scalarType.isDefaultValue(localValue) && !scalarType.isDefaultValue(superValue)) { attributeFieldDescriptor.setField(field, superValue); attributeInherited = true; } } // if (fieldDescriptor.isCollection) if (attributeInherited) { logger.debug("{}.{}: Field attribute inherited from {}: {}", field, attributeName, superField, localValue); } } /** * Merge children, in a breadth-first way: immediate children get processed first, then the next * generation, and so on. * * @param field * @param superField */ private void mergeChildren(MetaMetadataField field, MetaMetadataField superField) { logger.debug("{}: Merging children from {}...", field, superField); Set<String> childrenNames = new HashSet<String>(); collectChildrenNames(field, childrenNames); collectChildrenNames(superField, childrenNames); // these are purely inherited fields that need to be reincarnated as if they were authored in // field, because they are using generic types and with the current scope the type will be // different from what they were specified with in the superField. Set<MetaMetadataField> reincarnated = new HashSet<MetaMetadataField>(); // first, for all immediate children, merge attributes or copy the field object over from // superField. // this "breadth-first" process is critical for dealing with recursions. for (String childName : childrenNames) { MetaMetadataField f0 = superField == null ? null : superField.lookupChild(childName); MetaMetadataField f1 = field.lookupChild(childName); if (f0 == null && f1 != null) { // f1 is a newly defined field. f1.scope().addAncestor(field.scope()); field.setNewMetadataClass(true); if (f1 instanceof MetaMetadataNestedField) { String packageName = ((MetaMetadataNestedField) field).packageName(); ((MetaMetadataNestedField) f1).setPackageName(packageName); } if (field instanceof MetaMetadata) { f1.setDeclaringMmd((MetaMetadata) field); } } if (f0 != null && f1 == null && isUsingGenerics(f0)) { // if the super field is using generics, we will need to re-evaluate generic type vars, and // this can change the type that is being used for f1 f1 = ReflectionTools.getInstance(f0.getClass()); f1.setName(childName); f1.setParent(field); if (f1 instanceof MetaMetadataCollectionField) { ((MetaMetadataCollectionField) f1).createElementComposite(); } field.getChildrenMap().put(childName, f1); reincarnated.add(f1); logger.debug("{}: Reincarnated since superField {} is generic", f1, f0); } if (f0 != null && f1 != null && f0 != f1) { // set attributes and superField early. this is important because in the next for loop, when // we do inheritField(f1, f0), it is possible that f0 is not yet done inheritance. in that // case, we may want to deal with f0 first. mergeAttributes(f1, f0); f1.setSuperField(f0); } else if (f0 != null && f1 == null) { field.childrenMap().put(childName, f0); logger.debug("{}: Directly put {} into it", field, f0); } } // then, merge further nested structures in those immediate children for (String childName : childrenNames) { MetaMetadataField f0 = superField == null ? null : superField.lookupChild(childName); MetaMetadataField f1 = field.lookupChild(childName); if (f1 != null && f0 != f1) { boolean previouslyNotAncestor = false; FieldType f1type = f1.getFieldType(); if (f1type != FieldType.SCALAR && f1type != FieldType.COLLECTION_SCALAR) { // currently, we only do scope inheritance for nested fields. previouslyNotAncestor = !f1.scope().isAncestor(field.scope()); f1.scope().addAncestor(field.scope()); } if (f0 != null && f1.getClass() != f0.getClass()) { throw new MetaMetadataException(f1 + ": Can't inherit from " + f0 + ": wrong field type!"); } inheritField(f1, f0); if (!f1.isAuthoredChildOf(field) && previouslyNotAncestor) { f1.scope().removeImmediateAncestor(field.scope()); } if (f1.isNewMetadataClass()) { field.setNewMetadataClass(true); } if (f0 != null && f1.getTypeMmd() != f0.getTypeMmd()) { field.setNewMetadataClass(true); } if (reincarnated.contains(f1) && !isTypeDifferent(f1, f0)) { field.getChildrenMap().put(childName, f0); logger.debug("{}: Field unchanged, reincarnated instance destroyed", f1); } } } } private void collectChildrenNames(MetaMetadataField field, Set<String> childrenNames) { if (field != null) { for (int i = 0; i < field.getChildrenSize(); ++i) { childrenNames.add(field.getChild(i).getName()); } } } /** * Entrance method for inheriting attributes and children from superField to field. It merges * attributes, deals with scopes, finds the typeMmd, then call other methods to do the actual * dirty work. * * @param field * The field being processed. * @param superField * Can be null. */ private void inheritField(MetaMetadataField field, MetaMetadataField superField) { String fieldStackTrace = getFieldStackTrace(field); logger.debug("{}: Inheriting from {}, stack trace: {}", field, superField, fieldStackTrace); if (search(field) < 0) { FieldType fieldType = field.getFieldType(); logger.debug("{}: Field type {}", field, fieldType); if (fieldType != FieldType.SCALAR && fieldType != FieldType.COLLECTION_SCALAR) { MetaMetadataNestedField nested = (MetaMetadataNestedField) field; if (superField != null) { inheritScope(field, superField, false); } MetaMetadata typeMmd = null; if (nested.isInlineDefinition()) { typeMmd = createInlineMmd(nested.metaMetadataCompositeField()); } else { typeMmd = findTypeMmd(nested); inheritScope(nested, typeMmd, false); } if (typeMmd == null) { throw new MetaMetadataException("Cannot find typeMmd for " + field + ", type=" + nested.getType() + ", extends=" + nested.getExtendsAttribute()); } handleMmd(typeMmd); if (superField == null) { inheritFromTypeMmd(nested, typeMmd); } else { inheritFromSuperField(nested, typeMmd, superField); } } field.setInheritDone(true); logger.debug("{}: Inheritance done", field); } } /** * Create inline mmd type using a composite. * * @param composite * @return The created inline mmd. */ private MetaMetadata createInlineMmd(MetaMetadataCompositeField composite) { logger.debug("{}: Creating inline mmd...", composite); MmdScope scope = composite.scope(); // determine the new type name String typeName = composite.getTypeOrName(); if (typeName == null) { throw new MetaMetadataException("Cannot create inline type for " + composite + ", did you specify type or child_type?"); } if (scope.get(typeName) != null) { throw new MetaMetadataException("Type name " + typeName + " already in use: " + composite + ", use another type name."); } // determine which existing mmd to inherit from String extendsName = composite.getExtendsAttribute(); Object superMmdObj = scope.get(extendsName); if (superMmdObj == null) { throw new MetaMetadataException("Cannot find super type " + extendsName + " for " + composite); } if (!(superMmdObj instanceof MetaMetadata)) { throw new MetaMetadataException(extendsName + " is not a mmd type: " + composite); } MetaMetadata superMmd = (MetaMetadata) superMmdObj; logger.debug("{}: Super type of inline mmd: {}", composite, superMmd); // create inline mmd and add it to parent's scope MetaMetadata inlineMmd = createInlineMmdHelper(composite, typeName, superMmd); MmdScope parentScope = getParentScope(composite); parentScope.put(inlineMmd.getName(), inlineMmd); handleMmd(inlineMmd); return inlineMmd; } private MetaMetadata createInlineMmdHelper(MetaMetadataCompositeField composite, String typeName, MetaMetadata superMmd) { // create and set inlineMmd attributes MetaMetadata inlineMmd = new MetaMetadata(); inlineMmd.setName(typeName); inlineMmd.setPackageName(composite.packageName()); inlineMmd.setType(null); inlineMmd.setTypeMmd(superMmd); inlineMmd.setExtendsAttribute(superMmd.getName()); inlineMmd.setRepository(composite.getRepository()); inlineMmd.scope().addAncestors(superMmd.scope(), composite.scope()); if (composite.getSchemaOrgItemtype() != null) { inlineMmd.setSchemaOrgItemtype(composite.getSchemaOrgItemtype()); } inlineMmd.setNewMetadataClass(true); logger.debug("{}: Inline mmd created", inlineMmd); // set composite attributes and fields composite.setInlineMmd(inlineMmd); composite.setTypeMmd(inlineMmd); composite.setType(inlineMmd.getName()); composite.setExtendsAttribute(null); if (composite.getTag() == null) { composite.setTag(inlineMmd.getName()); // keep the tag name } HashMapArrayList<String, MetaMetadataField> kids = composite.getChildrenMap(); for (String kidKey : kids.keySet()) { MetaMetadataField kid = kids.get(kidKey); inlineMmd.getChildrenMap().put(kidKey, kid); kid.setParent(inlineMmd); } kids.clear(); logger.debug("{}: Attributes and fields set after creating inline mmd", composite); return inlineMmd; } /** * A convenient method to deal with the element composite object inside a collection field. * * @param composite * @return */ private MmdScope getParentScope(MetaMetadataCompositeField composite) { if (composite.getEnclosingCollectionField() == composite.parent()) { return ((MetaMetadataNestedField) composite.parent().parent()).scope(); } return ((MetaMetadataNestedField) composite.parent()).scope(); } /** * Finds the superMmd for a given mmd. The superMmd is specified by type or extends. * * @param mmd * @return */ private MetaMetadata findSuperMmd(MetaMetadata mmd) { if (mmd.getSuperMmd() != null) { return mmd.getSuperMmd(); } MetaMetadata result = findMmdFromScope(mmd); mmd.setSuperMmd(result); logger.debug("{}: Super type {}", mmd, result); return result; } /** * Finds the typeMmd for a (nested) field. The typeMmd is the mmd that is used as the type of a * composite field or the child type of a collection field. * * @param field * @return */ private MetaMetadata findTypeMmd(MetaMetadataField field) { if (field instanceof MetaMetadata) { return (MetaMetadata) field; } if (field.getTypeMmd() != null) { return field.getTypeMmd(); } MetaMetadata result = findMmdFromScope(field); field.setTypeMmd(result); logger.debug("{}: Type mmd {}", field, result); return result; } /** * Helper method that looks for mmd from the field's scope. * * @param field * @return */ private MetaMetadata findMmdFromScope(MetaMetadataField field) { MmdScope scope = field.scope(); String type = field.getType(); String extendsAttribute = field.getExtendsAttribute(); MetaMetadata result = null; if (result == null && type != null) { result = resolveTypeName(scope, type); logger.debug("{}: Type name {} resolved to {}", field, type, result); } if (result == null && extendsAttribute != null) { result = resolveTypeName(scope, extendsAttribute); logger.debug("{}: Type name {} resolved to {}", field, extendsAttribute, result); if (result != null && field instanceof MetaMetadata) { ((MetaMetadata) field).setNewMetadataClass(true); } } return result; } /** * Resolve a name to a mmd. The name can be a concrete mmd name or a generic type var. In the * later case, this method will use the scope to resolve the generic type var to the most general * concrete mmd type that applies. * * @param scope * @param typeName * @return */ private MetaMetadata resolveTypeName(MmdScope scope, String typeName) { Object obj = scope.get(typeName); if (obj instanceof MetaMetadata) { return (MetaMetadata) obj; } else if (obj instanceof MmdGenericTypeVar) { MmdGenericTypeVar gtv = (MmdGenericTypeVar) obj; Map<String, MmdGenericTypeVar> nestedGtvScope = gtv.getNestedGenericTypeVarScope(); if (nestedGtvScope != null && nestedGtvScope.size() > 0) { scope.putAll(nestedGtvScope); } if (gtv.getArg() != null) { MetaMetadata resolvedArgMmd = resolveTypeName(scope, gtv.getArg()); gtv.setResolvedArgMmd(resolvedArgMmd); return resolvedArgMmd; } else if (gtv.getExtendsAttribute() != null) { MetaMetadata resolvedExtendsMmd = resolveTypeName(scope, gtv.getExtendsAttribute()); gtv.setResolvedExtendsMmd(resolvedExtendsMmd); return resolvedExtendsMmd; } else { // neither arg nor extends specified for gtv return resolveTypeName(scope, MetaMetadata.ROOT_MMD_NAME); } } else { throw new MetaMetadataException("MetaMetadata or GenericTypeVar expected, " + "but got " + obj + " with " + typeName + ". Current mmd field stack for inheritance: " + getFieldStackTrace(null)); } } /** * Helper method that deals with collection fields and their element composites. * * @param newField * @param typeMmd */ private void inheritFromTypeMmd(MetaMetadataField newField, MetaMetadata typeMmd) { logger.debug("{}: inheriting from type mmd {}", newField, typeMmd); if (newField instanceof MetaMetadataCollectionField) { push(newField); try { MetaMetadataCompositeField elementComposite = ((MetaMetadataCollectionField) newField).getPreparedElementComposite(); inheritField(elementComposite, typeMmd); } finally { pop(); } } else { inheritField(newField, typeMmd); } } /** * Inherit attributes and children from superField. This method also deals with cases where a more * specific type (that is different from the type used on the superField) is used on the * (sub)field. * * @param field * @param fieldTypeMmd * @param superField */ private void inheritFromSuperField(MetaMetadataField field, MetaMetadata fieldTypeMmd, MetaMetadataField superField) { logger.debug("{}: inheriting from super field {}", field, superField); if (field instanceof MetaMetadataCollectionField) { push(field); try { MetaMetadataCompositeField elementComposite = ((MetaMetadataCollectionField) field).getPreparedElementComposite(); MetaMetadataCompositeField superElementComposite = ((MetaMetadataCollectionField) superField).getPreparedElementComposite(); mergeAttributes(elementComposite, superElementComposite); inheritField(elementComposite, superElementComposite); } finally { pop(); } } else { push(field); try { if (!(superField instanceof MetaMetadata) && !superField.isInheritDone()) { // if superField is not done, try to deal with superField first // note that superField can be a MetaMetadata and we don't want to deal with that case. inheritField(superField, superField.getSuperField()); } if (fieldTypeMmd == findTypeMmd(superField)) { // not changing type on the sub field. mergeChildren(field, superField); } else { // changing type on the sub field. mergeChildren(field, fieldTypeMmd); mergeChildren(field, superField); } field.setInheritDone(true); } finally { pop(); } } } /** * Key method to handle lexical scopes. For generic type vars, it also checks bounds. * * @param dest * @param src * @param validateGenerics */ private void inheritScope(ScopeProvider dest, ScopeProvider src, boolean validateGenerics) { if (src == null) { return; } MmdScope srcScope = src.getScope(); if (srcScope != null) { MmdScope destScope = dest.scope(); if (srcScope.size() > 0) { // handle generic type vars (inheriting, checking bounds, etc) logger.debug("{}: Inheriting and validating generic type vars from {}", dest, src); Collection<MmdGenericTypeVar> srcGtvs = srcScope.valuesOfType(MmdGenericTypeVar.class); for (MmdGenericTypeVar srcGtv : srcGtvs) { if (srcGtv.nothingSpecified()) { srcGtv.setExtendsAttribute(MetaMetadata.ROOT_MMD_NAME); } String gtvName = srcGtv.getName(); MmdGenericTypeVar destGtv = (MmdGenericTypeVar) destScope.get(gtvName); if (destGtv != null && destGtv != srcGtv) { if (destGtv.nothingSpecified()) { // a GTV can be omitted if nothing changes about it. in this case, ignore it. destScope.remove(gtvName); } else { // otherwise, inherited nested type vars inheritScope(destGtv, srcGtv, validateGenerics); if (validateGenerics) { validateGenericTypeVar(dest, src, gtvName, destGtv, srcGtv); } } } else { destScope.put(gtvName, srcGtv); } // if (gtv1 != null) } // for } // if (srcScope.size() > 0) // handle general scope destScope.addAncestor(srcScope); } } /** * Validate generic type var bounds. Parameter dest, src, and gtvName are just for error * reporting. * * @param dest * @param src * @param gtvName * @param destGtv * @param srcGtv */ private void validateGenericTypeVar(ScopeProvider dest, ScopeProvider src, String gtvName, MmdGenericTypeVar destGtv, MmdGenericTypeVar srcGtv) { if (srcGtv.isAssignment() && destGtv.isAssignment()) { // note that when evaluating srcMmd, we need to use destGtv because that is what will be used // when determining field type. and srcGtv.scope should be an ancestor of destGtv.scope. MetaMetadata srcMmd = resolveTypeName(destGtv.getScope(), srcGtv.getArg()); MetaMetadata destMmd = resolveTypeName(destGtv.getScope(), destGtv.getArg()); if (srcMmd != destMmd) { throw new MetaMetadataException("Incompatiable assignments to generic type var " + gtvName + " from " + src + " to " + dest); } } else if (srcGtv.isAssignment() && destGtv.isBound()) { throw new MetaMetadataException("Generic type var " + gtvName + " already assigned in " + src + ", cannot respecify bound in " + dest); } else if (srcGtv.isBound() && destGtv.isAssignment()) { if (!checkAssignmentWithBounds(destGtv, srcGtv)) { throw new MetaMetadataException("Generic type bound(s) not satisfied for " + gtvName + " in " + dest); } } else if (srcGtv.isBound() && destGtv.isBound()) { destGtv.setRebound(true); if (!checkBoundsWithBounds(destGtv, srcGtv)) { throw new MetaMetadataException("Generic type bound(s) not compatible for " + gtvName + " in " + dest); } } logger.debug("{}: Generic type var {} validated", dest, destGtv); } private boolean checkAssignmentWithBounds(MmdGenericTypeVar argGtv, MmdGenericTypeVar boundGtv) { MmdScope scope = argGtv.getScope(); MetaMetadata argMmd = resolveTypeName(scope, argGtv.getArg()); handleMmd(argMmd); MetaMetadata lowerBoundMmd = resolveTypeName(scope, boundGtv.getExtendsAttribute()); handleMmd(lowerBoundMmd); boolean satisfyLowerBound = lowerBoundMmd == null || argMmd.isDerivedFrom(lowerBoundMmd); // future work: upper bound return satisfyLowerBound; } private boolean checkBoundsWithBounds(MmdGenericTypeVar local, MmdGenericTypeVar other) { MmdScope scope = local.getScope(); MetaMetadata lowerBoundMmdLocal = resolveTypeName(scope, local.getExtendsAttribute()); handleMmd(lowerBoundMmdLocal); MetaMetadata lowerBoundMmdOther = resolveTypeName(scope, other.getExtendsAttribute()); handleMmd(lowerBoundMmdOther); boolean lowerBoundsCompatible = lowerBoundMmdOther == null || lowerBoundMmdLocal.isDerivedFrom(lowerBoundMmdOther); // future work: upper bound return lowerBoundsCompatible; } /** * Determines if a field involves generics. If so, the inheritance code may need to re-evaluate * the generic type vars. * * @param field * @return */ private boolean isUsingGenerics(MetaMetadataField field) { Map<String, MmdGenericTypeVar> gtvScope = field.getGenericTypeVars(); if (gtvScope != null && gtvScope.size() > 0) { return true; } Object obj = field.scope().get(field.getType()); if (obj == null) { obj = field.scope().get(field.getExtendsAttribute()); } if (obj instanceof MmdGenericTypeVar) { return true; } return false; } /** * @param f1 * @param f0 * @return If the type of the two fields, or the type of any of their children, is different. */ private boolean isTypeDifferent(MetaMetadataField f1, MetaMetadataField f0) { if (f1 != null && f0 != null) { if (f0.getTypeMmd() != f1.getTypeMmd()) { return true; } for (MetaMetadataField child1 : f1) { MetaMetadataField child0 = f0.lookupChild(child1.getName()); if (child0 == null || child0.getTypeMmd() != child1.getTypeMmd()) { return true; } } } return false; } }