/*
* Copyright (c) 2013 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:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.align.model.functions.join;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import eu.esdihumboldt.hale.common.align.model.AlignmentUtil;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
/**
* Information for a Join transformation.
*
* @author Kai Schwierczek
*/
public class JoinParameter {
/**
* Unmodifiable ordered list of the types to join.
*/
public final List<TypeEntityDefinition> types;
/**
* Unmodifiable set of join conditions.
*/
public final Set<JoinCondition> conditions;
/**
* Constructs a new join parameter descriptor.
*
* @param types the list of join types, in order
* @param conditions the join conditions
*/
public JoinParameter(List<TypeEntityDefinition> types, Set<JoinCondition> conditions) {
this.types = Collections.unmodifiableList(new ArrayList<>(types));
this.conditions = Collections.unmodifiableSet(new HashSet<>(conditions));
}
/**
* Checks whether this join parameter is valid.<br>
* <br>
* Valid means, that there has to be at least two types, with each type
* after the first having at least one join condition on previous types.
*
* @return a error description or <code>null</code> if the parameter is
* valid.
*/
public String validate() {
// enough types?
if (types.size() < 2)
return "Less than two types.";
// Check that each type is only in here once.
Set<TypeDefinition> typeSet = new HashSet<>();
for (TypeEntityDefinition type : types)
if (typeSet.contains(type.getDefinition()))
return "Same base type is used twice.";
else
typeSet.add(type.getDefinition());
// direct parent map
int[] parent = new int[types.size()];
// marker for found conditions for each type
boolean[] conditionFound = new boolean[types.size()];
// use sorted conditions (by join type)
List<JoinCondition> sortedConditions = new ArrayList<>(conditions);
Collections.sort(sortedConditions, new Comparator<JoinCondition>() {
@Override
public int compare(JoinCondition o1, JoinCondition o2) {
TypeEntityDefinition o1Type = AlignmentUtil.getTypeEntity(o1.baseProperty);
TypeEntityDefinition o2Type = AlignmentUtil.getTypeEntity(o2.baseProperty);
return types.indexOf(o1Type) - types.indexOf(o2Type);
}
});
// check types of each condition
for (JoinCondition condition : sortedConditions) {
TypeEntityDefinition baseType = AlignmentUtil.getTypeEntity(condition.baseProperty);
TypeEntityDefinition joinType = AlignmentUtil.getTypeEntity(condition.joinProperty);
int baseIndex = types.indexOf(baseType);
int joinIndex = types.indexOf(joinType);
// types have to exist, and join has to be after base
if (baseIndex == -1 || joinIndex == -1)
return "Property references no specified type.";
if (joinIndex <= baseIndex)
return "Invalid condition for type order.";
conditionFound[joinIndex] = true;
// sorted conditions allow this dependsOn-check
if (parent[joinIndex] < baseIndex) {
if (!dependsOn(baseIndex, parent[joinIndex], parent))
return "A join type depends on two types which do not depend on each other.";
parent[joinIndex] = baseIndex;
}
}
// check whether each type (except the first) has a join condition
for (int i = 1; i < conditionFound.length; i++)
if (!conditionFound[i])
return "A joined type does not have any join conditions.";
return null;
}
// Checks whether the first type depends on the second type.
private boolean dependsOn(int highType, int lowType, int[] parent) {
while (highType > lowType)
highType = parent[highType];
return highType == lowType;
}
/**
* Represents a single join condition.
*/
public static class JoinCondition {
/**
* The property of the base type.
*/
public final PropertyEntityDefinition baseProperty;
/**
* The property of the type to join.
*/
public final PropertyEntityDefinition joinProperty;
/**
* Constructs a join condition to join the type of
* <code>joinProperty</code> if the condition
* <code>joinProperty = baseProperty</code> matches.
*
* @param baseProperty the property of a base type
* @param joinProperty the property of the type to join
*/
public JoinCondition(PropertyEntityDefinition baseProperty,
PropertyEntityDefinition joinProperty) {
this.baseProperty = baseProperty;
this.joinProperty = joinProperty;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((baseProperty == null) ? 0 : baseProperty.hashCode());
result = prime * result + ((joinProperty == null) ? 0 : joinProperty.hashCode());
return result;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JoinCondition other = (JoinCondition) obj;
if (baseProperty == null) {
if (other.baseProperty != null)
return false;
}
else if (!baseProperty.equals(other.baseProperty))
return false;
if (joinProperty == null) {
if (other.joinProperty != null)
return false;
}
else if (!joinProperty.equals(other.joinProperty))
return false;
return true;
}
}
}