package ecologylab.bigsemantics.metametadata; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ecologylab.bigsemantics.Utils; import ecologylab.bigsemantics.html.utils.StringBuilderUtils; import ecologylab.bigsemantics.metadata.Metadata; import ecologylab.bigsemantics.metadata.MetadataClassDescriptor; import ecologylab.bigsemantics.metadata.MetadataFieldDescriptor; import ecologylab.bigsemantics.metametadata.declarations.MetaMetadataFieldDeclaration; import ecologylab.bigsemantics.metametadata.exceptions.MetaMetadataException; import ecologylab.bigsemantics.metametadata.fieldops.FieldOp; import ecologylab.bigsemantics.metametadata.fieldops.FieldOpScope; import ecologylab.generic.HashMapArrayList; import ecologylab.serialization.ElementState; import ecologylab.serialization.FieldType; import ecologylab.serialization.MetaInformation; import ecologylab.serialization.SimplTypesScope; import ecologylab.serialization.XMLTools; import ecologylab.serialization.annotations.simpl_collection; 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_scalar; import ecologylab.serialization.annotations.simpl_scope; import ecologylab.serialization.types.element.IMappable; import ecologylab.textformat.NamedStyle; /** * The basic meta-metadata field class. Encapsulate common attributes and methods for all types of * meta-metadata fields. * * @author quyin * @author damaraju */ @SuppressWarnings({ "rawtypes", "unchecked" }) @simpl_inherit public abstract class MetaMetadataField extends MetaMetadataFieldDeclaration implements ScopeProvider, IMappable<String>, Iterable<MetaMetadataField>, MMDConstants { /** * The Comparator for conveniently sort fields. */ static class LayerComparator implements Comparator<MetaMetadataField> { @Override public int compare(MetaMetadataField o1, MetaMetadataField o2) { // return negation for descending ordering in sort return -Float.compare(o1.getLayer(), o2.getLayer()); } } /** * The Iterator class of fields inside this one. */ class MetaMetadataFieldIterator implements Iterator<MetaMetadataField> { int currentIndex = 0; @Override public boolean hasNext() { int size = kids.size(); boolean result = currentIndex < size; if (result) { for (int i = currentIndex; i < size; i++) { MetaMetadataField nextField = kids.get(i); if (nextField.isIgnoreCompletely()) { currentIndex++; } else { break; } } if (currentIndex == size) { result = false; } } return result; } @Override public MetaMetadataField next() { return kids.get(currentIndex++); } @Override public void remove() { throw new MetaMetadataException("Removing child field through iterator is not yet supported"); } } /** * This class encapsulate the clone-on-write behavior of metadata field descriptor associated with * this field. * * @author quyin * */ protected class MetadataFieldDescriptorProxy { boolean fieldDescriptorCloned = false; private void cloneFieldDescriptorOnWrite() { if (!fieldDescriptorCloned) { MetaMetadataField.this.metadataFieldDescriptor = MetaMetadataField.this.metadataFieldDescriptor .clone(); fieldDescriptorCloned = true; } } public void setTagName(String newTagName) { if (newTagName != null && !newTagName.equals(MetaMetadataField.this.metadataFieldDescriptor.getTagName())) { cloneFieldDescriptorOnWrite(); MetaMetadataField.this.metadataFieldDescriptor.setTagName(newTagName); } } public void setElementClassDescriptor(MetadataClassDescriptor metadataClassDescriptor) { if (metadataClassDescriptor != MetaMetadataField.this.metadataFieldDescriptor .getElementClassDescriptor()) { cloneFieldDescriptorOnWrite(); MetaMetadataField.this.metadataFieldDescriptor .setElementClassDescriptor(metadataClassDescriptor); } } public void setCollectionOrMapTagName(String childTag) { if (childTag != null && !childTag.equals(MetaMetadataField.this.metadataFieldDescriptor .getCollectionOrMapTagName())) { cloneFieldDescriptorOnWrite(); MetaMetadataField.this.metadataFieldDescriptor.setCollectionOrMapTagName(childTag); } } public void setWrapped(boolean wrapped) { if (wrapped != MetaMetadataField.this.metadataFieldDescriptor.isWrapped()) { cloneFieldDescriptorOnWrite(); MetaMetadataField.this.metadataFieldDescriptor.setWrapped(wrapped); } } } static LayerComparator LAYER_COMPARATOR; static ArrayList<MetaMetadataField> EMPTY_COLLECTION; static Iterator<MetaMetadataField> EMPTY_ITERATOR; static List<MmdGenericTypeVar> EMPTY_GENERIC_TYPE_VAR_LIST; static Logger logger; static { LAYER_COMPARATOR = new LayerComparator(); EMPTY_COLLECTION = new ArrayList<MetaMetadataField>(0); EMPTY_ITERATOR = EMPTY_COLLECTION.iterator(); EMPTY_GENERIC_TYPE_VAR_LIST = new ArrayList<MmdGenericTypeVar>(); logger = LoggerFactory.getLogger(MetaMetadataField.class); } @simpl_scalar private String facetType; @simpl_scalar private String minkStyle; @simpl_scalar private String explorableLabel; @simpl_scalar private String showInSnippet; @simpl_scalar private String bibtexField; @simpl_scalar private String fieldAsCount; @simpl_scalar private int minkHeight; @simpl_scalar private int minkWidth; /** * The nested child fields inside this field. */ @simpl_map @simpl_scope(NestedMetaMetadataFieldTypesScope.NAME) @simpl_nowrap @mm_dont_inherit private HashMapArrayList<String, MetaMetadataField> kids; @simpl_collection @simpl_scope(FieldOpScope.NAME) @simpl_nowrap List<FieldOp> fieldOps; // ////////////// Presentation Semantics Below //////////////// /** * Collection of styles */ @simpl_collection("style") @simpl_nowrap private List<MetaMetadataStyle> styles; // ////////////// Members Below //////////////// private MetaMetadataRepository repository; @simpl_map("generic_type_var") @simpl_map_key_field("name") @simpl_nowrap @mm_dont_inherit private HashMapArrayList<String, MmdGenericTypeVar> genericTypeVars; private HashMapArrayList<String, MmdGenericTypeVar> allGenericTypeVars; private String fieldNameInJava; private String capFieldNameInJava; private MetadataFieldDescriptor metadataFieldDescriptor; private MetadataFieldDescriptorProxy fieldDescriptorProxy; /** * Class descriptor for the Metadata object that corresponds to this. Non-null for nested and * collection fields. Null for scalar fields. */ private MetadataClassDescriptor metadataClassDescriptor; /** * Class of the Metadata object that corresponds to this. Non-null for nested and collection * fields. Null for scalar fields. */ private Class<? extends Metadata> metadataClass; /** * (For caching toString()) */ private String toString; private String hashForExtraction; private boolean hashForExtractionOngoing; public MetaMetadataField() { kids = new HashMapArrayList<String, MetaMetadataField>(); } public MetaMetadataField(String name, HashMapArrayList<String, MetaMetadataField> childrenMap) { this.setName(name); this.setChildrenMap(childrenMap); } protected MetaMetadataField(MetaMetadataField copy, String name) { this.setName(name); this.setTag(copy.getTag()); this.setChildrenMap(copy.getChildrenMap()); } public String getLabel() { String label = super.getLabel(); return label == null ? getName() : label; } public String getType() { return null; } public String getExtendsAttribute() { return null; } /** * @return The nested fields inside of this one as a collection. */ public Collection<MetaMetadataField> getChildren() { return kids == null ? null : kids.values(); } public int getChildrenSize() { return kids == null ? 0 : kids.size(); } public boolean hasChildren() { return kids != null && kids.size() > 0; } public MetaMetadataField getChild(int i) { return kids == null ? null : kids.get(i); } /** * @return The nested fields inside of this one as a map. */ public HashMapArrayList<String, MetaMetadataField> getChildrenMap() { return kids; } public HashMapArrayList<String, MetaMetadataField> childrenMap() { if (kids == null) { kids = new HashMapArrayList<String, MetaMetadataField>(); } return kids; } public MetaMetadataField lookupChild(String name) { HashMapArrayList<String, MetaMetadataField> childrenMap = getChildrenMap(); return childrenMap == null ? null : childrenMap.get(name); } public MetaMetadataField lookupChild(MetadataFieldDescriptor metadataFieldDescriptor) { return lookupChild(XMLTools.getXmlTagName(metadataFieldDescriptor.getName(), null)); } public boolean isAuthoredChildOf(MetaMetadataField parentField) { if (parentField instanceof MetaMetadataCompositeField && this.parent() == parentField) return true; if (parentField instanceof MetaMetadataCollectionField && this.parent() == ((MetaMetadataCollectionField) parentField).getElementComposite()) return true; return false; } public boolean hasAuthoredChild(MetaMetadataField childField) { return childField.isAuthoredChildOf(this); } public List<FieldOp> getFieldOps() { return fieldOps; } /** * The (first) xpath of this field. * * @return */ public String getXpath() { return getXpath(0); } public String getSchemaOrgItemtype() { return null; } public List<MetaMetadataStyle> getStyles() { return styles; } public NamedStyle lookupStyle() { NamedStyle result = null; String styleName = getStyleName(); if (styleName != null) result = getRepository().lookupStyle(styleName); return (result != null) ? result : getRepository().getDefaultStyle(); } public MetaMetadataRepository getRepository() { if (repository == null) { repository = findRepository(); } return repository; } private MetaMetadataRepository findRepository() { ElementState parent = parent(); if (parent != null && parent instanceof MetaMetadataField) { return ((MetaMetadataField) parent).getRepository(); } else if (parent instanceof MetaMetadataRepository) { return (MetaMetadataRepository) parent; } else { logger.error("can't find repository for " + this); return null; } } public MmdScope getScope() { return MmdScope.EMPTY_SCOPE; } public MmdScope scope() { return MmdScope.EMPTY_SCOPE; } public void setScope(MmdScope scope) { // no op } public HashMapArrayList<String, MmdGenericTypeVar> getGenericTypeVars() { return genericTypeVars; } public List<MmdGenericTypeVar> getGenericTypeVarsList() { if (genericTypeVars == null) { return EMPTY_GENERIC_TYPE_VAR_LIST; } return (List<MmdGenericTypeVar>) genericTypeVars.values(); } public HashMapArrayList<String, MmdGenericTypeVar> getGenericTypeVarsFromScope() { HashMapArrayList<String, MmdGenericTypeVar> result = allGenericTypeVars; if (result == null) { result = new HashMapArrayList<String, MmdGenericTypeVar>(); Collection<MmdGenericTypeVar> gtvs = getScope().valuesOfType(MmdGenericTypeVar.class); for (MmdGenericTypeVar gtv : gtvs) { if (!result.containsKey(gtv.getName())) { result.put(gtv.getName(), gtv); } } allGenericTypeVars = result; } return result; } /** * Get the field name in java. * * @param capitalized * @return */ public String getFieldNameInJava(boolean capitalized) { if (capitalized) return getCapFieldNameInJava(); String rst = fieldNameInJava; if (rst == null) { rst = XMLTools.fieldNameFromElementName(getName()); fieldNameInJava = rst; } return fieldNameInJava; } /** * Get the capitalized field name in java (could be used in method names). * * @return */ private String getCapFieldNameInJava() { String rst = capFieldNameInJava; if (rst == null) { rst = XMLTools.javaNameFromElementName(getName(), true); capFieldNameInJava = rst; } return capFieldNameInJava; } /** * @return The corresponding metadataFieldDescriptor. */ public MetadataFieldDescriptor getMetadataFieldDescriptor() { return metadataFieldDescriptor; } public MetadataFieldDescriptorProxy getFieldDescriptorProxy() { return fieldDescriptorProxy; } /** * @return The corresponding metadataClassDescriptor. Null for scalars. * * Note that this class descriptor might be incomplete during the compilation process, * e.g. lacking the actual Class object. */ public MetadataClassDescriptor getMetadataClassDescriptor() { return metadataClassDescriptor; } /** * @return The corresponding metadata class. Null for scalars. * * Should be used after inheritance and binding process. */ public Class<? extends Metadata> getMetadataClass() { Class<? extends Metadata> metadataClass = this.metadataClass; if (metadataClass == null) { MetadataClassDescriptor metadataClassDescriptor = this.getMetadataClassDescriptor(); if (metadataClassDescriptor != null) { metadataClass = (Class<? extends Metadata>) metadataClassDescriptor.getDescribedClass(); } this.metadataClass = metadataClass; } return metadataClass; } /** * NOTE: This is not Object.getHashCode()! * * @return A hash that is used for versioning. */ public String getHashForExtraction() { if (hashForExtraction == null) { synchronized (this) { if (hashForExtraction == null) { hashForExtractionOngoing = true; String fp = getFingerprintString(); byte[] bytes = Utils.secureHashBytes(fp); hashForExtraction = Utils.base64urlNoPaddingEncode(bytes); hashForExtractionOngoing = false; } } } return hashForExtraction; } protected String getFingerprintString() { StringBuilder sb = StringBuilderUtils.acquire(); addToFp(sb, getName()); addToFp(sb, isRequired()); addToFp(sb, getTag()); addToFp(sb, getSchemaOrgItemprop()); addToFp(sb, getContextNode()); addToFp(sb, isExtractAsHtml()); addToFp(sb, getFieldParserKey()); addToFp(sb, getFormat()); addCollectionToFp(sb, getXpaths()); addCollectionToFp(sb, fieldOps); for (MetaMetadataField field : kids) { if (field.hashForExtractionOngoing) { addToFp(sb, "self ref to " + field.getName()); } else { sb.append(field.getName()).append(" : "); addToFp(sb, field.getHashForExtraction()); } } String fp = sb.toString(); StringBuilderUtils.release(sb); return fp; } protected void addToFp(StringBuilder fpBuilder, Object obj) { fpBuilder.append(obj).append("\n"); } protected void addCollectionToFp(StringBuilder fpBuilder, Collection collection) { if (collection != null) { for (Object obj : collection) { addToFp(fpBuilder, obj); } } } private String parentString() { StringBuilder result = new StringBuilder(); ElementState parent = parent(); while (parent instanceof MetaMetadataField) { MetaMetadataField pf = (MetaMetadataField) parent; result.insert(0, "<" + pf.getName() + ">"); parent = parent.parent(); } return result.toString(); } @Override public String toString() { String result = toString; if (result == null) { result = getClassSimpleName() + parentString() + "<" + getName() + ">"; toString = result; } return result; } protected void setChildrenMap(HashMapArrayList<String, MetaMetadataField> childMetaMetadata) { this.kids = childMetaMetadata; } protected HashMapArrayList<String, MetaMetadataField> initializeChildrenMap() { return null; } protected void setRepository(MetaMetadataRepository repository) { this.repository = repository; } protected void setGenericTypeVars(HashMapArrayList<String, MmdGenericTypeVar> genericTypeVars) { this.genericTypeVars = genericTypeVars; } public void setMetadataFieldDescriptor(MetadataFieldDescriptor metadataFieldDescriptor) { this.metadataFieldDescriptor = metadataFieldDescriptor; } protected void setMetadataClassDescriptor(MetadataClassDescriptor metadataClassDescriptor) { this.metadataClassDescriptor = metadataClassDescriptor; } protected void setMetadataClass(Class metadataClass) { this.metadataClass = metadataClass; } protected void resetToString() { this.toString = null; } protected void setFieldDescriptorProxy(MetadataFieldDescriptorProxy fieldDescriptorProxy) { this.fieldDescriptorProxy = fieldDescriptorProxy; } /** * @return The file in which this field is declared. */ public File getFile() { Object parent = parent(); if (parent != null && parent instanceof MetaMetadataField) { return ((MetaMetadataField) parent).getFile(); } return null; } public MetaMetadata getTypeMmd() { return null; } public void setTypeMmd(MetaMetadata result) { // no op } /** * Test if two fields are the same one or inherited from the same origin. */ public boolean equalOrSameOrigin(Object obj) { if (obj instanceof MetaMetadataField) { MetaMetadataField f = (MetaMetadataField) obj; if (f.getName().equals(this.getName()) && f.getDeclaringMmd() == this.getDeclaringMmd()) { return true; } } return false; } /** * * @return The tag if existed, or the name of this field. this method is different from * getTagForTypesScope() which is overridden in MetaMetadataCollectionField. they have * different purposes. */ public String getTagOrName() { return getTag() != null ? getTag() : getName(); } public String getTypeOrName() { String type = getType(); return type == null ? getName() : type; } /** * * @return The tag used to look up metadata from MetadataTypesScope. */ public String getTagForTypesScope() { return getTag() != null ? getTag() : getName(); } protected String resolveTag() { return getTag() != null ? getTag() : getName(); } @Override public Iterator<MetaMetadataField> iterator() { HashMapArrayList<String, MetaMetadataField> childrenMap = getChildrenMap(); return (childrenMap != null) ? new MetaMetadataFieldIterator() : EMPTY_ITERATOR; } @Override public String key() { return getName(); } public FieldType getFieldType() { if (this.metadataFieldDescriptor != null) return metadataFieldDescriptor.getType(); else { if (this instanceof MetaMetadataCompositeField) return FieldType.COMPOSITE_ELEMENT; else if (this instanceof MetaMetadataCollectionField) { MetaMetadataCollectionField coll = (MetaMetadataCollectionField) this; if (coll.getChildScalarType() != null) { return FieldType.COLLECTION_SCALAR; } else { return FieldType.COLLECTION_ELEMENT; } } else { return FieldType.SCALAR; } } } public boolean isUsedToDefineInlineMmd() { return getInlineMmd() == null; } public boolean isNewMetadataClass() { return false; } public void setNewMetadataClass(boolean newMetadataClass) { // no op } /** * bind the corresponding metadata field descriptor to this field, matched by field name. * customize the field descriptor when needed. * <p> * note that the customization assumes that the field descriptor is copied from super class to * this class. if this changes in the future, the customization process should do this copy. see * {@code customizeFieldDescriptor()}. * <p> * lazy evaluation. result cached. * * @param metadataTScope * the translation scope of (generated) metadata classes. * @param metadataClassDescriptor * the current metadata class descriptor. * @return the bound metadata field descriptor if any. * * @see {@code customizeFieldDescriptor()} */ protected MetadataFieldDescriptor bindMetadataFieldDescriptor(SimplTypesScope metadataTScope, MetadataClassDescriptor metadataClassDescriptor) { MetadataFieldDescriptor metadataFieldDescriptor = this.metadataFieldDescriptor; if (metadataFieldDescriptor == null) { synchronized (this) { metadataFieldDescriptor = this.metadataFieldDescriptor; String fieldName = this.getFieldNameInJava(false); if (metadataFieldDescriptor == null) { metadataFieldDescriptor = (MetadataFieldDescriptor) metadataClassDescriptor .getFieldDescriptorByFieldName(fieldName); if (metadataFieldDescriptor != null) { // if we don't have a field, then this is a wrapped collection, so we need to get the // wrapped field descriptor if (metadataFieldDescriptor.getField() == null) metadataFieldDescriptor = (MetadataFieldDescriptor) metadataFieldDescriptor .getWrappedFD(); this.metadataFieldDescriptor = metadataFieldDescriptor; // this method handles polymorphic type / changing tags if (this.metadataFieldDescriptor != null) { fieldDescriptorProxy = new MetadataFieldDescriptorProxy(); customizeFieldDescriptor(metadataTScope, fieldDescriptorProxy); } if (this.metadataFieldDescriptor != metadataFieldDescriptor) { // the field descriptor has been modified in customizeFieldDescriptor()! // we need to update it in the class descriptor so that deserialization of metadata // objects can work correctly, e.g. using the right classDescriptor for a composite // field or a right elementClassDescriptor for a collection field. customizeFieldDescriptorInClass(metadataTScope, metadataClassDescriptor); } } } else { warning("Ignoring <" + fieldName + "> because no corresponding MetadataFieldDescriptor can be found."); } } } return metadataFieldDescriptor; } private void customizeFieldDescriptorInClass(SimplTypesScope metadataTScope, MetadataClassDescriptor metadataClassDescriptor) { MetadataFieldDescriptor oldFD = metadataClassDescriptor.getFieldDescriptorByFieldName(this .getFieldNameInJava(false)); // oldFD is the non-wrapper one String newTagName = this.metadataFieldDescriptor.getTagName(); metadataClassDescriptor.replace(oldFD, this.metadataFieldDescriptor); MetadataFieldDescriptor wrapperFD = (MetadataFieldDescriptor) this.metadataFieldDescriptor .getWrapper(); if (wrapperFD != null) { MetadataFieldDescriptor clonedWrapperFD = wrapperFD.clone(); clonedWrapperFD.setTagName(newTagName); clonedWrapperFD.setWrappedFD(this.metadataFieldDescriptor); metadataClassDescriptor.replace(wrapperFD, clonedWrapperFD); } FieldType fieldType = this.metadataFieldDescriptor.getType(); if (fieldType == FieldType.COLLECTION_ELEMENT || fieldType == FieldType.MAP_ELEMENT) { if (!this.metadataFieldDescriptor.isWrapped()) { String childTagName = this.metadataFieldDescriptor.getCollectionOrMapTagName(); oldFD = metadataClassDescriptor.getFieldDescriptorByTag(childTagName, metadataTScope); metadataClassDescriptor.replace(oldFD, this.metadataFieldDescriptor); } } } /** * this method customizes field descriptor for this field, e.g. specific type or tag. * * @param metadataTScope * the translation scope of (generated) metadata classes. * @param fdProxy * the current metadata field descriptor. */ protected void customizeFieldDescriptor(SimplTypesScope metadataTScope, MetadataFieldDescriptorProxy fdProxy) { fdProxy.setTagName(this.getTagOrName()); } /** * * @param deserializationMM * @return true if binding succeeds */ public boolean validateMetaMetadataToMetadataBinding(MetaMetadataField deserializationMM) { if (deserializationMM != null) // should be always { MetadataClassDescriptor originalClassDescriptor = this.getMetadataClassDescriptor(); MetadataClassDescriptor deserializationClassDescriptor = deserializationMM .getMetadataClassDescriptor(); // quick fix for a NullPointerException for RSS. originalClassDescriptor can be null because // it might be a meta-metadata that does not generate metadata class, e.g. xml if (originalClassDescriptor == null) return true; // use the one from deserialization boolean sameMetadataSubclass = originalClassDescriptor.equals(deserializationClassDescriptor); // if they have the same metadataClassDescriptor, they can be of the same type, or one // of them is using "type=" attribute. boolean useMmdFromDeserialization = sameMetadataSubclass && (deserializationMM.getType() != null); if (!useMmdFromDeserialization && !sameMetadataSubclass) // if they have different metadataClassDescriptor, need to choose the more specific one useMmdFromDeserialization = originalClassDescriptor.getDescribedClass() .isAssignableFrom( deserializationClassDescriptor.getDescribedClass()); return useMmdFromDeserialization; } else { error("No meta-metadata in root after direct binding :-("); return false; } } /** * get the type name of this field, in terms of meta-metadata. * * TODO redefining this. * * @return the type name. */ abstract protected String getTypeName(); /** * generate java type name string. since type name will be used for several times (both in member * definition and methods), it should be cached. * * note that this could be different from changing getTypeName() into camel case: consider Entity * for composite fields or ArrayList for collection fields. */ abstract protected String getTypeNameInJava(); abstract public MetadataFieldDescriptor findOrGenerateMetadataFieldDescriptor(SimplTypesScope tscope, MetadataClassDescriptor contextCd); /** * Add additional meta information about the field to an existing meta information list. * * @param metaInfoBuf * The existing meta information list. Additional meta information will be added to this * list. Cannot be null. * @param compiler * Providing compiler services such as dependency handling, in case needed in this * method. This is not used right now; just for extensibility. */ abstract public void addAdditionalMetaInformation(List<MetaInformation> metaInfoBuf, MmdCompilerService compiler); }