/*
* 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.align.model.transformation.tree.visitor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.Condition;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.CellNode;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.SourceNode;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationNodeVisitor;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.TransformationTree;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.context.TransformationContext;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.impl.LeftoversImpl;
import eu.esdihumboldt.hale.common.align.model.transformation.tree.impl.SourceNodeImpl;
import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog;
import eu.esdihumboldt.hale.common.instance.model.FamilyInstance;
import eu.esdihumboldt.hale.common.instance.model.Filter;
import eu.esdihumboldt.hale.common.instance.model.Group;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.common.schema.model.Definition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
/**
* Visitor that annotates a transformation tree with the values of properties in
* a source instance.
*
* @author Simon Templer
*/
public class InstanceVisitor extends AbstractSourceToTargetVisitor {
private final FamilyInstance instance;
private final TransformationTree tree;
private final TransformationLog log;
/**
* Creates an instance visitor.
*
* @param instance the instance, may be null
* @param tree the transformation tree, may be null if instance is null
* @param log the transformation log
*/
public InstanceVisitor(FamilyInstance instance, TransformationTree tree,
TransformationLog log) {
super();
this.instance = instance;
this.tree = tree;
this.log = log;
// TODO support multiple instances with a instance per type basis or
// even duplication of type source nodes?
}
/**
* @see AbstractSourceToTargetVisitor#visit(CellNode)
*/
@Override
public boolean visit(CellNode cell) {
return false;
}
/**
* @see AbstractSourceToTargetVisitor#visit(SourceNode)
*/
@Override
public boolean visit(SourceNode source) {
if (source.getDefinition() instanceof TypeDefinition) {
if (instance == null)
return false;
// source root
if (source.getDefinition().equals(instance.getDefinition())) {
// check type filter (if any)
Filter filter = source.getEntityDefinition().getFilter();
if (filter != null && !filter.match(instance)) {
// instance does not match filter, don't descend further
return false;
/*
* XXX What about merged instances? Will this be OK for
* those? A type filter should only apply to the original
* instance if it is merged - but most filters should
* evaluate the same
*/
}
else {
source.setValue(instance); // also sets the node to defined
for (FamilyInstance child : instance.getChildren()) {
// Find fitting SourceNodes.
Collection<SourceNode> candidateNodes = tree
.getRootSourceNodes(child.getDefinition());
if (candidateNodes.isEmpty()) {
/*
* No node found - but this may be because no
* property of the type is mapped, but there still
* might be child instances (in a Join) that have
* types with associated relations. To prevent those
* being skipped we add an artificial node
* representing the instance.
*/
candidateNodes = new ArrayList<>();
EntityDefinition entityDef = new TypeEntityDefinition(
child.getDefinition(), SchemaSpaceID.SOURCE, null);
candidateNodes.add(new SourceNodeImpl(entityDef, null, false));
}
for (SourceNode candidateNode : candidateNodes) {
filter = candidateNode.getEntityDefinition().getFilter();
if (filter == null || filter.match(child)) {
// XXX add to all candidates!?
if (candidateNode.getValue() == null) {
candidateNode.setAnnotatedParent(source);
source.addAnnotatedChild(candidateNode);
}
else {
// Duplicate here, because there is no
// guarantee, that the Duplication
// Visitor will visit candidateNode after
// this node.
SourceNodeImpl duplicateNode = new SourceNodeImpl(
candidateNode.getEntityDefinition(),
candidateNode.getParent(), false);
duplicateNode.setAnnotatedParent(source);
source.addAnnotatedChild(duplicateNode);
TransformationContext context = candidateNode.getContext();
duplicateNode.setContext(context);
if (context != null) {
context.duplicateContext(candidateNode, duplicateNode,
Collections.<Cell> emptySet(), log);
}
else {
/*
* Not sure what this really means if we
* get here.
*
* Best guess: Probably that we weren't
* able to determine how the duplication
* of this source can be propagted to
* the target. Thus the duplicated node
* will probably not have any
* connection.
*/
log.warn(log.createMessage(
"No transformation context for duplicated node of source "
+ candidateNode.getDefinition()
.getDisplayName(),
null));
}
candidateNode = duplicateNode;
}
// run instance visitor on that annotated child
InstanceVisitor visitor = new InstanceVisitor(child, tree, log);
candidateNode.accept(visitor);
}
}
}
return true;
}
}
else
return false;
}
else {
Object parentValue = source.getParent().getValue();
if (parentValue == null || !(parentValue instanceof Group)) {
source.setDefined(false);
return false;
}
else {
Group parentGroup = (Group) parentValue;
Definition<?> currentDef = source.getDefinition();
Object[] values = parentGroup.getProperty(currentDef.getName());
if (values == null) {
source.setDefined(false);
return false;
}
// check for contexts
EntityDefinition entityDef = source.getEntityDefinition();
// index context
Integer index = AlignmentUtil.getContextIndex(entityDef);
if (index != null) {
// only use the value at the given index, if present
if (index < values.length) {
// annotate with the value at the index
Object value = values[index];
source.setValue(value);
return true;
}
else {
source.setDefined(false);
return false;
}
}
// condition context
Condition condition = AlignmentUtil.getContextCondition(entityDef);
if (condition != null) {
if (condition.getFilter() == null) {
// assume exclusion
source.setDefined(false);
return false;
}
// apply condition as filter on values and continue with
// those values
Collection<Object> matchedValues = new ArrayList<Object>();
for (Object value : values) {
// determine parent
Object parent = null;
SourceNode parentNode = source.getParent();
if (parentNode != null && parentNode.isDefined()) {
parent = parentNode.getValue();
}
// test the condition
if (AlignmentUtil.matchCondition(condition, value, parent)) {
matchedValues.add(value);
}
}
values = matchedValues.toArray();
}
// (named contexts not allowed)
// default behavior (default context)
if (values.length >= 1) {
// annotate with the first value
Object value = values[0];
source.setValue(value);
source.setAllValues(values);
}
else {
source.setDefined(false);
return false;
}
if (values.length > 1) {
// handle additional values
Object[] leftovers = new Object[values.length - 1];
System.arraycopy(values, 1, leftovers, 0, leftovers.length);
source.setLeftovers(new LeftoversImpl(source, leftovers));
}
return true;
}
}
}
/**
* @see TransformationNodeVisitor#includeAnnotatedNodes()
*/
@Override
public boolean includeAnnotatedNodes() {
// annotated nodes are ignored, as these are handled when created
return false;
}
}