/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.schema.model.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.namespace.QName;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.Definition;
import eu.esdihumboldt.hale.common.schema.model.DefinitionGroup;
import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil;
import eu.esdihumboldt.hale.common.schema.model.TypeConstraint;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
/**
* Default {@link TypeDefinition} implementation.
*
* @author Simon Templer
*/
public class DefaultTypeDefinition extends AbstractDefinition<TypeConstraint> implements
TypeDefinition {
/**
* The definition of the super type
*/
private DefaultTypeDefinition superType;
/**
* The sub-types
*/
private SortedSet<DefaultTypeDefinition> subTypes;
/**
* The declared children
*/
private final DefinitionGroup declaredChildren;
/**
* The list of inherited children, names mapped to child definitions
*/
private LinkedHashMap<QName, ChildDefinition<?>> inheritedChildren;
/**
* The map of children that are overridden for some reason.
*/
private Map<QName, ChildDefinition<?>> overriddenChildren;
private final Function<ChildDefinition<?>, ChildDefinition<?>> overriddenChildrenTransformer = new Function<ChildDefinition<?>, ChildDefinition<?>>() {
@Override
public ChildDefinition<?> apply(ChildDefinition<?> input) {
if (overriddenChildren != null) {
ChildDefinition<?> overridden = overriddenChildren.get(input.getName());
if (overridden != null) {
return overridden;
}
}
return input;
}
};
/**
* Create a type definition with the given name
*
* @param name the type name
*/
public DefaultTypeDefinition(QName name) {
this(name, true);
}
/**
* Create a type definition with the given name
*
* @param name the type name
* @param flattenAllowed if flattening of declared groups is allowed
*/
public DefaultTypeDefinition(QName name, boolean flattenAllowed) {
super(name);
declaredChildren = new DefaultGroup(getIdentifier() + "/declaredChildren", flattenAllowed);
}
/**
* @see Definition#getIdentifier()
*/
@Override
public String getIdentifier() {
return name.getNamespaceURI() + "/" + name.getLocalPart();
}
/**
* @see TypeDefinition#getDeclaredChildren()
*/
@Override
public Collection<? extends ChildDefinition<?>> getDeclaredChildren() {
if (overriddenChildren == null || overriddenChildren.isEmpty()) {
return declaredChildren.getDeclaredChildren();
}
else {
return Collections2.transform(declaredChildren.getDeclaredChildren(),
overriddenChildrenTransformer);
}
}
/**
* @see DefinitionGroup#addChild(ChildDefinition)
*/
@Override
public void addChild(ChildDefinition<?> child) {
declaredChildren.addChild(child);
}
/**
* Get the unmodifiable map of inherited children.
*
* @return the inherited children, names mapped to definitions
*/
protected Map<QName, ChildDefinition<?>> getInheritedChildren() {
initInheritedChildren();
if (overriddenChildren == null || overriddenChildren.isEmpty()) {
return Collections.unmodifiableMap(inheritedChildren);
}
else {
Map<QName, ChildDefinition<?>> result = new LinkedHashMap<>();
for (Entry<QName, ChildDefinition<?>> entry : inheritedChildren.entrySet()) {
QName name = entry.getKey();
ChildDefinition<?> child = overriddenChildren.get(name);
if (child == null) {
child = entry.getValue();
}
result.put(name, child);
}
return result;
}
}
/**
* {@inheritDoc}<br>
* May not be called while creating the model.
*
* @see TypeDefinition#getChildren()
*/
@Override
public Collection<? extends ChildDefinition<?>> getChildren() {
Collection<ChildDefinition<?>> children = new ArrayList<ChildDefinition<?>>();
initInheritedChildren();
// add inherited children
if (overriddenChildren == null || overriddenChildren.isEmpty()) {
children.addAll(inheritedChildren.values());
}
else {
children.addAll(Collections2.transform(inheritedChildren.values(),
overriddenChildrenTransformer));
}
// add declared children afterwards - correct order for output
children.addAll(getDeclaredChildren());
return children;
}
/**
* Initialize the inherited children.<br>
* May not be called while creating the model.
*/
private void initInheritedChildren() {
synchronized (this) {
if (inheritedChildren == null) {
inheritedChildren = new LinkedHashMap<QName, ChildDefinition<?>>();
// populate inherited attributes
DefaultTypeDefinition parent = getSuperType();
LinkedList<DefaultTypeDefinition> parents = new LinkedList<DefaultTypeDefinition>();
while (parent != null) {
parents.add(parent);
parent = parent.getSuperType();
}
// add children starting from the topmost supertype
Iterator<DefaultTypeDefinition> it = parents.descendingIterator();
while (it.hasNext()) {
parent = it.next();
for (ChildDefinition<?> parentChild : parent.getDeclaredChildren()) {
// create reparented copy
ChildDefinition<?> reparent = DefinitionUtil.reparentChild(parentChild,
this);
inheritedChildren.put(reparent.getName(), reparent);
}
}
}
}
}
/**
* @see AbstractDefinition#getInheritedConstraint(Class)
*/
@Override
protected <T extends TypeConstraint> T getInheritedConstraint(Class<T> constraintType) {
TypeDefinition superType = getSuperType();
if (superType != null) {
// get the super type constraint (this also may be an inherited
// constraint from its super types)
T superConstraint = superType.getConstraint(constraintType);
if (superConstraint.isInheritable()) {
// if inheritance is allowed for the constraint, return it
return superConstraint;
}
}
return null;
}
/**
* @see TypeDefinition#getSuperType()
*/
@Override
public DefaultTypeDefinition getSuperType() {
return superType;
}
/**
* Set the type's super type. This will add this type to the super type's
* sub-type list and remove it from the previous super type (if any).
*
* @param superType the super-type to set
*/
public void setSuperType(DefaultTypeDefinition superType) {
if (this.superType != null) {
this.superType.removeSubType(this);
}
this.superType = superType;
superType.addSubType(this);
}
/**
* Remove a sub-type
*
* @param subtype the sub-type to remove
* @see #setSuperType(DefaultTypeDefinition)
*/
protected void removeSubType(DefaultTypeDefinition subtype) {
if (subTypes != null) {
subTypes.remove(subtype);
}
}
/**
* Add a sub-type
*
* @param subtype the sub-type to add
* @see #setSuperType(DefaultTypeDefinition)
*/
protected void addSubType(DefaultTypeDefinition subtype) {
if (subTypes == null) {
subTypes = new TreeSet<DefaultTypeDefinition>();
}
subTypes.add(subtype);
}
/**
* @see TypeDefinition#getSubTypes()
*/
@Override
public Collection<? extends DefaultTypeDefinition> getSubTypes() {
if (subTypes == null) {
return Collections.emptyList();
}
else {
return Collections.unmodifiableCollection(subTypes);
}
}
/**
* {@inheritDoc}<br>
* May not be called while creating the model.
*
* @see TypeDefinition#getChild(QName)
*/
@Override
public ChildDefinition<?> getChild(QName name) {
ChildDefinition<?> result = null;
if (overriddenChildren != null) {
result = overriddenChildren.get(name);
}
if (result == null) {
result = declaredChildren.getChild(name);
}
if (result == null) {
initInheritedChildren();
result = inheritedChildren.get(name);
}
return result;
}
/**
* @see AbstractDefinition#toString()
*/
@Override
public String toString() {
return "[type] " + super.toString();
}
/**
* Override the child with the same name.
*
* @param child the child to replace the original child
*/
public void overrideChild(ChildDefinition<?> child) {
if (overriddenChildren == null) {
overriddenChildren = new HashMap<>();
}
overriddenChildren.put(child.getName(), child);
}
}