blob: 1cb7f4c2e290f9b6ce862933cb892abbe14d5e55 [file] [log] [blame]
/*
* 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.uima.cas.impl;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.apache.uima.UIMARuntimeException;
import org.apache.uima.cas.CASException;
import org.apache.uima.cas.CASRuntimeException;
import org.apache.uima.cas.CommonArrayFS;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeaturePath;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeClass;
import org.apache.uima.cas.impl.TypeSystemUtils.PathValid;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.jcas.cas.TOP;
/**
* Implementation of the feature path interface.
*/
class FeaturePathImpl implements FeaturePath {
private static final String MESSAGE_DIGEST = "org.apache.uima.cas.impl.annot_impl";
private static final String FEATURE_PATH_SEPARATOR = "/";
private static final String BUILT_IN_FUNCTION_SEPARATOR = ":";
private static final byte NO_BUILT_IN_FUNCTION = 0;
private static final byte FUNCTION_COVERED_TEXT = 1;
private static final byte FUNCTION_ID = 2;
private static final byte FUNCTION_TYPE_NAME = 3;
private static final String FUNCTION_NAME_COVERED_TEXT = "coveredtext()";
private static final String FUNCTION_NAME_ID = "fsid()";
private static final String FUNCTION_NAME_TYPE_NAME = "typename()";
private static final TOP FEATURE_PATH_FAILED = new TOP();
/**
* The path's builtInFunction, or 0
*/
private byte builtInFunction = 0;
private String originalBuiltInName = null;
// featurePath element names
final private ArrayList<String> featurePathElementNames = new ArrayList<String>();
private boolean pathStartsWithSlash = true;
/**
* FeatureImpl array corresponding to feature path names.
* This can change for each evaluation of this FeaturePath instance against a
* different starting Feature Structure.
* It can be pre-set-up using typeInit.
* It has values for the first names in the featurePathElementNames which are always valid for a
* given starting type (set into boundBaseType).
*/
final private ArrayList<FeatureImpl> boundFeatures = new ArrayList<FeatureImpl>();
private FeatureImpl targetFeature; // set to the last value of boundFeatures
private TypeImpl targetType; // set to type of range of last found feature, works when there are no features
/**
* The Type used as the starting type for path validation
* This must be non-null if boundFeatures size > 0;
*/
private TypeImpl boundBaseType = null;
/*
* (non-Javadoc)
*
* @see
* org.apache.uima.cas.FeaturePath#addFeature(org.apache.uima.cas.Feature)
*/
@Override
public void addFeature(Feature feat) {
// check if currently feature path ends with a built-in function
if (this.builtInFunction > 0) {
throw new CASRuntimeException(MESSAGE_DIGEST, "INVALID_FEATURE_PATH_SYNTAX_ADD",
new Object[] { getFeaturePathString(), feat.getShortName() });
}
// add feature to feature path
this.featurePathElementNames.add(feat.getShortName());
this.boundFeatures.add((FeatureImpl) feat);
// if current featurePath was already initialized we cannot guarantee that
// the path is still ever valid so we have to evaluate the path on the
// fly.
if (this.boundBaseType != null &&
PathValid.NEVER == TypeSystemUtils.isPathValid(this.boundBaseType, this.featurePathElementNames)) {
this.boundBaseType = null; // can't be used for this path
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getFeature(int)
*/
@Override
public FeatureImpl getFeature(int i) {
return (this.size() == this.boundFeatures.size()) ? this.boundFeatures.get(i)
: null;
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#size()
*/
@Override
public int size() {
return this.featurePathElementNames.size();
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#initialize(java.lang.String)
*/
@Override
public void initialize(String featurePath) throws CASException {
this.builtInFunction = NO_BUILT_IN_FUNCTION;
this.originalBuiltInName = null;
// throw exception if featurePath is null
if (featurePath == null) {
throw new CASException(MESSAGE_DIGEST, "INVALID_FEATURE_PATH_SYNTAX",
new Object[] { featurePath, "null for a feature path" });
}
pathStartsWithSlash = featurePath.startsWith("/"); // v2 compatibility
// check featurePath for invalid character sequences
if (featurePath.indexOf("//") > -1) { // two forward slashes in a
// row is invalid
// invalid featurePath syntax
throw new CASException(MESSAGE_DIGEST, "INVALID_FEATURE_PATH_SYNTAX",
new Object[] { featurePath, "//" });
}
this.featurePathElementNames.clear();
// parse feature path into path elements
StringTokenizer tokenizer = new StringTokenizer(featurePath, FEATURE_PATH_SEPARATOR);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
// check if there are more tokens available, if we are at the last
// token we have to check for built-in functions
if (tokenizer.hasMoreTokens()) {
this.featurePathElementNames.add(token);
} else {
// we have the last token, check for built-in functions
int index = -1;
if ((index = token.indexOf(BUILT_IN_FUNCTION_SEPARATOR)) != -1) {
if (index > 0) {
// we have a built-in function that is separated with a ":"
this.featurePathElementNames.add(token.substring(0, index));
}
// get built-in function
originalBuiltInName = token.substring(index + 1);
String builtInFunctionName = originalBuiltInName.toLowerCase();
if (builtInFunctionName.equals(FUNCTION_NAME_COVERED_TEXT)) {
this.builtInFunction = FUNCTION_COVERED_TEXT;
} else if (builtInFunctionName.equals(FUNCTION_NAME_ID)) {
this.builtInFunction = FUNCTION_ID;
} else if (builtInFunctionName.equals(FUNCTION_NAME_TYPE_NAME)) {
this.builtInFunction = FUNCTION_TYPE_NAME;
} else {
throw new CASException(MESSAGE_DIGEST, "INVALID_FEATURE_PATH_SYNTAX",
new Object[] { featurePath, builtInFunctionName });
}
} else {
this.featurePathElementNames.add(token);
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#typeInit(org.apache.uima.cas.Type)
*/
@Override
public void typeInit(Type typeAtStartOfFeaturePath) throws CASException {
this.boundBaseType = (TypeImpl) typeAtStartOfFeaturePath;
// do feature path type initialization only if a featurePath is available
if (this.featurePathElementNames.size() > 0) {
// LowLevelTypeSystem llTypeSystem = ((TypeImpl) featurePathType)
// .getTypeSystem().getLowLevelTypeSystem();
// store featurePathType
this.boundBaseType = (TypeImpl) typeAtStartOfFeaturePath;
// validate featurePath for given type
if (PathValid.NEVER == TypeSystemUtils.isPathValid(typeAtStartOfFeaturePath, this.featurePathElementNames)) {
// invalid featurePath - throw an configuration exception
throw new CASException(MESSAGE_DIGEST, "ERROR_VALIDATE_FEATURE_PATH",
new Object[] { getFeaturePathString(), typeAtStartOfFeaturePath.getName() });
} else {
// is ALWAYS or POSSIBLE.
// ALWAYS means all features are available at the top-most type
// POSSIBLE means one or more features is not present at the top-most type, but are available in
// one or more subtypes.
this.boundFeatures.clear(); // reset
// object
TypeImpl currentType = (TypeImpl) typeAtStartOfFeaturePath;
// iterate over all featurePathNames and store the resolved CAS
// feature in the boundFeatures list, until one not found
for (String featName : this.featurePathElementNames) {
FeatureImpl fi = currentType.getFeatureByBaseName(featName);
if (fi != null) {
this.boundFeatures.add(fi);
currentType = fi.getRangeImpl();
} else {
break;
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getFeaturePath()
*/
@Override
public String getFeaturePath() {
return getFeaturePathString();
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getBooleanValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Boolean getBooleanValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getBooleanValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getByteValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Byte getByteValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getByteValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getDoubleValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Double getDoubleValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getDoubleValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getFloatValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Float getFloatValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getFloatValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getFSValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public FeatureStructure getFSValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs;
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getIntValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Integer getIntValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getIntValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getLongValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Long getLongValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getLongValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getShortValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Short getShortValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getShortValue(targetFeature);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getStringValue(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public String getStringValue(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getStringValue(targetFeature);
}
// /*
// * (non-Javadoc)
// *
// * @see
// * org.apache.uima.cas.FeaturePath#getJavaObjectValue(org.apache.uima.cas.
// * FeatureStructure)
// */
// @Override
// public Object getJavaObjectValue(FeatureStructure fs) {
// TOP tgtFs = getTargetFs((TOP) fs);
// return (tgtFs == FEATURE_PATH_FAILED) ? null : tgtFs.getJavaObjectValue(targetFeature);
// }
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getType(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public Type getType(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
return (tgtFs == FEATURE_PATH_FAILED) ? null : targetType;
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getTypClass(org.apache.uima.cas.
* FeatureStructure)
* @deprecated use getTypeClass instead (spelling correction)
*/
@Deprecated
@Override
public TypeClass getTypClass(FeatureStructure fs) {
TypeImpl type = (TypeImpl) getType(fs);
return (type == null) ? null : TypeClass.values()[TypeSystemImpl.getTypeClass(type)];
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getTypClass(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public TypeClass getTypeClass(FeatureStructure fs) {
TypeImpl type = (TypeImpl) getType(fs);
return (type == null) ? null : TypeClass.values()[TypeSystemImpl.getTypeClass(type)];
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#getValueAsString(org.apache.uima.cas.
* FeatureStructure)
*/
@Override
public String ll_getValueAsString(int fsRef, LowLevelCAS llCas) {
TOP fs = llCas.ll_getFSForRef(fsRef);
return getValueAsString(fs);
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FeaturePath#ll_getValueAsString(int,
* org.apache.uima.cas.impl.LowLevelCAS)
*/
@Override
public String getValueAsString(FeatureStructure fs) {
TOP tgtFs = getTargetFs((TOP) fs);
if (tgtFs == FEATURE_PATH_FAILED) {
return null;
}
if (targetType == null) {
return null;
}
switch (TypeSystemImpl.getTypeClass(targetType)) {
case LowLevelCAS.TYPE_CLASS_INVALID:
return null;
case LowLevelCAS.TYPE_CLASS_STRING:
case LowLevelCAS.TYPE_CLASS_BOOLEAN:
case LowLevelCAS.TYPE_CLASS_BYTE:
case LowLevelCAS.TYPE_CLASS_SHORT:
case LowLevelCAS.TYPE_CLASS_INT:
case LowLevelCAS.TYPE_CLASS_LONG:
case LowLevelCAS.TYPE_CLASS_FLOAT:
case LowLevelCAS.TYPE_CLASS_DOUBLE:
verifyNoBuiltInFunction();
return tgtFs.getFeatureValueAsString(targetFeature);
case LowLevelCAS.TYPE_CLASS_BOOLEANARRAY:
case LowLevelCAS.TYPE_CLASS_BYTEARRAY:
case LowLevelCAS.TYPE_CLASS_SHORTARRAY:
case LowLevelCAS.TYPE_CLASS_INTARRAY:
case LowLevelCAS.TYPE_CLASS_LONGARRAY:
case LowLevelCAS.TYPE_CLASS_FLOATARRAY:
case LowLevelCAS.TYPE_CLASS_DOUBLEARRAY:
case LowLevelCAS.TYPE_CLASS_STRINGARRAY:
case LowLevelCAS.TYPE_CLASS_FSARRAY:
if (this.builtInFunction > NO_BUILT_IN_FUNCTION) {
return evaluateBuiltInFunction(tgtFs);
}
return ((CommonArrayFS)tgtFs).getValuesAsCommaSeparatedString();
case LowLevelCAS.TYPE_CLASS_FS:
if (tgtFs == null) {
return null;
}
if (this.builtInFunction > NO_BUILT_IN_FUNCTION) {
return evaluateBuiltInFunction(tgtFs);
}
return tgtFs.toString();
} // end of switch
return null;
}
/**
* Method that throws the CASRuntimeException for an unsupported built-in
* function
*
* @param typeName
* type name that does not support the built-in function
*/
private void throwBuiltInFunctionException(String typeName) {
// get built-in function name
String functionName = null;
if (this.builtInFunction == FUNCTION_COVERED_TEXT) {
functionName = FUNCTION_NAME_COVERED_TEXT;
} else if (this.builtInFunction == FUNCTION_ID) {
functionName = FUNCTION_NAME_ID;
} else if (this.builtInFunction == FUNCTION_TYPE_NAME) {
functionName = FUNCTION_NAME_TYPE_NAME;
}
// throw runtime exception
throw new CASRuntimeException(MESSAGE_DIGEST, "BUILT_IN_FUNCTION_NOT_SUPPORTED",
new Object[] { functionName, typeName });
}
/**
* evaluate the built-in function for the returned FeatureStructure
*
* @param returnFS
* FeatureStructure that is returned
*
* @return Returns the built-in function value for the given FS.
*/
private String evaluateBuiltInFunction(TOP returnFS) {
if (this.builtInFunction == FUNCTION_COVERED_TEXT) {
if (returnFS instanceof AnnotationFS) {
return ((AnnotationFS) returnFS).getCoveredText();
} else {
throw new CASRuntimeException(MESSAGE_DIGEST, "BUILT_IN_FUNCTION_NOT_SUPPORTED",
new Object[] { FUNCTION_NAME_COVERED_TEXT, returnFS.getType().getName() });
}
} else if (this.builtInFunction == FUNCTION_ID) {
return Integer.toString(returnFS._id);
} else if (this.builtInFunction == FUNCTION_TYPE_NAME) {
return returnFS.getType().getName();
}
return null;
}
/**
* evaluates the internal feature path for the given FeatureStructure
*
* returns
*
*
* @param fs
* FeatureStructure to use as the starting point for the feature path
*
* @return
* the Feature Structure result,
* or the Feature Structure to run feature or array extraction on,
* or the Feature Structure to run a built-in function on
* or null (meaning no fs was found that matched the path).
*
* For 0 element feature paths, this is the same as the argument.
*
* For null fs, the returned value is null;
*
*/
private TOP getTargetFs(TOP fs) {
if (null == fs) {
return FEATURE_PATH_FAILED;
}
if (this.featurePathElementNames.size() == 0) {
targetType = fs._getTypeImpl();
return fs;
}
// we have a feature path that must be evaluated
if (boundBaseType == null || !boundBaseType.subsumes(fs._getTypeImpl())) {
boundFeatures.clear(); // reset if supplied FS not the one the features were calculated for.
boundBaseType = fs._getTypeImpl();
}
// set current FS values
TOP currentFs = fs;
TypeImpl rangeType = null;
int rangeTypeClass = -1;
// resolve feature path value
for (int i = 0; i < this.featurePathElementNames.size(); i++) {
if (currentFs == null) {
return FEATURE_PATH_FAILED;
}
if (i < this.boundFeatures.size()) {
targetFeature = this.boundFeatures.get(i);
/*
* It is possible that the previously bound feature isn't valid for this FS.
* This can happen if a type hierarchy defines 2 different features for two different subtypes of type Tt
* with the same feature name.
*
* So we check if this bound feature is appropriate for the current FS
*/
if ( ! ((TypeImpl)targetFeature.getDomain()).subsumes(currentFs._getTypeImpl())) {
setTargetFeature(currentFs, i);
}
} else {
setTargetFeature(currentFs, i);
}
// switch feature type class
// currentRangeTypeCode = llCas.ll_getTypeSystem().ll_getRangeType(targetFeatureCode);
targetType = rangeType = targetFeature.getRangeImpl();
rangeTypeClass = TypeSystemImpl.getTypeClass(rangeType);
switch (rangeTypeClass) {
case LowLevelCAS.TYPE_CLASS_STRING:
case LowLevelCAS.TYPE_CLASS_INT:
case LowLevelCAS.TYPE_CLASS_BOOLEAN:
case LowLevelCAS.TYPE_CLASS_BYTE:
case LowLevelCAS.TYPE_CLASS_DOUBLE:
case LowLevelCAS.TYPE_CLASS_FLOAT:
case LowLevelCAS.TYPE_CLASS_LONG:
case LowLevelCAS.TYPE_CLASS_SHORT:
case LowLevelCAS.TYPE_CLASS_INVALID:
return currentFs; // is the fs which has the feature which is the primitive value
case LowLevelCAS.TYPE_CLASS_BOOLEANARRAY:
case LowLevelCAS.TYPE_CLASS_BYTEARRAY:
case LowLevelCAS.TYPE_CLASS_DOUBLEARRAY:
case LowLevelCAS.TYPE_CLASS_FLOATARRAY:
case LowLevelCAS.TYPE_CLASS_FSARRAY:
case LowLevelCAS.TYPE_CLASS_INTARRAY:
case LowLevelCAS.TYPE_CLASS_LONGARRAY:
case LowLevelCAS.TYPE_CLASS_SHORTARRAY:
case LowLevelCAS.TYPE_CLASS_STRINGARRAY:
return currentFs.getFeatureValue(targetFeature);
case LowLevelCAS.TYPE_CLASS_FS:
currentFs = currentFs.getFeatureValue(targetFeature);
if (currentFs == null) {
if (i == (this.featurePathElementNames.size() - 1)) {
// at the last element, keep targetType == to the range type
} else {
/*
* not at the last element, so terminating the feature path prematurely.
* There are 2 cases:
* - the PathValid is POSSIBLE
* - the PathValid is ALWAYS
*/
PathValid pathValid = TypeSystemUtils.isPathValid(this.boundBaseType, this.featurePathElementNames);
if (pathValid == PathValid.POSSIBLE) {
targetType = null; // following v2 design here
}
}
return null;
}
break;
default: throw new CASRuntimeException(UIMARuntimeException.INTERNAL_ERROR);
} // end of switch
} // end of loop over all items in feature path
return currentFs;
}
private void setTargetFeature(TOP currentFs, int i) {
targetFeature = currentFs._getTypeImpl().getFeatureByBaseName(featurePathElementNames.get(i));
if (targetFeature == null) {
throw new CASRuntimeException(MESSAGE_DIGEST, "INVALID_FEATURE_PATH_FEATURE_NOT_DEFINED",
new Object[] { getFeaturePathString(), currentFs._getTypeImpl().getName(), this.featurePathElementNames.get(i) });
}
boundFeatures.add(targetFeature); // cache for future use
}
private void verifyNoBuiltInFunction() {
if (this.builtInFunction > NO_BUILT_IN_FUNCTION) {
throwBuiltInFunctionException(targetFeature.getRangeImpl().getName());
}
}
private String getFeaturePathString() {
StringBuilder sb = new StringBuilder();
if (featurePathElementNames.size() == 0) {
if (pathStartsWithSlash) {
sb.append('/');
}
} else {
for (String s : featurePathElementNames) {
sb.append('/').append(s);
}
}
appendBuiltInFunction(sb);
return sb.toString();
}
private void appendBuiltInFunction(StringBuilder sb) {
if (this.builtInFunction > 0) {
sb.append(':').append(originalBuiltInName); // because capitalization could be different
}
}
}