| /************************************************************************ |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER |
| * |
| * Copyright 2009, 2010 Oracle and/or its affiliates. All rights reserved. |
| * |
| * Use is subject to license terms. |
| * |
| * Licensed 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. You can also |
| * obtain a copy of the License at http://odftoolkit.org/docs/license.txt |
| * |
| * 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 schema2template.model; |
| |
| import com.sun.msv.grammar.AttributeExp; |
| import com.sun.msv.grammar.DataExp; |
| import com.sun.msv.grammar.ElementExp; |
| import com.sun.msv.grammar.Expression; |
| import com.sun.msv.grammar.NameClassAndExpression; |
| import com.sun.msv.grammar.ValueExp; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * One of the following RelaxNG definitions of an Element, Attribute, Value or Datatype. |
| * |
| * <p>Each PuzzlePiece encapsulates one MSV Expression. Two PuzzlePiece can share |
| * the same MSV Expression (RelaxNG pattern: |
| * <element><choice><name>aName</name><name>anotherName</name></choice></element>) |
| * </p> |
| * <p>Conventions: <ul> |
| * <li>hashCode uses the hashCode from the encapsulated Expressions. So two Definitions (rarely) can |
| * have the same hashCode. Equals uses Name _and_ hashCode.</li> |
| * <li>Sorting is done by ns:local tag names as first key and hashCode as second key.</li> |
| * <li>All returned PuzzlePieceSet objects are immutable to protect them against |
| * naive usage in velocity templates</li></ul></p> |
| */ |
| public class PuzzlePiece implements Comparable<PuzzlePiece>, QNamedPuzzleComponent { |
| |
| private static MSVExpressionVisitorType TYPE_VISITOR = new MSVExpressionVisitorType(); |
| private static MSVNameClassVisitorList NAME_VISITOR = new MSVNameClassVisitorList(); |
| private Expression mExpression; |
| // all multiples of this tagname (contains only this if there are no multiples) |
| private PuzzlePieceSet mMultiples = new PuzzlePieceSet(); |
| // definitions of elements which can have this as children |
| private PuzzlePieceSet mParents = new PuzzlePieceSet(); |
| // DEFINITION CONTENT |
| // ns:local tagname |
| private String mName; |
| |
| /* Properties for PuzzlePiece of Type.ELEMENT */ |
| private PuzzlePieceSet mChildElements = new PuzzlePieceSet(); |
| private HashSet<String> mMandatoryChildElementNames = new HashSet<String>(); |
| private PuzzlePieceSet mAttributes = new PuzzlePieceSet(); |
| private HashSet<String> mMandatoryChildAttributeNames = new HashSet<String>(); |
| private boolean mCanHaveText = false; |
| private Set<Expression> mSingletonChildExpressions; |
| private Set<Expression> mMultipleChildExpressions; |
| |
| /* Properties for PuzzlePiece of Type.ATTRIBUTE */ |
| // Values like "left", "centered", "right" |
| private PuzzlePieceSet mValues = new PuzzlePieceSet(); |
| // generic Data Types like "string", "countryCode", ... |
| private PuzzlePieceSet mDatatypes = new PuzzlePieceSet(); |
| |
| private PuzzlePiece(Expression exp, String name) { |
| mExpression = exp; |
| mName = name; |
| } |
| |
| private PuzzlePiece(Expression exp) { |
| mExpression = exp; |
| MSVExpressionType type = (MSVExpressionType) exp.visit(TYPE_VISITOR); |
| if (type == MSVExpressionType.ATTRIBUTE || type == MSVExpressionType.ELEMENT) { |
| List<String> names = (List<String>) ((NameClassAndExpression) exp).getNameClass().visit(NAME_VISITOR); |
| if (names == null || names.size() != 1) { |
| throw new RuntimeException("Definition: ELEMENT or ATTRIBUTE expression exp with none or more than one name is only allowed in this(exp, name)"); |
| } |
| mName = names.get(0); |
| } |
| if (type == MSVExpressionType.VALUE) { |
| mName = ((ValueExp) exp).value.toString(); |
| } |
| if (type == MSVExpressionType.DATA) { |
| mName = ((DataExp) exp).getName().localName; |
| } |
| } |
| |
| /** |
| * Uses the name and the wrapped MSV Expression to test for equality. |
| * |
| * @param b Another object |
| * @return Whether both objects equal |
| */ |
| public boolean equals(Object b) { |
| if (b instanceof PuzzlePiece) { |
| PuzzlePiece d = (PuzzlePiece) b; |
| if (d.mName.equals(mName) && d.mExpression.equals(mExpression)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Uses the wrapped MSV Expression for the hashCode. MSV Expressions are numbered consecutively by a distinct Hash Code. |
| */ |
| public int hashCode() { |
| return mExpression.hashCode(); |
| } |
| |
| /** |
| * Uses the ns:local name of the wrapped MSV Expression as first key and the hashCode as second key. |
| * If o1.equals(o2) this method will return 0 (since both must share the same Expression and name). |
| * |
| * @param o Other Object |
| * @return Comparison |
| */ |
| public int compareTo(PuzzlePiece o) { |
| int retval = mName.compareTo(o.mName); |
| if (retval != 0) { |
| return retval; |
| } |
| return hashCode() - o.hashCode(); |
| } |
| |
| /* |
| * Return whether the content of this and of another PuzzlePiece are equal. |
| * |
| * Content is everything that's inside a RelaxNG definition block. So child |
| * elements, attributes, data types, name, definition type are all content. |
| * Parent elements or multiples are no content information as they're |
| * outside the RelaxNG definition block. |
| * |
| * This method is useful for reduction of Multiples with identical |
| * definition content. |
| * |
| * No recursive checking: E.g. the child element DefinitionSets have to be really |
| * equal to let this.contentEquals(other) be true. So PuzzlePiece reduction |
| * has to be bottom up (VALUES, DATATYPES -> ATTRIBUTES -> ELEMENTS) and reduction |
| * of ELEMENT definitions is not really possible (how to go bottom up in |
| * ELEMENT tree?) |
| */ |
| protected boolean contentEquals(PuzzlePiece other) { |
| if (!mName.equals(other.mName)) { |
| return false; |
| } |
| if (!getType().equals(other.getType())) { |
| return false; |
| } |
| if (mCanHaveText != other.mCanHaveText) { |
| return false; |
| } |
| if (!mChildElements.equals(other.mChildElements)) { |
| return false; |
| } |
| if (!mAttributes.equals(other.mAttributes)) { |
| return false; |
| } |
| if (!mMandatoryChildElementNames.equals(other.mMandatoryChildElementNames)) { |
| return false; |
| } |
| if (!mMandatoryChildAttributeNames.equals(other.mMandatoryChildAttributeNames)) { |
| return false; |
| } |
| if (!mValues.equals(other.mValues)) { |
| return false; |
| } |
| if (!mDatatypes.equals(other.mDatatypes)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Gets the ns:local tag name of this PuzzlePiece |
| * |
| * @return The tag name |
| */ |
| public String getQName() { |
| return mName; |
| } |
| |
| public String getLocalName() { |
| return XMLModel.extractLocalname(mName); |
| } |
| |
| public String getNamespace() { |
| return XMLModel.extractNamespace(mName); |
| } |
| |
| /** |
| * <p>Returns String representation (convenient method for getQName())</p> |
| * |
| * <p>Template Usage: Just use $aDefinition as you would use a string variable.</p> |
| */ |
| public String toString() { |
| return getQName(); |
| } |
| |
| /** |
| * Gets the type of this (ELEMENT, ATTRIBUTE, DATA, VALUE) |
| * |
| * @return The ExpressionType of this PuzzlePiece |
| */ |
| public MSVExpressionType getType() { |
| return (MSVExpressionType) mExpression.visit(TYPE_VISITOR); |
| } |
| |
| /** |
| * Gets the wrapped Expression |
| * |
| * @return The Expression wrapped by this. |
| */ |
| public Expression getExpression() { |
| return mExpression; |
| } |
| |
| /** |
| * Determines whether this Element can have a text node as child |
| */ |
| public boolean canHaveText() { |
| return mCanHaveText; |
| } |
| |
| public boolean isSingleton(PuzzleComponent child) { |
| for (PuzzlePiece element : child.getCollection()) { |
| if (mMultipleChildExpressions.contains(element.getExpression())) { |
| return false; |
| } |
| if (!mSingletonChildExpressions.contains(element.getExpression())) { |
| throw new RuntimeException("Definition.isSingleton: Unknown child element."); |
| } |
| } |
| return true; |
| } |
| |
| public Collection<PuzzlePiece> getCollection() { |
| return Collections.singletonList(this); |
| } |
| |
| /** |
| * Gets the List of Definitions which share the same tag name, but are defined multiple times in the schema. |
| * The list is type specific, i.e. an ATTRIBUTE can never be a multiple of an ELEMENT. |
| * |
| * @return The list of Definitions which share the same tag name. |
| */ |
| public PuzzlePieceSet withMultiples() { |
| return mMultiples; |
| } |
| |
| /** |
| * Gets the index of 'this' in the List of Definitions returned by withMultiples() |
| * |
| * @return Index of this PuzzlePiece object in the PuzzlePieceSet returned by withMultiples() |
| */ |
| public int getMultipleNumber() { |
| int retval = 0; |
| Iterator<PuzzlePiece> iter = mMultiples.iterator(); |
| while (iter.hasNext()) { |
| if (iter.next().equals(this)) { |
| return retval; |
| } |
| retval++; |
| } |
| throw new RuntimeException("aDefinition.getMultipleNumber: Internal error"); |
| } |
| |
| /** |
| * Gets the Parents which can contain this PuzzlePiece as a child |
| * |
| * @return The parent Definitions |
| */ |
| public PuzzlePieceSet getParents() { |
| return mParents; |
| } |
| |
| /** |
| * Gets the child elements of this PuzzlePiece. Please note that only Definitions of type ELEMENT can have child elements. |
| * |
| * @return The child Definitions of type ELEMENT |
| */ |
| public PuzzlePieceSet getChildElements() { |
| return mChildElements; |
| } |
| |
| public boolean isMandatory(QNamedPuzzleComponent child) { |
| switch (child.getType()) { |
| case ATTRIBUTE: |
| if (mMandatoryChildAttributeNames.contains(child.getQName())) { |
| return true; |
| } |
| break; |
| case ELEMENT: |
| if (mMandatoryChildElementNames.contains(child.getQName())) { |
| return true; |
| } |
| break; |
| } |
| return false; |
| } |
| |
| /** |
| * Gets the Attributes of this PuzzlePiece. Please note that only Definitions of type ELEMENT can have attributes. |
| * |
| * @return The child Definitions of type ATTRIBUTE |
| */ |
| public PuzzlePieceSet getAttributes() { |
| return mAttributes; |
| } |
| |
| /** |
| * Gets the defined constant values. Please note that only Definitions of type ATTRIBUTE can have values. |
| * |
| * @return The constant values |
| */ |
| public PuzzlePieceSet getValues() { |
| return mValues; |
| } |
| |
| /** |
| * Gets the defined datatypes. Please note that only Definitions of type ATTRIBUTE can have datatypes. |
| * |
| * @return The datatypes |
| */ |
| public PuzzlePieceSet getDatatypes() { |
| return mDatatypes; |
| } |
| |
| |
| /* |
| * --------------------------------------------------------------------------- |
| * PuzzlePiece Factory |
| * --------------------------------------------------------------------------- |
| */ |
| /** |
| * Creates all PuzzlePiece objects from MSV root tree. |
| * |
| * The PuzzlePiece objects are all made immutable to protect them |
| * against changes by naive template usage. Note that the Sets of all elements/attributes |
| * can only be made immutable by the caller after this method run. |
| * |
| * @param root MSV root Expression |
| * @param newElementSet empty Set. Will be filled with Definitions of Type.ELEMENT |
| * @param newAttributeSet empty Set. Will be filled with Definitions of Type.ATTRIBUTE |
| */ |
| public static void extractPuzzlePieces(Expression root, PuzzlePieceSet newElementSet, PuzzlePieceSet newAttributeSet) { |
| // e.g. the newElementSet is the set to iterate later in the template |
| extractTypedPuzzlePieces(root, newElementSet, ElementExp.class); |
| extractTypedPuzzlePieces(root, newAttributeSet, AttributeExp.class); |
| configureProperties(newElementSet, newAttributeSet); |
| reduceDatatypes(newAttributeSet); |
| reduceValues(newAttributeSet); |
| reduceAttributes(newElementSet, newAttributeSet); |
| makePuzzlePiecesImmutable(newElementSet); |
| makePuzzlePiecesImmutable(newAttributeSet); |
| } |
| |
| // Extracts all Definitions of Type [ATTRIBUTE, ELEMENT] from MSV tree. |
| private static <T extends Expression> void extractTypedPuzzlePieces(Expression root, PuzzlePieceSet setToBeFilled, Class<T> superclass) { |
| MSVExpressionIterator iter = new MSVExpressionIterator(root, superclass); |
| HashMap<String, List<PuzzlePiece>> multipleMap = new HashMap<String, List<PuzzlePiece>>(); |
| |
| while (iter.hasNext()) { |
| Expression exp = iter.next(); |
| |
| // If there is more than one name for this expression, create more than one PuzzlePiece |
| List<String> names = (List<String>) ((NameClassAndExpression) exp).getNameClass().visit(NAME_VISITOR); |
| |
| for (String name : names) { |
| if (name.length() == 0) { |
| throw new RuntimeException("Unnamed ELEMENT or ATTRIBUTE expression."); |
| } |
| // Create and store new definition |
| PuzzlePiece newDefinition = new PuzzlePiece(exp, name); |
| |
| setToBeFilled.add(newDefinition); |
| |
| // Check for multiples |
| List<PuzzlePiece> multiples = multipleMap.get(name); |
| if (multiples != null) { |
| multiples.add(newDefinition); |
| } else { |
| multiples = new ArrayList<PuzzlePiece>(1); |
| multiples.add(newDefinition); |
| multipleMap.put(name, multiples); |
| } |
| } |
| |
| } |
| |
| // Fills multiple information |
| Iterator<PuzzlePiece> defIter = setToBeFilled.iterator(); |
| while (defIter.hasNext()) { |
| PuzzlePiece def = defIter.next(); |
| def.mMultiples = new PuzzlePieceSet(multipleMap.get(def.getQName())); |
| } |
| } |
| |
| // Builds Map Expression->List<PuzzlePiece> |
| private static Map<Expression, List<PuzzlePiece>> buildReverseMap(PuzzlePieceSet defs) { |
| Map<Expression, List<PuzzlePiece>> retval = new HashMap<Expression, List<PuzzlePiece>>(); |
| Iterator<PuzzlePiece> iter = defs.iterator(); |
| while (iter.hasNext()) { |
| PuzzlePiece def = iter.next(); |
| List<PuzzlePiece> list = retval.get(def.getExpression()); |
| if (list == null) { |
| list = new ArrayList<PuzzlePiece>(); |
| retval.put(def.getExpression(), list); |
| } |
| list.add(def); |
| } |
| return retval; |
| } |
| |
| // Builds Map Name->List<Expression> |
| private static Map<String, List<Expression>> buildNameExpressionsMap(PuzzlePieceSet defs) { |
| Map<String, List<Expression>> retval = new HashMap<String, List<Expression>>(); |
| Iterator<PuzzlePiece> iter = defs.iterator(); |
| while (iter.hasNext()) { |
| PuzzlePiece def = iter.next(); |
| List<Expression> list = retval.get(def.getQName()); |
| if (list == null) { |
| list = new ArrayList<Expression>(); |
| retval.put(def.getQName(), list); |
| } |
| list.add(def.getExpression()); |
| } |
| return retval; |
| } |
| |
| // Unite Value Definitions with equal content. Has to be last step (after Value Definitions have been assigned to Attributes) |
| private static void reduceValues(PuzzlePieceSet attributes) { |
| PuzzlePieceSet values = new PuzzlePieceSet(); |
| for (PuzzlePiece attr : attributes) { |
| values.addAll(attr.getValues()); |
| } |
| Map<PuzzlePiece, PuzzlePiece> lostToSurvived = values.uniteDefinitionsWithEqualContent(); |
| for (PuzzlePiece attr : attributes) { |
| PuzzlePieceSet attributeValues = attr.getValues(); |
| PuzzlePieceSet immutable = new PuzzlePieceSet(attributeValues); |
| for (PuzzlePiece value : immutable) { |
| if (lostToSurvived.containsKey(value)) { |
| // Replace lost with survived |
| attributeValues.remove(value); |
| attributeValues.add(lostToSurvived.get(value)); |
| } |
| } |
| } |
| } |
| |
| // Unite Datatype Definitions with equal content. Has to be last step (after Datatype Definitions have been assigned to Attributes). |
| // Has to be even after reduceValues and reduceDatatypes, otherwise some attributes do not seem to have equal content |
| private static void reduceDatatypes(PuzzlePieceSet attributes) { |
| PuzzlePieceSet datatypes = new PuzzlePieceSet(); |
| for (PuzzlePiece attr : attributes) { |
| datatypes.addAll(attr.getDatatypes()); |
| } |
| Map<PuzzlePiece, PuzzlePiece> lostToSurvived = datatypes.uniteDefinitionsWithEqualContent(); |
| for (PuzzlePiece attr : attributes) { |
| PuzzlePieceSet attributeDatatypes = attr.getValues(); |
| PuzzlePieceSet immutable = new PuzzlePieceSet(attributeDatatypes); |
| for (PuzzlePiece datatype : immutable) { |
| if (lostToSurvived.containsKey(datatype)) { |
| // Replace lost with survived |
| attributeDatatypes.remove(datatype); |
| attributeDatatypes.add(lostToSurvived.get(datatype)); |
| } |
| } |
| } |
| } |
| |
| // Unite Attribute Definitions with equal content. Has to be last step (after Attribute Definitions have been assigned to Elements) |
| private static void reduceAttributes(PuzzlePieceSet elements, PuzzlePieceSet attributes) { |
| Map<PuzzlePiece, PuzzlePiece> lostToSurvived = attributes.uniteDefinitionsWithEqualContent(); |
| for (PuzzlePiece el : elements) { |
| PuzzlePieceSet elementAttributes = el.getAttributes(); |
| PuzzlePieceSet immutable = new PuzzlePieceSet(elementAttributes); |
| for (PuzzlePiece attribute : immutable) { |
| if (lostToSurvived.containsKey(attribute)) { |
| // Replace lost with survived |
| elementAttributes.remove(attribute); |
| elementAttributes.add(lostToSurvived.get(attribute)); |
| } |
| } |
| } |
| } |
| |
| // Sets Children, Attributes and Parents. |
| private static void configureProperties(PuzzlePieceSet elements, PuzzlePieceSet attributes) { |
| Map<Expression, List<PuzzlePiece>> reverseElementMap = buildReverseMap(elements); |
| Map<Expression, List<PuzzlePiece>> reverseAttributeMap = buildReverseMap(attributes); |
| |
| // Handle Element Definitions |
| Iterator<PuzzlePiece> iter = elements.iterator(); |
| while (iter.hasNext()) { |
| PuzzlePiece def = iter.next(); |
| MSVExpressionIterator childFinder = new MSVExpressionIterator(def.getExpression(), NameClassAndExpression.class, MSVExpressionIterator.DIRECT_CHILDREN_ONLY); |
| while (childFinder.hasNext()) { |
| Expression child_exp = childFinder.next(); |
| //2DO: IS CHILDEXPR BEREITS VORGEKOMMEN |
| // OR UNIQUE NEXT |
| List<PuzzlePiece> child_defs = null; |
| PuzzlePieceSet whereToAdd = null; |
| if (child_exp instanceof ElementExp) { |
| child_defs = reverseElementMap.get(child_exp); |
| whereToAdd = def.mChildElements; |
| } else if (child_exp instanceof AttributeExp) { |
| child_defs = reverseAttributeMap.get(child_exp); |
| whereToAdd = def.mAttributes; |
| } |
| if (child_defs != null) { |
| whereToAdd.addAll(child_defs); |
| for (PuzzlePiece child_def : child_defs) { |
| child_def.mParents.add(def); |
| } |
| } |
| } |
| MSVExpressionInformation elementInfo = new MSVExpressionInformation(def.getExpression()); |
| def.mCanHaveText = elementInfo.canHaveText(); |
| |
| Map<String, List<Expression>> atnameToDefs = buildNameExpressionsMap(def.mAttributes); |
| for (String name : atnameToDefs.keySet()) { |
| if (elementInfo.isMandatory(atnameToDefs.get(name))) { |
| def.mMandatoryChildAttributeNames.add(name); |
| } |
| } |
| |
| Map<String, List<Expression>> elnameToDefs = buildNameExpressionsMap(def.mChildElements); |
| for (String name : elnameToDefs.keySet()) { |
| if (elementInfo.isMandatory(elnameToDefs.get(name))) { |
| def.mMandatoryChildElementNames.add(name); |
| } |
| } |
| |
| def.mSingletonChildExpressions = elementInfo.getSingletons(); |
| def.mMultipleChildExpressions = elementInfo.getMultiples(); |
| |
| } |
| |
| // Handle Attribute Definitions |
| Iterator<PuzzlePiece> aiter = attributes.iterator(); |
| while (aiter.hasNext()) { |
| PuzzlePiece def = aiter.next(); |
| |
| MSVExpressionIterator datatypeFinder = new MSVExpressionIterator(def.getExpression(), DataExp.class, MSVExpressionIterator.DIRECT_CHILDREN_ONLY); |
| while (datatypeFinder.hasNext()) { |
| DataExp data_exp = (DataExp) datatypeFinder.next(); |
| def.mDatatypes.add(new PuzzlePiece(data_exp)); |
| } |
| |
| MSVExpressionIterator valueFinder = new MSVExpressionIterator(def.getExpression(), ValueExp.class, MSVExpressionIterator.DIRECT_CHILDREN_ONLY); |
| while (valueFinder.hasNext()) { |
| ValueExp value_exp = (ValueExp) valueFinder.next(); |
| if (value_exp.getName().localName.equals("token")) { |
| def.mValues.add(new PuzzlePiece(value_exp)); |
| } |
| } |
| |
| } |
| |
| } |
| |
| // Makes all Definitions unmodifiable |
| private static void makePuzzlePiecesImmutable(PuzzlePieceSet defs) { |
| Iterator<PuzzlePiece> iter = defs.iterator(); |
| while (iter.hasNext()) { |
| PuzzlePiece def = iter.next(); |
| def.mAttributes.makeImmutable(); |
| def.mChildElements.makeImmutable(); |
| def.mMultiples.makeImmutable(); |
| def.mParents.makeImmutable(); |
| def.mValues.makeImmutable(); |
| def.mDatatypes.makeImmutable(); |
| } |
| defs.makeImmutable(); |
| } |
| } |