/*
* 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.
*
* Copied from the Apache UIMA Sandbox Dictionary Annotator FeaturePathInfo_Impl.
*
* CHANGES:
* - Changed class name.
* - Replaced exceptions with FeaturePathExceptions.
*/
package de.tudarmstadt.ukp.dkpro.core.api.featurepath;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.impl.LowLevelCAS;
import org.apache.uima.cas.impl.TypeSystemUtils;
import org.apache.uima.cas.impl.TypeSystemUtils.PathValid;
import org.apache.uima.cas.text.AnnotationFS;
/**
* Code copied and adjusted from Dictionary Annotator
*
* FeaturePathInfo implementation validates the given featurePath for a
* specified annotation. It can return the featurePath value as string or match
* the featurePath value against a specified condition.
*/
public class FeaturePathInfo {
// featurePath string, separated by "/"
private String featurePathString;
// featurePath element names
private ArrayList<String> featurePathElementNames;
// featurePath element features
private ArrayList<Feature> featurePathElements;
/**
* Constructor to create a new featurePath object
*/
public FeaturePathInfo() {
this.featurePathElementNames = new ArrayList<String>();
this.featurePathElements = null;
}
/**
* Initialize the object's featurePath for the given type. If the featurePath
* is not valid an exception is thrown.
*
* @param featurePath
* featurePath string separated by "/"
* @throws FeaturePathException
* if an error occurs during initialization of the feature path
*/
public void initialize(String featurePath)
throws FeaturePathException {
this.featurePathString = featurePath;
this.featurePathElementNames = new ArrayList<String>();
this.featurePathElements = null;
// check featurePath for invalid character sequences
if (this.featurePathString.indexOf("//") > -1) {
// invalid featurePath syntax
throw new FeaturePathException();
}
// parse feature path into path elements
StringTokenizer tokenizer = new StringTokenizer(this.featurePathString,
"/");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
this.featurePathElementNames.add(token);
}
}
/**
* Checks the feature path for the given type and checks if it can be valid.
*
* @param featurePathType
* the anchor type.
* @throws FeaturePathException
* if an error occurs during initialization of the feature path
*/
public void typeSystemInit(Type featurePathType)
throws FeaturePathException {
// validate featurePath for given type
PathValid pathValid = TypeSystemUtils.isPathValid(featurePathType,
this.featurePathElementNames);
if (PathValid.NEVER == pathValid) {
// invalid featurePath - throw an configuration exception
throw new FeaturePathException();
} else if (PathValid.ALWAYS == pathValid) {
// the featurePath is always valid, so we can resolve and cache the
// path elements
this.featurePathElements = new ArrayList<Feature>();
Type currentType = featurePathType;
// iterate over all featurePathNames and store the resolved CAS
// feature in the featurePathElements list
for (int i = 0; i < this.featurePathElementNames.size(); i++) {
// get feature
Feature feature = currentType
.getFeatureByBaseName(this.featurePathElementNames.get(i));
// store feature
this.featurePathElements.add(feature);
// get current feature type to resolve the next feature name
currentType = feature.getRange();
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.annotator.dict_annot.dictionary.impl.FeaturePathInfo#getValue(org.apache.uima.cas.text.AnnotationFS)
*/
public String getValue(AnnotationFS annotFS) {
// handle special case where no featurePath was specified
// (featurePathString == null)
if (this.featurePathElementNames.size() == 0) {
// no featurePath was specified, return the coveredText of the annotFS
// as matching text
return annotFS.getCoveredText();
} else {
// we have a feature path that must be evaluated
// featurePathValue
String featurePathValue = null;
// check if further featurePath elements are possible
boolean noFurtherElementsPossible = false;
// set current FS values
FeatureStructure currentFS = annotFS;
Type currentType = annotFS.getType();
int currentFeatureTypeCode = LowLevelCAS.TYPE_CLASS_INVALID;
// resolve feature path value
for (int i = 0; i < this.featurePathElementNames.size(); i++) {
// if we had in the last iteration a primitive feature or a FS that
// was
// not set, the feature path is not valid for this annotation.
if (noFurtherElementsPossible) {
return null;
}
// get the Feature for the current featurePath element. If the
// featurePath is always valid the featurePath Feature elements are
// cached, otherwise the feature names must be resolved by name
Feature feature;
if (this.featurePathElements == null) {
// resolve Feature by name
feature = currentType
.getFeatureByBaseName(this.featurePathElementNames.get(i));
} else {
// use cached Feature element
feature = this.featurePathElements.get(i);
}
// if feature is null the feature was not defined
if (feature == null) {
return null;
}
// get feature type and type code
Type featureType = feature.getRange();
currentFeatureTypeCode = TypeSystemUtils.classifyType(featureType);
// switch feature type code
switch (currentFeatureTypeCode) {
case LowLevelCAS.TYPE_CLASS_STRING:
featurePathValue = currentFS.getStringValue(feature);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_INT:
featurePathValue = Integer.toString(currentFS
.getIntValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_BOOLEAN:
featurePathValue = Boolean.toString(currentFS
.getBooleanValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_BYTE:
featurePathValue = Byte
.toString(currentFS.getByteValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_DOUBLE:
featurePathValue = Double.toString(currentFS
.getDoubleValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_FLOAT:
featurePathValue = Float.toString(currentFS
.getFloatValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_LONG:
featurePathValue = Long
.toString(currentFS.getLongValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_INVALID:
featurePathValue = null;
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_SHORT:
featurePathValue = Short.toString(currentFS
.getShortValue(feature));
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_FS:
currentFS = currentFS.getFeatureValue(feature);
if (currentFS == null) {
// FS value not set - feature path cannot return a valid value
noFurtherElementsPossible = true;
featurePathValue = null;
} else {
currentType = currentFS.getType();
}
break;
default:
// // create message for unsupported feature path type
// ResourceBundle bundle = ResourceBundle.getBundle(
// DictionaryAnnotator.MESSAGE_DIGEST, Locale.getDefault(),
// this.getClass().getClassLoader());
// // retrieve the message from the resource bundle
// String rawMessage = bundle
// .getString("dictionary_annotator_error_feature_path_element_not_supported");
// MessageFormat messageFormat = new MessageFormat(rawMessage);
// messageFormat.setLocale(Locale.getDefault());
// String message = messageFormat.format(new Object[] {
// currentType.getName(),
// this.featurePathElementNames.get(i),
// this.featurePathString });
// // throw a RuntimeException with the unsupported feature path
throw new RuntimeException("! feature path error");
}
}
// the featurePath was processed correctly, check the featurePath value
if (featurePathValue != null) {
return featurePathValue;
} else {
// it seems that the last featurePath element was a FS
// check now if the FS is of type AnnotationFS
if (currentFS != null
&& currentFeatureTypeCode == LowLevelCAS.TYPE_CLASS_FS) {
if (currentFS instanceof AnnotationFS) {
// the last FS was an Annotation, so return the covered Text
// of the annotation as featurePath value
return ((AnnotationFS) currentFS).getCoveredText();
}
}
return null;
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.annotator.regex.FeaturePath#getFeaturePath()
*/
public String getFeaturePath() {
return this.featurePathString;
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.annotator.dict_annot.dictionary.impl.FeaturePathInfo#match(org.apache.uima.cas.text.AnnotationFS,
* org.apache.uima.annotator.dict_annot.impl.Condition)
*/
public boolean match(AnnotationFS annotFS, Condition condition) {
// handle special case where no featurePath was specified
if (this.featurePathElementNames.size() == 0) {
// no featurePath was specified, return the false
return false;
} else {
// we have a feature path that must be evaluated
// check if further featurePath elements are possible
boolean noFurtherElementsPossible = false;
// check condition result
boolean checkCondidtion = false;
// set current FS values
FeatureStructure currentFS = annotFS;
Type currentType = annotFS.getType();
int currentFeatureTypeCode = LowLevelCAS.TYPE_CLASS_INVALID;
// resolve feature path value
for (int i = 0; i < this.featurePathElementNames.size(); i++) {
// if we had in the last iteration a primitive feature or a FS that
// was not set, the feature path is not valid for this annotation.
if (noFurtherElementsPossible) {
return false;
}
// get the Feature for the current featurePath element. If the
// featurePath is always valid the featurePath Feature elements are
// cached, otherwise the feature names must be resolved by name
Feature feature;
if (this.featurePathElements == null) {
// resolve Feature by name
feature = currentType
.getFeatureByBaseName(this.featurePathElementNames.get(i));
} else {
// use cached Feature element
feature = this.featurePathElements.get(i);
}
// if feature is null the feature was not defined
if (feature == null) {
return false;
}
// get feature type and type code
Type featureType = feature.getRange();
currentFeatureTypeCode = TypeSystemUtils.classifyType(featureType);
// switch feature type code
switch (currentFeatureTypeCode) {
case LowLevelCAS.TYPE_CLASS_STRING:
checkCondidtion = checkString(currentFS.getStringValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_INT:
checkCondidtion = checkInt(currentFS.getIntValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_BOOLEAN:
checkCondidtion = checkBoolean(currentFS.getBooleanValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_BYTE:
checkCondidtion = checkByte(currentFS.getByteValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_DOUBLE:
checkCondidtion = checkDouble(currentFS.getDoubleValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_FLOAT:
checkCondidtion = checkFloat(currentFS.getFloatValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_LONG:
checkCondidtion = checkLong(currentFS.getLongValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_INVALID:
noFurtherElementsPossible = true;
checkCondidtion = false;
break;
case LowLevelCAS.TYPE_CLASS_SHORT:
checkCondidtion = checkShort(currentFS.getShortValue(feature),
condition);
noFurtherElementsPossible = true;
break;
case LowLevelCAS.TYPE_CLASS_FS:
currentFS = currentFS.getFeatureValue(feature);
if (currentFS == null) {
// FS value not set - feature path cannot return a valid value
noFurtherElementsPossible = true;
checkCondidtion = false;
} else {
currentType = currentFS.getType();
}
break;
default:
// create message for unsupported feature path type
// ResourceBundle bundle = ResourceBundle.getBundle(
// DictionaryAnnotator.MESSAGE_DIGEST, Locale.getDefault(),
// this.getClass().getClassLoader());
// // retrieve the message from the resource bundle
// String rawMessage = bundle
// .getString("dictionary_annotator_error_feature_path_element_not_supported");
// MessageFormat messageFormat = new MessageFormat(rawMessage);
// messageFormat.setLocale(Locale.getDefault());
// String message = messageFormat.format(new Object[] {
// currentType.getName(),
// this.featurePathElementNames.get(i),
// this.featurePathString });
// // throw a RuntimeException with the unsupported feature path
// throw new RuntimeException(message);
throw new RuntimeException("feature path element not supported");
}
}
return checkCondidtion;
}
}
/**
* Check the given byte value for the specified condition
*
* @param in
* byte value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the byte value
*/
private final boolean checkByte(byte in, Condition condition) {
String value = condition.getValue();
byte v;
try {
v = Byte.parseByte(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given double value for the specified condition
*
* @param in
* double value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the double value
*/
private final boolean checkDouble(double in, Condition condition) {
String value = condition.getValue();
double v;
try {
v = Double.parseDouble(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given float value for the specified condition
*
* @param in
* float value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the float value
*/
private final boolean checkFloat(float in, Condition condition) {
String value = condition.getValue();
float v;
try {
v = Float.parseFloat(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given long value for the specified condition
*
* @param in
* long value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the long value
*/
private final boolean checkLong(long in, Condition condition) {
String value = condition.getValue();
long v;
try {
v = Long.parseLong(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given int value for the specified condition
*
* @param in
* int value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the int value
*/
private final boolean checkInt(int in, Condition condition) {
String value = condition.getValue();
int v;
try {
v = Integer.parseInt(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given short value for the specified condition
*
* @param in
* short value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the short value
*/
private final boolean checkShort(short in, Condition condition) {
String value = condition.getValue();
short v;
try {
v = Short.parseShort(value);
} catch (NumberFormatException e) {
return false;
}
switch (condition.getConditionType()) {
case EQUALS: {
return (in == v);
}
case NOT_EQUALS: {
return (in != v);
}
case GREATER: {
return (in > v);
}
case GREATER_EQ: {
return (in >= v);
}
case LESS: {
return (in < v);
}
case LESS_EQ: {
return (in <= v);
}
default: {
return false;
}
}
}
/**
* Check the given String value for the specified condition
*
* @param s
* String value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the String value
*/
private final boolean checkString(String s, Condition condition) {
String value = condition.getValue();
// Value can not be null
assert (value != null);
FilterOp op = condition.getConditionType();
// First check for conditions that makes sense if input value is null.
switch (op) {
case NULL: {
return (s == null);
}
case NOT_NULL: {
return (s != null);
}
}
// If we get here and s is null, we fail.
if (s == null) {
return false;
}
// Now neither the value nor s are null, so we can compare them.
final int comp = s.compareTo(value);
switch (op) {
case EQUALS: {
return (comp == 0);
}
case NOT_EQUALS: {
return (comp != 0);
}
case GREATER: {
return (comp > 0);
}
case GREATER_EQ: {
return (comp >= 0);
}
case LESS: {
return (comp < 0);
}
case LESS_EQ: {
return (comp <= 0);
}
default: {
// Can't get here, but the compiler doesn't know that.
return false;
}
}
}
/**
* Check the given boolean value for the specified condition
*
* @param b
* boolean value to check
* @param condition
* condition to check
*
* @return returns true if the condition match the boolean value
*/
private final boolean checkBoolean(boolean b, Condition condition) {
String value = condition.getValue();
boolean v = Boolean.parseBoolean(value);
switch (condition.getConditionType()) {
case EQUALS: {
return (v == b);
}
case NOT_EQUALS: {
return (v != b);
}
default: {
return false;
}
}
}
}