| /************************************************************************ |
| * |
| * 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 java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| |
| /** |
| * Collection Class for RelaxNG definitions of an Element, Attribute, Value or Datatype. |
| * |
| * <p>Conventions: <ul><li>PuzzlePiece sorting is done by ns:local tag names as first key and hashCode as second key (see class PuzzlePiece).</li> |
| * <li>Since it is a Collection, PuzzlePieceSet is not meant to be used in a Collection. So equals(o) and hashCode() are not overwritten</li> |
| * <li>All returned PuzzlePieceSet objects are immutable to protect them against |
| * naive usage in velocity templates</li></ul></p> |
| */ |
| public class PuzzlePieceSet implements QNamedPuzzleComponent, Collection<PuzzlePiece> { |
| |
| private boolean mImmutable = false; |
| private SortedSet<PuzzlePiece> mDefinitions; |
| |
| public PuzzlePieceSet() { |
| mDefinitions = new TreeSet<PuzzlePiece>(); |
| } |
| |
| public PuzzlePieceSet(Collection<PuzzlePiece> c) { |
| mDefinitions = new TreeSet<PuzzlePiece>(c); |
| } |
| |
| private void assertNotImmutable() { |
| if (mImmutable) { |
| throw new RuntimeException("Attempt to change an immutable DefinitionSet."); |
| } |
| } |
| |
| private void assertNotEmpty(String plannedAction) { |
| if (this.size()==0) { |
| throw new RuntimeException("Attempt to " + plannedAction + " of empty DefinitionSet "); |
| } |
| } |
| |
| private void assertMultiples(String plannedAction) { |
| assertNotEmpty(plannedAction); |
| PuzzlePiece first = first(); |
| MSVExpressionType type = first.getType(); |
| String name = first.getQName(); |
| for (PuzzlePiece def : this) { |
| if (!type.equals(def.getType())) { |
| throw new RuntimeException("Attempt to " + plannedAction + " of DefinitionSet consisting of different types of Definition objetcs."); |
| } |
| String defname = def.getQName(); |
| if ((name == null && defname != null) || !name.equals(defname)) { |
| throw new RuntimeException("Attempt to " + plannedAction + " of DefinitionSet consisting of differently named Definition objetcs."); |
| } |
| } |
| } |
| |
| public boolean equals(Object o) { |
| return (o instanceof PuzzlePieceSet && ((PuzzlePieceSet) o).mDefinitions.equals(mDefinitions)) ? true : false; |
| } |
| |
| public int hashCode() { |
| return mDefinitions.hashCode(); |
| } |
| |
| private PuzzlePiece first() { |
| return this.iterator().next(); |
| } |
| |
| /* Unite Definitions with equal content |
| * |
| * Returns a Map of the lost Definitions to their survived counterparts |
| */ |
| Map<PuzzlePiece, PuzzlePiece> uniteDefinitionsWithEqualContent() { |
| Map<PuzzlePiece, PuzzlePiece> retval = new HashMap<PuzzlePiece, PuzzlePiece>(); |
| SortedSet<PuzzlePiece> immutableSet = new TreeSet<PuzzlePiece>(this.mDefinitions); |
| for (PuzzlePiece def1 : immutableSet) { |
| if (!this.mDefinitions.contains(def1)) { |
| // if def1 is already removed, we shouldn't process it |
| continue; |
| } |
| for (PuzzlePiece def2 : immutableSet.tailSet(def1)) { |
| if (def1 == def2) { |
| // Don't compare def1 to def1 |
| continue; |
| } |
| if (!this.mDefinitions.contains(def2)) { |
| //if def2 is already removed, we shouldn't process it |
| continue; |
| } |
| if (!def1.getQName().equals(def2.getQName())) { |
| break; |
| } |
| // Now test for content equality |
| if (def1.contentEquals(def2)) { |
| // remove from map |
| this.remove(def2); |
| // map lost to survived counterpart |
| retval.put(def2, def1); |
| // mix parent information |
| def1.getParents().addAll(def2.getParents()); |
| } |
| } |
| } |
| // Remove deleted multiples in a separate step since in the first step the needed |
| // information wasn't present (note that Multiples are no synonym for Definitions with equal content!) |
| for (PuzzlePiece def : this.mDefinitions) { |
| PuzzlePieceSet immutableDups = new PuzzlePieceSet(def.withMultiples()); |
| for (PuzzlePiece multiple : immutableDups) { |
| if (multiple != def) { |
| if (!this.mDefinitions.contains(multiple)) { |
| def.withMultiples().remove(multiple); |
| } |
| } |
| } |
| } |
| return retval; |
| } |
| |
| /** |
| * Make PuzzlePieceSet immutable. Cannot be undone. |
| * |
| * Template Usage: Not for use in templates as all PuzzlePieceSet already have been made immutable. |
| */ |
| public void makeImmutable() { |
| mImmutable = true; |
| } |
| |
| /** |
| * <p>Returns new PuzzlePieceSet containing the elements of this PuzzlePieceSet, but restricted to one |
| * PuzzlePiece per Name.</p> |
| * |
| * <p>Template Usage: #foreach ($element in $elements.withoutMultiples())</p> |
| * |
| * @return new PuzzlePieceSet |
| */ |
| public PuzzlePieceSet withoutMultiples() { |
| Map<String, PuzzlePiece> uniqueMap = new HashMap<String, PuzzlePiece>(); |
| for (PuzzlePiece def : this) { |
| uniqueMap.put(def.getQName(), def); |
| } |
| return new PuzzlePieceSet(uniqueMap.values()); |
| } |
| |
| /** |
| * <p>Returns new PuzzlePieceSet containing the elements of this PuzzlePieceSet, but without |
| * the elements of the parameter removeAll</p> |
| * |
| * <p>Template Usage: #set ($non_base_attributes = $element.getAttributes().without($baseclass.getAttributes())</p> |
| * |
| * @param removeAll QNamedPuzzleComponent which (or which elements) should be removed from the new PuzzlePieceSet |
| * @return new PuzzlePieceSet |
| */ |
| public PuzzlePieceSet without(QNamedPuzzleComponent removeAll) { |
| PuzzlePieceSet retval = new PuzzlePieceSet(this); |
| retval.removeAll(removeAll.getCollection()); |
| return retval; |
| } |
| |
| /** |
| * Returns new PuzzlePieceSet containing the elements of this PuzzlePieceSet, but only those |
| * which have at least one element from the QNamedPuzzleComponent parameter as one of their parent Definitions. |
| * |
| * <p>Template Usage: Imagine we have one attribute name and we're not interested in the differences |
| * between Definitions sharing the same name. We're now printing the |
| * resulting allowed attribute values for each parent element name: </p> |
| * <code><br /> |
| * #set ( $oneOrMoreAttributes = $model.getAttribute($atttributename) )<br /> |
| * ## we want to write information about only _one_ parent per Name...<br /> |
| * #foreach ($parent in $oneOrMoreAttributes.getParents().withoutMultiples())<br/> |
| * - Allowed Values for Parent Element $parent :<br /> |
| * ## but we want the attribute values displayed which are allowed in _all_ parents with the same Name...<br /> |
| * #foreach ($value in $oneOrMoreAttributes.byParent($parent.withMultiples()).getValues())<br /> |
| * -- "$value" <br /> |
| * #end<br /> |
| * #end |
| * </code><br /> |
| * |
| * @param parents |
| * @return new PuzzlePieceSet |
| */ |
| public PuzzlePieceSet byParent(QNamedPuzzleComponent parents) { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| PuzzlePieceSet defparents = def.getParents(); |
| for (PuzzlePiece parent : parents.getCollection()) { |
| if (defparents.contains(parent)) { |
| retval.add(def); |
| break; |
| } |
| } |
| } |
| return retval; |
| } |
| |
| /** |
| * Check whether this List contains an Element by this Name |
| * |
| * @param aDefinitionName |
| * @return True if an element by this name exists |
| */ |
| public boolean containsName(String aDefinitionName) { |
| for (PuzzlePiece def : this) { |
| if (def.getQName().equals(aDefinitionName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check whether this List contains an Element by this Name |
| * |
| * @param aNamed |
| * @return True if an element by this name exists |
| */ |
| public boolean containsName(QNamed aNamed) { |
| return containsName(aNamed.getQName()); |
| } |
| |
| /* |
| * ----------------------------------------------------- |
| * Interface QNamed |
| * ----------------------------------------------------- |
| */ |
| |
| |
| /** |
| * Gets the ns:local tag name of the Definitions - provided that this PuzzlePieceSet |
| * is not empty and all Definitions share the same tag name. Throws Exception otherwise. |
| * |
| * @return The tag name |
| */ |
| public String getQName() { |
| assertMultiples("get name"); |
| return first().getQName(); |
| } |
| |
| /** |
| * Gets the type of the Definitions - provided that this PuzzlePieceSet |
| * is not empty and all Definitions have the same type and name. Throws Exception otherwise. |
| */ |
| public MSVExpressionType getType() { |
| assertMultiples("get type"); |
| return first().getType(); |
| } |
| |
| /** |
| * Determines whether the Definitions can have text - provided that this PuzzlePieceSet |
| * is not empty and all Definitions have the same type and name. Throws Exception otherwise. |
| */ |
| public boolean canHaveText() { |
| assertMultiples("determine text availability"); |
| for (PuzzlePiece def : this) { |
| if (def.canHaveText()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean isSingleton(PuzzleComponent child) { |
| for (PuzzlePiece def : this) { |
| if (!def.isSingleton(child)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public String getLocalName() { |
| return XMLModel.extractLocalname(getQName()); |
| } |
| |
| public String getNamespace() { |
| return XMLModel.extractNamespace(getQName()); |
| } |
| |
| /** |
| * <p>Returns String representation (convenient method for getQName())</p> |
| * |
| * <p>Template Usage: Just use $aDefinitionSet as you would use a string variable</p> |
| */ |
| public String toString() { |
| return getQName(); |
| } |
| |
| /* |
| * ----------------------------------------------------- |
| * Interface Collection<PuzzlePiece> |
| * ----------------------------------------------------- |
| */ |
| |
| public boolean add(PuzzlePiece e) { |
| assertNotImmutable(); |
| return mDefinitions.add(e); |
| } |
| |
| public boolean addAll(Collection<? extends PuzzlePiece> c) { |
| assertNotImmutable(); |
| return mDefinitions.addAll(c); |
| } |
| |
| public void clear() { |
| assertNotImmutable(); |
| mDefinitions.clear(); |
| } |
| |
| public boolean contains(Object o) { |
| return (o instanceof PuzzlePiece) ? mDefinitions.contains((PuzzlePiece) o) : false; |
| } |
| |
| public boolean containsAll(Collection<?> c) { |
| return mDefinitions.containsAll(c); |
| } |
| |
| public boolean isEmpty() { |
| return mDefinitions.isEmpty(); |
| } |
| |
| public Iterator<PuzzlePiece> iterator() { |
| return mDefinitions.iterator(); |
| } |
| |
| public boolean remove(Object o) { |
| assertNotImmutable(); |
| return (o instanceof PuzzlePiece) ? mDefinitions.remove((PuzzlePiece) o) : false; |
| } |
| |
| public boolean removeAll(Collection<?> c) { |
| assertNotImmutable(); |
| return mDefinitions.removeAll(c); |
| } |
| |
| public boolean retainAll(Collection<?> c) { |
| assertNotImmutable(); |
| return mDefinitions.retainAll(c); |
| } |
| |
| public int size() { |
| return mDefinitions.size(); |
| } |
| |
| public Object[] toArray() { |
| return mDefinitions.toArray(); |
| } |
| |
| public <T> T[] toArray(T[] a) { |
| return mDefinitions.toArray(a); |
| } |
| |
| /* |
| * ----------------------------------------------------- |
| * Interface QNamedPuzzleComponent |
| * ----------------------------------------------------- |
| */ |
| |
| public PuzzlePieceSet getChildElements() { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| retval.addAll(def.getChildElements()); |
| } |
| return retval; |
| } |
| |
| public boolean isMandatory(QNamedPuzzleComponent child) { |
| for (PuzzlePiece def : this) { |
| if (def.isMandatory(child)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public Collection<PuzzlePiece> getCollection() { |
| return mDefinitions; |
| } |
| |
| public PuzzlePieceSet getAttributes() { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| retval.addAll(def.getAttributes()); |
| } |
| return retval; |
| } |
| |
| public PuzzlePieceSet getDatatypes() { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| retval.addAll(def.getDatatypes()); |
| } |
| return retval; |
| } |
| |
| public PuzzlePieceSet getParents() { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| retval.addAll(def.getParents()); |
| } |
| return retval; |
| } |
| |
| |
| public PuzzlePieceSet getValues() { |
| PuzzlePieceSet retval = new PuzzlePieceSet(); |
| for (PuzzlePiece def : this) { |
| retval.addAll(def.getValues()); |
| } |
| return retval; |
| } |
| |
| } |