blob: 42d5768d681cec314bca3c9ccedd905609b36c13 [file] [log] [blame]
/************************************************************************
*
* 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;
}
}