/*
* 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.ui.function.contribution;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import eu.esdihumboldt.hale.common.align.extension.function.AbstractParameter;
import eu.esdihumboldt.hale.common.align.extension.function.FunctionDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.ParameterDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.PropertyFunctionDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.PropertyParameter;
import eu.esdihumboldt.hale.common.align.extension.function.PropertyParameterDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.TypeFunctionDefinition;
import eu.esdihumboldt.hale.common.align.extension.function.TypeParameter;
import eu.esdihumboldt.hale.common.align.extension.function.TypeParameterDefinition;
import eu.esdihumboldt.hale.common.align.model.EntityDefinition;
import eu.esdihumboldt.hale.common.align.model.Property;
import eu.esdihumboldt.hale.common.align.model.Type;
import eu.esdihumboldt.hale.common.align.model.condition.EntityCondition;
import eu.esdihumboldt.hale.common.align.model.condition.PropertyCondition;
import eu.esdihumboldt.hale.common.align.model.condition.TypeCondition;
import eu.esdihumboldt.hale.common.align.model.impl.DefaultProperty;
import eu.esdihumboldt.hale.common.align.model.impl.DefaultType;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.ui.selection.SchemaSelection;
/**
* Checks if a function is applicable for a schema selection.
*
* @author Simon Templer
*/
public class SchemaSelectionFunctionMatcher {
private final boolean ignoreTarget;
private final boolean ignoreSource;
/**
* Default constructor.
*/
public SchemaSelectionFunctionMatcher() {
this(false, false);
}
/**
* Constructor.
*
* @param ignoreSource if the source items should be ignored
* @param ignoreTarget if the target items should be ignored
*/
public SchemaSelectionFunctionMatcher(boolean ignoreSource, boolean ignoreTarget) {
super();
this.ignoreTarget = ignoreTarget;
this.ignoreSource = ignoreSource;
}
/**
* Determine if the given function is applicable for the given selection.
*
* @param function the function to test
* @param selection the schema selection
* @return <code>true</code> if the definition matches the selection,
* <code>false</code> otherwise
* @throws IllegalStateException if the function type is unknown (neither a
* type nor a property function)
*/
public boolean matchFunction(FunctionDefinition<?> function, SchemaSelection selection) {
if (function instanceof TypeFunctionDefinition) {
TypeFunctionDefinition tf = (TypeFunctionDefinition) function;
// match selection against function definition
return matchTypeFunction(tf, selection);
}
else if (function instanceof PropertyFunctionDefinition) {
PropertyFunctionDefinition pf = (PropertyFunctionDefinition) function;
// match selection against function definition
return matchPropertyFunction(pf, selection);
}
else {
throw new IllegalStateException("Unsupported function type encountered");
}
}
/**
* Test if the given function definition matches the given selection
*
* @param function the function definition
* @param selection the schema selection
* @return <code>true</code> if the definition matches the selection,
* <code>false</code> otherwise
*/
public boolean matchPropertyFunction(PropertyFunctionDefinition function,
SchemaSelection selection) {
/*
* Deactivated restriction that property cells can only be created with
* existing type cells.
*/
// AlignmentService as = (AlignmentService) PlatformUI.getWorkbench().getService(
// AlignmentService.class);
//
// if (!AlignmentUtil.hasTypeRelation(as.getAlignment())) {
// // don't allow creating property relations if there are no type
// // relations present
// return false;
// }
if (selection == null || selection.isEmpty()) {
// for no selection always allow creating a new cell if there are
// type relations present
return true;
}
Set<EntityDefinition> sourceItems = selection.getSourceItems();
Set<EntityDefinition> targetItems = selection.getTargetItems();
// check types
if (!ignoreSource && !checkType(sourceItems, PropertyEntityDefinition.class)) {
if (checkType(sourceItems, TypeEntityDefinition.class)) {
/*
* Allow types selected as source, handle as if no source is
* selected
*/
sourceItems = new HashSet<EntityDefinition>();
}
else
return false;
}
if (!ignoreTarget && !checkType(targetItems, PropertyEntityDefinition.class)) {
return false;
}
// TODO check if properties have the same parent type? what about joins?
// check counts
if (!ignoreSource && !checkCount(sourceItems.size(), function.getSource(), false)) {
return false;
}
if (!ignoreTarget && !checkCount(targetItems.size(), function.getTarget(), true)) {
return false;
}
// check mandatory source/target with special conditions
if (!ignoreSource && !checkMandatoryConditions(sourceItems, function.getSource())) {
return false;
}
if (!ignoreTarget && !checkMandatoryConditions(targetItems, function.getTarget())) {
return false;
}
// TODO other checks?
return true;
}
/**
* Checks if all entities that are mandatory in the function definition and
* have specific attached conditions can be met by at least one of the given
* schema entities.
*
* @param schemaEntities the schema entities
* @param functionEntities the entities as defined in the function
* @return if the conditions on mandatory function entities can be met
*/
protected boolean checkMandatoryConditions(Set<EntityDefinition> schemaEntities,
Iterable<? extends ParameterDefinition> functionEntities) {
for (ParameterDefinition functionEntity : functionEntities) {
if (functionEntity.getMinOccurrence() != 0) {
// entity is mandatory
if (functionEntity instanceof PropertyParameter) {
PropertyParameterDefinition pp = (PropertyParameterDefinition) functionEntity;
if (!pp.getConditions().isEmpty()) {
// check if there is an entity given that matches the
// conditions
if (!checkConditions(pp.getConditions(), schemaEntities)) {
// conditions not met
return false;
}
}
}
else if (functionEntity instanceof TypeParameter) {
TypeParameterDefinition tp = (TypeParameterDefinition) functionEntity;
if (!tp.getConditions().isEmpty()) {
// check if there is an entity given that matches the
// conditions
if (!checkConditions(tp.getConditions(), schemaEntities)) {
// conditions not met
return false;
}
}
}
}
}
return true;
}
/**
* Check if the given conditions can be met by at least on of the given
* schema entities.
*
* @param conditions the conditions to be met
* @param schemaEntities the schema entities to test
* @return if the conditions are met by one of the entities
*/
private boolean checkConditions(List<? extends EntityCondition<?>> conditions,
Set<EntityDefinition> schemaEntities) {
for (EntityDefinition entity : schemaEntities) {
boolean entityValid = true;
for (EntityCondition<?> condition : conditions) {
if (!checkCondition(condition, entity)) {
entityValid = false;
}
}
if (entityValid) {
return true;
}
}
return false;
}
/**
* Checks if the given condition is met by the given entity.
*
* @param condition the condition
* @param entity the entity to test
* @return if the entity meets the condition
*/
private boolean checkCondition(EntityCondition<?> condition, EntityDefinition entity) {
if (condition instanceof PropertyCondition) {
if (entity instanceof PropertyEntityDefinition) {
Property p = new DefaultProperty((PropertyEntityDefinition) entity);
return ((PropertyCondition) condition).accept(p);
}
}
else if (condition instanceof TypeCondition) {
if (entity instanceof TypeEntityDefinition) {
Type t = new DefaultType((TypeEntityDefinition) entity);
return ((TypeCondition) condition).accept(t);
}
}
return false;
}
/**
* Test if the given function definition matches the given selection
*
* @param function the function definition
* @param selection the schema selection
* @return <code>true</code> if the definition matches the selection,
* <code>false</code> otherwise
*/
public boolean matchTypeFunction(TypeFunctionDefinition function, SchemaSelection selection) {
// check types
Set<EntityDefinition> sourceItems = selection.getSourceItems();
if (!ignoreSource && !checkType(sourceItems, TypeEntityDefinition.class)) {
return false;
}
Set<EntityDefinition> targetItems = selection.getTargetItems();
if (!ignoreTarget && !checkType(targetItems, TypeEntityDefinition.class)) {
return false;
}
// check counts
if (!ignoreSource && !checkCount(sourceItems.size(), function.getSource(), true)) {
return false;
}
if (!ignoreTarget && !checkCount(targetItems.size(), function.getTarget(), false)) {
return false;
}
// check mandatory source/target with special conditions
if (!ignoreSource && !checkMandatoryConditions(sourceItems, function.getSource())) {
return false;
}
if (!ignoreTarget && !checkMandatoryConditions(targetItems, function.getTarget())) {
return false;
}
// TODO other checks?
return true;
}
/**
* Checks if each item is of the given type
*
* @param items the items
* @param type the type
* @return <code>true</code> if all items are of the given type
*/
protected boolean checkType(Iterable<?> items, Class<?> type) {
for (Object item : items) {
if (!type.isAssignableFrom(item.getClass())) {
return false;
}
}
return true;
}
/**
* Checks if the given entity count is compatible with the given set of
* entity definitions
*
* @param count the entity count
* @param entities the entity definitions
* @param isTarget if the entities are target entities
* @return if then entity count is compatible with the definitions
*/
protected boolean checkCount(int count, Set<? extends ParameterDefinition> entities,
boolean isTarget) {
int min = 0;
int max = 0;
for (ParameterDefinition param : entities) {
min += param.getMinOccurrence();
if (max != AbstractParameter.UNBOUNDED) {
int pMax = param.getMaxOccurrence();
if (pMax == AbstractParameter.UNBOUNDED) {
max = pMax;
}
else {
max += pMax;
}
}
}
// check minimum
if (count < min) {
return false;
}
if (max == 0 && !isTarget) {
// allow augmentations
return true;
}
// check maximum
if (max != AbstractParameter.UNBOUNDED && count > max) {
return false;
}
return true;
}
}