/*
* 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.ui.geometry.service.impl;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.xml.namespace.QName;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.DefinitionGroup;
import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil;
import eu.esdihumboldt.hale.common.schema.model.GroupPropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.ChoiceFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.GeometryType;
import eu.esdihumboldt.hale.ui.geometry.service.GeometrySchemaService;
import eu.esdihumboldt.hale.ui.geometry.service.GeometrySchemaServiceListener;
import eu.esdihumboldt.util.Pair;
/**
* Abstract geometry schema service implementation.
*
* @author Simon Templer
*/
public abstract class AbstractGeometrySchemaService implements GeometrySchemaService {
private final CopyOnWriteArraySet<GeometrySchemaServiceListener> listeners = new CopyOnWriteArraySet<GeometrySchemaServiceListener>();
/**
* @see GeometrySchemaService#getDefaultGeometry(TypeDefinition)
*/
@Override
public List<QName> getDefaultGeometry(TypeDefinition type) {
List<QName> path = loadDefaultGeometry(type);
if (path == null) {
path = determineDefaultGeometry(type);
saveDefaultGeometry(type, path);
}
return path;
}
/**
* @see GeometrySchemaService#setDefaultGeometry(TypeDefinition, List)
*/
@Override
public void setDefaultGeometry(TypeDefinition type, List<QName> path) {
saveDefaultGeometry(type, path);
notifyDefaultGeometryChanged(type);
}
/**
* Determine the path to a geometry property to be used as default geometry
* for the given type. By default the first geometry property found with a
* breadth first search is used.
*
* @param type the type definition
* @return the path to the default geometry property or <code>null</code> if
* unknown
*/
protected List<QName> determineDefaultGeometry(TypeDefinition type) {
// breadth first search for geometry properties
Queue<Pair<List<QName>, DefinitionGroup>> groups = new LinkedList<Pair<List<QName>, DefinitionGroup>>();
groups.add(new Pair<List<QName>, DefinitionGroup>(new ArrayList<QName>(), type));
List<QName> firstGeometryPath = null;
while (firstGeometryPath == null && !groups.isEmpty()) {
// for each parent group...
Pair<List<QName>, DefinitionGroup> group = groups.poll();
DefinitionGroup parent = group.getSecond();
List<QName> parentPath = group.getFirst();
// max depth for default geometries
if (parentPath.size() > 5)
continue;
// check properties if they are geometry properties, add groups to
// queue
for (ChildDefinition<?> child : DefinitionUtil.getAllChildren(parent)) {
// path for child
List<QName> path = new ArrayList<QName>(parentPath);
path.add(child.getName());
if (child.asProperty() != null) {
PropertyDefinition property = child.asProperty();
TypeDefinition propertyType = property.getPropertyType();
// check if we found a geometry property
if (propertyType.getConstraint(GeometryType.class).isGeometry()) {
// match
firstGeometryPath = path;
}
else {
// test children later on
groups.add(new Pair<List<QName>, DefinitionGroup>(path, propertyType));
}
}
else if (child.asGroup() != null) {
// test group later on
GroupPropertyDefinition childGroup = child.asGroup();
groups.add(new Pair<List<QName>, DefinitionGroup>(path, childGroup));
}
else {
throw new IllegalStateException("Invalid child definition encountered");
}
}
}
if (firstGeometryPath != null) {
// a geometry property was found
return generalizeGeometryProperty(type, firstGeometryPath);
}
else {
return null;
}
}
/**
* Generalize the path to the geometry property for the given type. This
* serves to prevent focusing on a single geometry property in a choice.
*
* @param type the type definition
* @param geometryPath the geometry path
* @return the generalized geometry path
*/
private List<QName> generalizeGeometryProperty(TypeDefinition type, List<QName> geometryPath) {
// collect child definitions associated to path names
List<ChildDefinition<?>> pathChildren = new ArrayList<ChildDefinition<?>>();
DefinitionGroup parent = type;
for (QName name : geometryPath) {
ChildDefinition<?> child = parent.getChild(name);
if (child == null) {
// invalid path
break;
}
pathChildren.add(child);
if (child.asProperty() != null) {
parent = child.asProperty().getPropertyType();
}
else if (child.asGroup() != null) {
parent = child.asGroup();
}
else {
throw new IllegalStateException("Invalid child definition");
}
}
// traverse the list in reverse order, peeking at the previous item
// remove geometry properties parented by a choice
for (int i = pathChildren.size() - 1; i > 0; i--) {
// peek at the previous item
ChildDefinition<?> previous = pathChildren.get(i - 1);
if (previous.asGroup() != null
&& previous.asGroup().getConstraint(ChoiceFlag.class).isEnabled()) {
// previous item is a choice:
// delete the current item
pathChildren.remove(i);
// and continue
}
else {
// don't continue if the parent is not a choice
// XXX should it be reduced further if there are more choices
// along the path?
// XXX then we could use another approach
// XXX namely finding the first choice in the path and removing
// everything after it
break;
}
}
// create a name list from the child list
List<QName> names = new ArrayList<QName>(pathChildren.size());
for (ChildDefinition<?> child : pathChildren) {
names.add(child.getName());
}
return names;
}
/**
* Load the path of the default geometry for the given type.
*
* @param type the type definition
* @return the path to the default geometry property or <code>null</code> if
* unknown
*/
protected abstract List<QName> loadDefaultGeometry(TypeDefinition type);
/**
* Save the association of the given property path as the default geometry
* of the given type.
*
* @param type the type definition
* @param path the property path
*/
protected abstract void saveDefaultGeometry(TypeDefinition type, List<QName> path);
/**
* Notifies the listeners that the default geometry for the given type has
* changed.
*
* @param type the type definition
*/
protected void notifyDefaultGeometryChanged(TypeDefinition type) {
for (GeometrySchemaServiceListener listener : listeners) {
listener.defaultGeometryChanged(type);
}
}
/**
* @see GeometrySchemaService#addListener(GeometrySchemaServiceListener)
*/
@Override
public void addListener(GeometrySchemaServiceListener listener) {
listeners.add(listener);
}
/**
* @see GeometrySchemaService#removeListener(GeometrySchemaServiceListener)
*/
@Override
public void removeListener(GeometrySchemaServiceListener listener) {
listeners.remove(listener);
}
}