/*
* 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.cst.functions.core;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import net.jcip.annotations.Immutable;
import org.springframework.core.convert.ConversionException;
import com.google.common.collect.ListMultimap;
import com.vividsolutions.jts.geom.Geometry;
import eu.esdihumboldt.hale.common.align.model.functions.RenameFunction;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.transformation.engine.TransformationEngine;
import eu.esdihumboldt.hale.common.align.transformation.function.PropertyValue;
import eu.esdihumboldt.hale.common.align.transformation.function.TransformationException;
import eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractSingleTargetPropertyTransformation;
import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog;
import eu.esdihumboldt.hale.common.convert.ConversionUtil;
import eu.esdihumboldt.hale.common.core.io.Value;
import eu.esdihumboldt.hale.common.instance.model.DataSet;
import eu.esdihumboldt.hale.common.instance.model.Group;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.InstanceFactory;
import eu.esdihumboldt.hale.common.instance.model.MutableGroup;
import eu.esdihumboldt.hale.common.instance.model.MutableInstance;
import eu.esdihumboldt.hale.common.instance.model.impl.DefaultGroup;
import eu.esdihumboldt.hale.common.instance.model.impl.DefaultInstanceFactory;
import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.ElementType;
/**
* Property rename function.
*
* @author Simon Templer
*/
@Immutable
public class Rename extends AbstractSingleTargetPropertyTransformation<TransformationEngine>
implements RenameFunction {
// object symbolizing that no match for the source to the target definition
// was found (in contrast to null value)
private static enum Result {
NO_MATCH
}
@Override
protected Object evaluate(String transformationIdentifier, TransformationEngine engine,
ListMultimap<String, PropertyValue> variables, String resultName,
PropertyEntityDefinition resultProperty, Map<String, String> executionParameters,
TransformationLog log) throws TransformationException {
// get the source value
Object sourceValue = variables.values().iterator().next().getValue();
boolean structuralRenameEnabled = getOptionalParameter(PARAMETER_STRUCTURAL_RENAME,
Value.of(false)).as(Boolean.class);
boolean ignoreNamespacesEnabled = getOptionalParameter(PARAMETER_IGNORE_NAMESPACES,
Value.of(false)).as(Boolean.class);
boolean copyGeometriesEnabled = getOptionalParameter(PARAMETER_COPY_GEOMETRIES,
Value.of(true)).as(Boolean.class);
// not a group? just return value.
if (!(sourceValue instanceof Group))
return sourceValue;
else if (!structuralRenameEnabled) {
// not structural rename -> value only
if (sourceValue instanceof Instance)
return ((Instance) sourceValue).getValue();
else
return null;
}
else {
// structural rename
Object result = structuralRename(sourceValue, resultProperty.getDefinition(),
ignoreNamespacesEnabled, new DefaultInstanceFactory(), copyGeometriesEnabled);
if (result == Result.NO_MATCH)
return null; // source could neither be used for target value,
// nor any child properties
return result;
}
}
/**
* Performs a structural rename on the given source object to the given
* target definition.
*
* @param source the source value (or group/instance)
* @param targetDefinition the target definition
* @param allowIgnoreNamespaces if for the structure comparison, namespaces
* may be ignored
* @param instanceFactory the instance factory
* @param copyGeometries specifies if geometry objects should be copied
* @return the transformed value (or group/instance) or NO_MATCH
*/
public static Object structuralRename(Object source, ChildDefinition<?> targetDefinition,
boolean allowIgnoreNamespaces, InstanceFactory instanceFactory, boolean copyGeometries) {
if (!(source instanceof Group)) {
// source simple value
if (targetDefinition.asProperty() != null) {
// target can have value
TypeDefinition propertyType = targetDefinition.asProperty().getPropertyType();
if (copyGeometries || !isGeometry(source)) {
if (propertyType.getChildren().isEmpty()) {
// simple value
return convertValue(source, targetDefinition.asProperty().getPropertyType());
}
else {
// instance with value
MutableInstance instance = instanceFactory.createInstance(propertyType);
instance.setDataSet(DataSet.TRANSFORMED);
instance.setValue(convertValue(source, propertyType));
return instance;
}
}
else {
return Result.NO_MATCH;
}
}
}
// source is group or instance
if (targetDefinition.asProperty() != null) {
// target can have value
TypeDefinition propertyType = targetDefinition.asProperty().getPropertyType();
if (source instanceof Instance) {
// source has value
if (propertyType.getChildren().isEmpty()) {
// simple value
return convertValue(((Instance) source).getValue(), targetDefinition
.asProperty().getPropertyType());
}
else {
// instance with value
MutableInstance instance = instanceFactory.createInstance(targetDefinition
.asProperty().getPropertyType());
instance.setDataSet(DataSet.TRANSFORMED);
if (copyGeometries || !isGeometry(((Instance) source).getValue())) {
instance.setValue(convertValue(((Instance) source).getValue(),
targetDefinition.asProperty().getPropertyType()));
}
renameChildren((Group) source, instance, targetDefinition,
allowIgnoreNamespaces, instanceFactory, copyGeometries);
return instance;
}
}
else {
// source has no value
if (targetDefinition.asProperty().getPropertyType().getChildren().isEmpty())
return Result.NO_MATCH; // no match possible
else {
// instance with no value set
MutableInstance instance = instanceFactory.createInstance(targetDefinition
.asProperty().getPropertyType());
instance.setDataSet(DataSet.TRANSFORMED);
if (renameChildren((Group) source, instance, targetDefinition,
allowIgnoreNamespaces, instanceFactory, copyGeometries))
return instance;
else
return Result.NO_MATCH; // no child matched and no value
}
}
}
else if (targetDefinition.asGroup() != null) {
// target can not have a value
if (targetDefinition.asGroup().getDeclaredChildren().isEmpty())
return Result.NO_MATCH; // target neither has a value nor
// children?
else {
// group
MutableGroup group = new DefaultGroup(targetDefinition.asGroup());
if (renameChildren((Group) source, group, targetDefinition, allowIgnoreNamespaces,
instanceFactory, copyGeometries))
return group;
else
return Result.NO_MATCH; // no child matched and no value
}
}
else {
// neither asProperty nor asGroup -> illegal ChildDefinition
throw new IllegalStateException("Illegal child type.");
}
}
/**
* Determines if the given value is a geometry object.
*
* @param value the value
* @return <code>true</code> if the value is a geometry object or
* collection, false otherwise
*/
private static boolean isGeometry(Object value) {
if (value instanceof GeometryProperty) {
return true;
}
if (value instanceof Geometry) {
return true;
}
if (value instanceof Collection<?>) {
Collection<?> col = ((Collection<?>) value);
if (!col.isEmpty()) {
boolean other = false;
for (Object element : col) {
if (!(element instanceof GeometryProperty) && !(element instanceof Geometry)) {
other = true;
break;
}
}
if (!other) {
return true;
}
}
}
return false;
}
/**
* Tries to match any direct child of source group to the given
* targetDefinition. Matches are added to the given target group.
*
* @param source the source group
* @param target the target group
* @param targetDefinition the target definition
* @param allowIgnoreNamespaces if for the structure comparison, namespaces
* may be ignored
* @param instanceFactory the instance factory
* @param copyGeometries specifies if geometry objects should be copied
* @return true, if any property could be matched to the targetDefinition
*/
private static boolean renameChildren(Group source, MutableGroup target,
ChildDefinition<?> targetDefinition, boolean allowIgnoreNamespaces,
InstanceFactory instanceFactory, boolean copyGeometries) {
boolean matchedChild = false;
// walk over all source property names
for (QName sourcePropertyName : source.getPropertyNames()) {
// find property name in target definition
ChildDefinition<?> targetDefinitionChild = DefinitionUtil.getChild(targetDefinition,
sourcePropertyName);
if (targetDefinitionChild == null && allowIgnoreNamespaces) {
// no corresponding target found
// but we have the option to switch to another namespace
targetDefinitionChild = DefinitionUtil.getChild(targetDefinition,
sourcePropertyName, true);
}
if (targetDefinitionChild != null) {
Object[] sourceProperties = source.getProperty(sourcePropertyName);
// walk over all source property values
for (Object sourceProperty : sourceProperties) {
// try to match them
Object result = structuralRename(sourceProperty, targetDefinitionChild,
allowIgnoreNamespaces, instanceFactory, copyGeometries);
if (result != Result.NO_MATCH) {
// found match!
target.addProperty(targetDefinitionChild.getName(), result);
matchedChild = true;
}
}
}
}
return matchedChild;
}
/**
* Tries to convert the value to be compatible with targetType, returns the
* value itself if the conversion failed.
*
* @param value the value to convert
* @param targetType the target type
* @return the converted value if successful, the original value otherwise
*/
private static Object convertValue(Object value, TypeDefinition targetType) {
if (value == null)
return null;
Class<?> target = targetType.getConstraint(Binding.class).getBinding();
if (target.isAssignableFrom(value.getClass()))
return value;
if (Collection.class.isAssignableFrom(target) && target.isAssignableFrom(List.class)) {
// collection / list
ElementType elementType = targetType.getConstraint(ElementType.class);
try {
return ConversionUtil.getAsList(value, elementType.getBinding(), true);
} catch (ConversionException ce) {
return value;
}
}
try {
return ConversionUtil.getAs(value, target);
} catch (ConversionException ce) {
return value;
}
}
}