/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.stanbol.entityhub.servicesapi.mapping;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.query.Constraint;
import org.apache.stanbol.entityhub.servicesapi.query.TextConstraint;
import org.apache.stanbol.entityhub.servicesapi.util.PatternUtils;
/**
* A FieldMapping consisting of <ul>
* <li> a required field pattern that is matched against source field. Wildcards
* can be used to define such patterns (e.g. http://myOntology.com/* to match
* all properties defined within this Ontology)
* <li> a constraint that is used to filter values of the source field
* <li> a set of mappings (target field names) to copy the filtered values to. If
* this set contains a <code>null</code> value, than a field with the same
* name as the source field is created for the source.
* </ul>
* Note that a Filter with the pattern '*' no constraint and only an <code>null</code>
* value as mapping would create a 1:1 copy of the source.
* TODO: Is it OK to keep an actual implementation in the Service API package?
* @author Rupert Westenthaler
*
*/
public class FieldMapping {
/**
* The '#' char is used for comments
*/
public static final char COMMENT_CHAR = '#';
private final String pattern;
private final Pattern regex;
private final boolean usesWildcard;
private Set<String> mappings;
private Constraint filter;
private final boolean inverse;
private final boolean global;
/**
* Returns <code>true</code> if fields that match the pattern are ignored.
* This is can only be the case if no Filter is defined ({@link #getFilter()}
* returns <code>null</code>).
* @return the ignore field state
*/
public final boolean ignoreField() {
return inverse && filter == null;
}
/**
* Creates a FieldMapping that matches all fields but does not map any field.
* However it applies the filter to all other mappings if there is no more
* specific Filter applied.
* @param globalFilter The global filter. Typically a {@link TextConstraint}.
* @throws IllegalArgumentException if the parsed Filter is <code>null</code>
*/
public FieldMapping(Constraint globalFilter) throws IllegalArgumentException{
this(null,false,globalFilter);
}
/**
* Creates an 1:1 mapping for all values of fields that confirm the the
* defined pattern.<p>
* NOTE <ul>
* <li> mappings are ignored if the fieldPattern uses a wildcard
* <li> parsing <code>null</code> as fieldPattern matches any field, but does
* not map anything. This can be used to define global language filters-
* </ul>
* @param fieldPattern the pattern (typically the names pace followed by an *)
* @param mappedTo the list of target fields (if the mappings contain <code>null</code>
* filtered values of the current field in the source {@link Representation}
* are copied to the same field name in the target {@link Representation}.
* @throws IllegalArgumentException if <code>null</code> or an empty string is parsed as pattern
*/
public FieldMapping(String fieldPattern,String...mappedTo) throws IllegalArgumentException{
this(fieldPattern,null,mappedTo);
}
/**
* Creates a Mapping the maps (<code>ignore = false)</code>) or ignores (
* <code>ignore = true</code>) fields that match the defined pattern.
* @param fieldPattern the pattern used to match field names
* @param ignoreField if <code>false</code> (the default) than fields that match
* the parsed pattern are processed. If <code>true</code> than fields that
* match the pattern are ignored.
* @param mappedTo the list of target fields (if the mappings contain <code>null</code>
* filtered values of the current field in the source {@link Representation}
* are copied to the same field name in the target {@link Representation}.
* @throws IllegalArgumentException if <code>null</code> or an empty string is parsed as pattern
*/
public FieldMapping(String fieldPattern,boolean ignoreField,String...mappedTo) throws IllegalArgumentException{
this(fieldPattern,ignoreField,null,mappedTo);
}
/**
* Creates an mapping based on the parsed parameter
* @param fieldPattern the pattern used to select fields of the source representation
* @param filter the constraint used to filter values of selected fields
* @param mappedTo the list of target fields (if the mappings contain <code>null</code>
* filtered values of the current field in the source {@link Representation}
* are copied to the same field name in the target {@link Representation}.
* @throws IllegalArgumentException if <code>null</code> or an empty string is parsed as pattern
*/
public FieldMapping(String fieldPattern,Constraint filter,String...mappedTo) throws IllegalArgumentException {
this(fieldPattern,false,filter,mappedTo);
}
/**
* Private internal constructor that does all the initialisation stuff. This
* is private because some combinations would not result in valid mappings!
* The public constructors can only create valid field mappings.
* See documentation of the public variants!
*/
private FieldMapping(String fieldPattern,boolean ignore,Constraint filter,String...mappedTo){
if(fieldPattern == null || fieldPattern.length()<1){
if(filter == null){
throw new IllegalArgumentException("The Filter MUST NOT be NULL for the global Fieldmapping!");
}
this.global = true;
fieldPattern = "*";
} else {
this.global = false;
}
this.pattern = fieldPattern;
this.inverse = ignore; //if ignore=true -> filter==null && mappedTo.lenght==0
if(PatternUtils.usesWildCard(fieldPattern)){
this.regex = Pattern.compile(PatternUtils.wildcardToRegex(fieldPattern,true));
this.usesWildcard = true;
} else {
this.regex = null;
usesWildcard = false;
}
this.filter = filter;
if(this.global){
mappedTo = new String[]{}; //set to empty -> if global than map nothing
//NOTE: FieldMappings do now allow to map a Wildcard to an other field
// This is e.g. usefull for collecting all Literal values in a field
// holding the disambiguation context.
// } else if(this.usesWildcard){
// mappedTo = new String[]{null}; //wildcard always maps the selected field 1:1
} else if(mappedTo == null || mappedTo.length<1){
mappedTo = new String[]{null}; //if no mapping parse map the field 1:1
} //else used the parsed one
this.mappings = new HashSet<String>(Arrays.asList(mappedTo));
}
/**
* Returns <code>true</code> if this fieldMapping maps any field. This
* means that the {@link #getFieldPattern()}<code>.equals("*")</code>
* @return if this is a global field mapping
*/
public final boolean isGlobal() {
return global;
}
/**
* Getter for the RegexPettern representing the parsed wildcard.
* @return The regex pattern or <code>null</code> if this mapping does not
* use wildcards within the field pattern.
*/
public final Pattern getRegexPattern() {
return regex;
}
/**
* Returns <code>true</code> if the fieldPattern uses wildcards (? or *)
* @return Returns <code>true</code> if the fieldPattern uses wildcards
*/
public final boolean usesWildcard() {
return usesWildcard;
}
/**
* The Wildcard Pattern (*,?) used to match field name against.
* @return the pattern
*/
public String getFieldPattern(){
return pattern;
}
/**
* The target fields values of the source fields are copied to
* @return the target fields
*/
public Set<String> getMappings(){
return mappings;
}
/**
* The constraint used to filter values of the source field
* @return the constraint used to filter values of the source field
*/
public Constraint getFilter(){
return filter;
}
/**
* Setter for the filter used for values. If <code>null</code> is parsed
* the filter is removed.
* @param constraint the constraint or <code>null</code> to deactivate any
* filtering.
*/
public void setFilter(Constraint constraint){
this.filter = constraint;
}
/**
* Removes any specified filter
*/
public void removeFilter(){
setFilter(null);
}
/**
* Adds a mapping
* @param mapping the Mapping (use <code>null</code> to configure a 1:1 Mapping)
*/
public void addMapping(String mapping){
mappings.add(mapping);
}
/**
* Removes the mapping from the list. Please note, that if the last mapping
* is removed, the <code>null</code> mapping is added to the list to preserve
* the default 1:1 mapping.
* @param mapping The mapping to remove
*/
public void removeMapping(String mapping){
if(mappings.remove(mapping) && mappings.isEmpty()){
mappings.add(null); //if the last element is removed add null to
//preserve the 1:1 mapping
}
}
@Override
public String toString() {
return inverse?"!":""+pattern+(filter!=null?" | "+filter:"")+" > "+mappings;
}
@Override
public int hashCode() {
return pattern.hashCode()+mappings.hashCode()+(inverse?1:0)+(filter!=null?filter.hashCode():0);
}
@Override
public boolean equals(Object obj) {
return obj instanceof FieldMapping && // check type
((FieldMapping)obj).pattern.equals(pattern) && //check field pattern
((FieldMapping)obj).inverse == inverse && //check inverse
((FieldMapping)obj).mappings.equals(mappings) && //check mappings
( //check the optional value filter
(((FieldMapping)obj).filter == null && filter == null) ||
(((FieldMapping)obj).filter != null && ((FieldMapping)obj).filter.equals(filter))
);
}
}