/*
* 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.io.gml.writer.internal.geometry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.xml.namespace.QName;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.AbstractFlag;
import eu.esdihumboldt.hale.io.gml.writer.internal.GmlWriterUtil;
/**
* Abstract type matcher. Finds candidates matching a custom parameter.
*
* @param <T> the match parameter type
*
* @author Simon Templer
* @partner 01 / Fraunhofer Institute for Computer Graphics Research
*/
public abstract class AbstractTypeMatcher<T> {
/**
* Path candidate
*/
private static class PathCandidate {
private final TypeDefinition type;
private final DefinitionPath path;
private final HashSet<TypeDefinition> checkedTypes;
/**
* Constructor
*
* @param type the associated type
* @param path the definition path
* @param checkedTypes the type definitions that have already been
* checked (to prevent cycles)
*/
public PathCandidate(TypeDefinition type, DefinitionPath path,
HashSet<TypeDefinition> checkedTypes) {
this.type = type;
this.path = path;
this.checkedTypes = checkedTypes;
}
/**
* @return the attributeType
*/
public TypeDefinition getType() {
return type;
}
/**
* @return the definitionPath
*/
public DefinitionPath getPath() {
return path;
}
/**
* @return the handledTypes
*/
public HashSet<TypeDefinition> getCheckedTypes() {
return checkedTypes;
}
}
/**
* Find candidates for a possible path
*
* @param elementType the start element type
* @param elementName the start element name
* @param unique if the start element cannot be repeated
* @param matchParam the match parameter
*
* @return the path candidates
*/
public List<DefinitionPath> findCandidates(TypeDefinition elementType, QName elementName,
boolean unique, T matchParam) {
return findCandidates(elementType, elementName, unique, matchParam, Integer.MAX_VALUE);
}
/**
* Find candidates for a possible path
*
* @param elementType the start element type
* @param elementName the start element name
* @param unique if the start element cannot be repeated
* @param matchParam the match parameter
* @param maxDepth the maximum depth that is allowed for paths
*
* @return the path candidates
*/
public List<DefinitionPath> findCandidates(TypeDefinition elementType, QName elementName,
boolean unique, T matchParam, int maxDepth) {
Queue<PathCandidate> candidates = new LinkedList<PathCandidate>();
PathCandidate base = new PathCandidate(elementType, new DefinitionPath(elementType,
elementName, unique), new HashSet<TypeDefinition>());
candidates.add(base);
List<DefinitionPath> results = new ArrayList<>();
while (!candidates.isEmpty()) {
PathCandidate candidate = candidates.poll();
TypeDefinition type = candidate.getType();
DefinitionPath basePath = candidate.getPath();
HashSet<TypeDefinition> checkedTypes = candidate.getCheckedTypes();
if (basePath.getSteps().size() >= maxDepth) {
// max depth already reached, only direct match allowed
// check if there is a direct match
DefinitionPath path = matchPath(type, matchParam, basePath);
if (path != null) {
if (results.isEmpty()) {
// first result -> adjust max depth
maxDepth = path.getSteps().size();
}
results.add(path);
}
// skip sub paths
continue;
}
if (checkedTypes.contains(type)) {
continue; // prevent cycles
}
else {
checkedTypes.add(type);
}
// check if there is a direct match
DefinitionPath path = matchPath(type, matchParam, basePath);
if (path != null) {
if (results.isEmpty()) {
// first result -> adjust max depth
maxDepth = path.getSteps().size();
}
results.add(path);
// XXX currently always only one path is returned - this might
// change if we allow matchPath to yield multiple results
// skip sub paths
continue;
}
if (!type.getConstraint(AbstractFlag.class).isEnabled()) {
// only allow stepping down properties if the type is not
// abstract
// step down properties
// XXX why differentiate here?
@SuppressWarnings("unchecked")
Iterable<ChildDefinition<?>> children = (Iterable<ChildDefinition<?>>) ((basePath
.isEmpty() || basePath.getLastElement().isProperty()) ? (type.getChildren())
: (type.getDeclaredChildren()));
Iterable<DefinitionPath> childPaths = GmlWriterUtil.collectPropertyPaths(children,
basePath, true);
for (DefinitionPath childPath : childPaths) {
// only descend into elements
candidates.add(new PathCandidate(childPath.getLastType(), childPath,
new HashSet<TypeDefinition>(checkedTypes)));
}
}
// step down sub-types
// XXX done through choice
// Set<TypeDefinition> substitutionTypes = new HashSet<TypeDefinition>();
// for (SchemaElement element : type.getSubstitutions(basePath.getLastName())) {
// substitutionTypes.add(element.getType());
// candidates.add(new PathCandidate(element.getType(),
// new DefinitionPath(basePath).addSubstitution(element),
// new HashSet<TypeDefinition>(checkedTypes)));
// }
// step down sub-types - elements may be downcast using xsi:type
if (!type.getConstraint(AbstractFlag.class).isEnabled()) {
/*
* don't do it for abstract types as they have no element that
* may be used XXX is this true?
*/
for (@SuppressWarnings("unused")
TypeDefinition subtype : type.getSubTypes()) {
// FIXME how to determine which types are ok for xsi:type?!
// if (!substitutionTypes.contains(subtype)) { // only types that are no valid substitutions
// // add candidate
//// Name element = basePath.getLastName(); // the element name that will be extended with xsi:type
// candidates.add(new PathCandidate(subtype,
// new DefinitionPath(basePath).addDowncast(subtype),
// new HashSet<TypeDefinition>(checkedTypes)));
// }
}
}
}
return results;
}
/**
* Determines if a type definition is compatible with the match parameter
*
* @param type the type definition
* @param matchParam the match parameter
* @param path the current definition path
*
* @return the (eventually updated) definition path if a match is found,
* otherwise <code>null</code>
*/
protected abstract DefinitionPath matchPath(TypeDefinition type, T matchParam,
DefinitionPath path);
}