/*
* Copyright 2012
* Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology
* Technische Universität Darmstadt
*
* Licensed 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 de.tudarmstadt.ukp.clarin.webanno.api.annotation.util;
import static org.apache.uima.fit.util.CasUtil.selectCovered;
import static org.apache.uima.fit.util.JCasUtil.select;
import static org.apache.uima.fit.util.JCasUtil.selectCovered;
import static org.apache.uima.fit.util.JCasUtil.selectFollowing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.uima.cas.ArrayFS;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.impl.CASImpl;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.cas.text.AnnotationIndex;
import org.apache.uima.fit.util.CasUtil;
import org.apache.uima.fit.util.FSUtil;
import org.apache.uima.fit.util.JCasUtil;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.tcas.Annotation;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.AnnotatorState;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.LinkWithRoleModel;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
import de.tudarmstadt.ukp.clarin.webanno.model.Project;
import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token;
/**
* Contain Methods for updating CAS Objects directed from brat UI, different utility methods to
* process the CAS such getting the sentence address, determine page numbers,...
*/
public class WebAnnoCasUtil
{
/**
* Annotation a and annotation b are the same if they have the same address.
*
* @param a
* a FS.
* @param b
* a FS.
* @return if both FSes are the same.
*/
public static boolean isSame(FeatureStructure a, FeatureStructure b)
{
if (a == null || b == null) {
return false;
}
if (a.getCAS() != b.getCAS()) {
return false;
}
return getAddr(a) == getAddr(b);
}
/**
* Check if the two given offsets are within the same sentence.
*
* @param aJcas
* the JCAs.
* @param aReferenceOffset
* the reference offset.
* @param aCompareOffset
* the comparison offset.
* @return if the two offsets are within the same sentence.
*/
public static boolean isSameSentence(JCas aJcas, int aReferenceOffset, int aCompareOffset)
{
// Trivial case
if (aReferenceOffset == aCompareOffset) {
return true;
}
int offset1 = Math.min(aReferenceOffset, aCompareOffset);
int offset2 = Math.max(aReferenceOffset, aCompareOffset);
// Scanning through sentences
Iterator<Sentence> si = JCasUtil.iterator(aJcas, Sentence.class);
while (si.hasNext()) {
Sentence s = si.next();
if (s.getBegin() <= offset1 && offset1 <= s.getEnd()) {
return s.getBegin() <= offset2 && offset2 <= s.getEnd();
}
// Sentences are sorted. When we hit the first sentence that is beyond the reference
// offset, we will never again find a sentence that contains it.
if (offset1 < s.getBegin()) {
return false;
}
}
return false;
}
public static int getAddr(FeatureStructure aFS)
{
return ((CASImpl) aFS.getCAS()).ll_getFSRef(aFS);
}
public static AnnotationFS selectByAddr(JCas aJCas, int aAddress)
{
return selectByAddr(aJCas, AnnotationFS.class, aAddress);
}
public static FeatureStructure selectByAddr(CAS aCas, int aAddress)
{
return selectByAddr(aCas, FeatureStructure.class, aAddress);
}
public static <T extends FeatureStructure> T selectByAddr(CAS aCas, Class<T> aType, int aAddress)
{
return aType.cast(aCas.getLowLevelCAS().ll_getFSForRef(aAddress));
}
public static <T extends FeatureStructure> T selectByAddr(JCas aJCas, Class<T> aType,
int aAddress)
{
return aType.cast(aJCas.getLowLevelCas().ll_getFSForRef(aAddress));
}
private static <T extends Annotation> T selectSingleAt(JCas aJcas, final Class<T> type,
int aBegin, int aEnd)
{
List<T> covered = selectCovered(aJcas, type, aBegin, aEnd);
if (covered.isEmpty()) {
return null;
}
else {
T first = covered.get(0);
if (first.getBegin() == aBegin && first.getEnd() == aEnd) {
return first;
}
else {
return null;
}
}
}
public static List<AnnotationFS> selectAt(CAS aJcas, final Type type, int aBegin, int aEnd)
{
List<AnnotationFS> covered = CasUtil.selectCovered(aJcas, type, aBegin, aEnd);
// Remove all that do not have the exact same offset
Iterator<AnnotationFS> i = covered.iterator();
while (i.hasNext()) {
AnnotationFS cur = i.next();
if (!(cur.getBegin() == aBegin && cur.getEnd() == aEnd)) {
i.remove();
}
}
return covered;
}
/**
* Get an annotation using the begin/offsets and its type
*
* @param aJcas
* the JCas.
* @param aType
* the type.
* @param aBegin
* the begin offset.
* @param aEnd
* the end offset.
* @return the annotation FS.
*/
public static AnnotationFS selectSingleFsAt(JCas aJcas, Type aType, int aBegin, int aEnd)
{
for (AnnotationFS anFS : selectCovered(aJcas.getCas(), aType, aBegin, aEnd)) {
if (anFS.getBegin() == aBegin && anFS.getEnd() == aEnd) {
return anFS;
}
}
return null;
}
/**
* Get the sentence for this CAS based on the begin and end offsets. This is basically used to
* transform sentence address in one CAS to other sentence address for different CAS
*
* @param aJcas
* the JCas.
* @param aBegin
* the begin offset.
* @param aEnd
* the end offset.
* @return the sentence.
*/
public static Sentence selectSentenceAt(JCas aJcas, int aBegin, int aEnd)
{
return selectSingleAt(aJcas, Sentence.class, aBegin, aEnd);
}
/**
* Get overlapping annotations where selection overlaps with annotations.<br>
* Example: if annotation is (5, 13) and selection covered was from (7, 12); the annotation (5,
* 13) is returned as overlapped selection <br>
* If multiple annotations are [(3, 8), (9, 15), (16, 21)] and selection covered was from (10,
* 18), overlapped annotation [(9, 15), (16, 21)] should be returned
*
* @param <T>
* the JCas type.
* @param aJCas
* a JCas containing the annotation.
* @param aType
* a UIMA type.
* @param aBegin
* begin offset.
* @param aEnd
* end offset.
* @return a return value.
*/
public static <T extends Annotation> List<T> selectOverlapping(JCas aJCas,
final Class<T> aType, int aBegin, int aEnd)
{
List<T> annotations = new ArrayList<T>();
for (T t : select(aJCas, aType)) {
if (t.getBegin() >= aEnd) {
break;
}
// not yet there
if (t.getEnd() <= aBegin) {
continue;
}
annotations.add(t);
}
return annotations;
}
/**
* Get the internal address of the first sentence annotation from JCAS. This will be used as a
* reference for moving forward/backward sentences positions
*
* @param aJcas
* The CAS object assumed to contains some sentence annotations
* @return the sentence number or -1 if aJcas don't have sentence annotation
*/
public static int getFirstSentenceAddress(JCas aJcas)
{
int firstSentenceAddress = -1;
for (Sentence selectedSentence : select(aJcas, Sentence.class)) {
firstSentenceAddress = getAddr(selectedSentence);
break;
}
return firstSentenceAddress;
}
/**
* Get the internal address of the first sentence annotation from JCAS. This will be used as a
* reference for moving forward/backward sentences positions
*
* @param aJcas
* The CAS object assumed to contains some sentence annotations
* @return the sentence number or -1 if aJcas don't have sentence annotation
*/
public static Sentence getFirstSentence(JCas aJcas)
{
Sentence firstSentence = null;
for (Sentence s : select(aJcas, Sentence.class)) {
firstSentence = s;
break;
}
return firstSentence;
}
/**
* Get the current sentence based on the annotation begin/end offset
*
* @param aJCas
* the JCas.
* @param aBegin
* the begin offset.
* @param aEnd
* the end offset.
* @return the sentence.
*/
public static Sentence getCurrentSentence(JCas aJCas, int aBegin, int aEnd)
{
Sentence currentSentence = null;
for (Sentence sentence : select(aJCas, Sentence.class)) {
if (sentence.getBegin() <= aBegin && sentence.getEnd() > aBegin
&& sentence.getEnd() <= aEnd) {
currentSentence = sentence;
break;
}
}
return currentSentence;
}
/**
* Get the sentence based on the annotation begin offset
*
* @param aJCas
* the JCas.
* @param aBegin
* the begin offset.
* @return the sentence.
*/
public static Sentence getSentence(JCas aJCas, int aBegin)
{
Sentence currentSentence = null;
for (Sentence sentence : select(aJCas, Sentence.class)) {
if (sentence.getBegin() <= aBegin && sentence.getEnd() > aBegin) {
currentSentence = sentence;
break;
}
}
return currentSentence;
}
public static Token getNextToken(JCas aJCas, int aBegin, int aEnd)
{
AnnotationFS currentToken = selectSingleAt(aJCas, Token.class, aBegin, aEnd);
// thid happens when tokens such as Dr. OR Ms. selected with double
// click, which make seletected text as Dr OR Ms
if (currentToken == null) {
currentToken = selectSingleAt(aJCas, Token.class, aBegin, aEnd + 1);
}
Token nextToken = null;
for (Token token : selectFollowing(Token.class, currentToken, 1)) {
nextToken = token;
}
return nextToken;
}
/**
* Get the last sentence CAS address in the current display window
*
* @param aJcas
* the JCas.
* @param aFirstSentenceAddress
* the CAS address of the first sentence in the display window
* @param aWindowSize
* the window size
* @return The address of the last sentence address in the current display window.
*/
public static Sentence getLastSentenceInDisplayWindow(JCas aJcas, int aFirstSentenceAddress,
int aWindowSize)
{
int count = 0;
FSIterator<Sentence> si = seekByAddress(aJcas, Sentence.class, aFirstSentenceAddress);
Sentence s = si.get();
while (count < aWindowSize - 1) {
si.moveToNext();
if (si.isValid()) {
s = si.get();
}
else {
break;
}
count++;
}
return s;
}
/**
* Get an iterator position at the annotation with the specified address.
*
* @param aJcas
* the CAS object
* @param aType
* the expected annotation type
* @param aAddr
* the annotationa address
* @return the iterator.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T extends Annotation> FSIterator<T> seekByAddress(JCas aJcas, Class<T> aType,
int aAddr)
{
AnnotationIndex<T> idx = (AnnotationIndex) aJcas.getAnnotationIndex(JCasUtil
.getAnnotationType(aJcas, aType));
return idx.iterator(selectByAddr(aJcas, aAddr));
}
/**
* Get an iterator position at the annotation with the specified address.
*
* @param aJcas
* the CAS object
* @param aType
* the expected annotation type
* @param aFS
* the annotation to seek for
* @return the iterator.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T extends Annotation> FSIterator<T> seekByFs(JCas aJcas, Class<T> aType,
AnnotationFS aFS)
{
AnnotationIndex<T> idx = (AnnotationIndex) aJcas.getAnnotationIndex(JCasUtil
.getAnnotationType(aJcas, aType));
return idx.iterator(aFS);
}
/**
* Gets the address of the first sentence visible on screen in such a way that the specified
* focus offset is centered on screen.
*
* @param aJcas
* the CAS object
* @param aSentence
* the old sentence
* @param aFocosOffset
* the actual offset of the sentence.
* @param aProject
* the project.
* @param aDocument
* the document.
* @param aWindowSize
* the window size.
* @return the ID of the first sentence.
*/
public static Sentence findWindowStartCenteringOnSelection(JCas aJcas, Sentence aSentence,
int aFocosOffset, Project aProject, SourceDocument aDocument, int aWindowSize)
{
FSIterator<Sentence> si = seekByFs(aJcas, Sentence.class, aSentence);
// no auto-forward for single sentence window
Sentence s = si.get();
if (aWindowSize == 1) {
return s;
}
// Seek the sentence that contains the current focus
while (si.isValid()) {
if (s.getEnd() < aFocosOffset) {
// Focus after current sentence
si.moveToNext();
}
else if (aFocosOffset < s.getBegin()) {
// Focus before current sentence
si.moveToPrevious();
}
else {
// Focus must be in current sentence
break;
}
s = si.get();
}
// Center sentence
Sentence c = (Sentence) aSentence;
Sentence n = (Sentence) s;
if (aWindowSize == 2 && n.getBegin() > c.getBegin()) {
return s;
}
int count = 0;
while (si.isValid() && count < (aWindowSize / 2)) {
si.moveToPrevious();
if (si.isValid()) {
s = si.get();
}
count++;
}
return s;
}
public static int getNextSentenceAddress(JCas aJcas, Sentence aSentence) {
try {
return WebAnnoCasUtil.getAddr(selectFollowing(Sentence.class, aSentence, 1).get(0));
} catch (Exception e) { // end of the document reached
return WebAnnoCasUtil.getAddr(aSentence);
}
}
/**
* Move to the next page of size display window.
*
* @param aJcas
* the JCas.
* @param aCurrenSentenceBeginAddress
* The beginning sentence address of the current window.
* @param aWindowSize
* the window size.
* @return the Beginning address of the next window
*/
public static int getNextPageFirstSentenceAddress(JCas aJcas, int aCurrenSentenceBeginAddress,
int aWindowSize)
{
List<Integer> beginningAddresses = getDisplayWindowBeginningSentenceAddresses(aJcas,
aWindowSize);
int beginningAddress = aCurrenSentenceBeginAddress;
for (int i = 0; i < beginningAddresses.size(); i++) {
if (i == beginningAddresses.size() - 1) {
beginningAddress = beginningAddresses.get(i);
break;
}
if (beginningAddresses.get(i) == aCurrenSentenceBeginAddress) {
beginningAddress = beginningAddresses.get(i + 1);
break;
}
if ((beginningAddresses.get(i) < aCurrenSentenceBeginAddress && beginningAddresses
.get(i + 1) > aCurrenSentenceBeginAddress)) {
beginningAddress = beginningAddresses.get(i + 1);
break;
}
}
return beginningAddress;
}
/**
* Return the beginning position of the Sentence for the previous display window
*
* @param aJcas
* the JCas.
*
* @param aCurrenSentenceBeginAddress
* The beginning address of the current sentence of the display window
* @param aWindowSize
* the window size.
* @return hum?
*/
public static int getPreviousDisplayWindowSentenceBeginAddress(JCas aJcas,
int aCurrenSentenceBeginAddress, int aWindowSize)
{
List<Integer> beginningAddresses = getDisplayWindowBeginningSentenceAddresses(aJcas,
aWindowSize);
int beginningAddress = aCurrenSentenceBeginAddress;
for (int i = 0; i < beginningAddresses.size() - 1; i++) {
if (i == 0 && aCurrenSentenceBeginAddress >= beginningAddresses.get(i)
&& beginningAddresses.get(i + 1) >= aCurrenSentenceBeginAddress) {
beginningAddress = beginningAddresses.get(i);
break;
}
if (aCurrenSentenceBeginAddress >= beginningAddresses.get(i)
&& beginningAddresses.get(i + 1) >= aCurrenSentenceBeginAddress) {
beginningAddress = beginningAddresses.get(i);
break;
}
beginningAddress = beginningAddresses.get(i);
}
return beginningAddress;
}
public static int getLastDisplayWindowFirstSentenceAddress(JCas aJcas, int aWindowSize)
{
List<Integer> displayWindowBeginingSentenceAddresses = getDisplayWindowBeginningSentenceAddresses(
aJcas, aWindowSize);
return displayWindowBeginingSentenceAddresses.get(displayWindowBeginingSentenceAddresses
.size() - 1);
}
/**
* Get the total number of sentences
*
* @param aJcas
* the JCas.
* @return the number of sentences.
*/
public static int getNumberOfPages(JCas aJcas)
{
return select(aJcas, Sentence.class).size();
}
/**
* Returns the beginning address of all pages. This is used properly display<b> Page X of Y </b>
*
* @param aJcas
* the JCas.
* @param aWindowSize
* the window size.
* @return hum?
*/
public static List<Integer> getDisplayWindowBeginningSentenceAddresses(JCas aJcas,
int aWindowSize)
{
List<Integer> beginningAddresses = new ArrayList<Integer>();
int j = 0;
for (Sentence sentence : select(aJcas, Sentence.class)) {
if (j % aWindowSize == 0) {
beginningAddresses.add(getAddr(sentence));
}
j++;
}
return beginningAddresses;
}
/**
* Get the ordinal sentence number in the display window. This will be sent to brat so that it
* will adjust the sentence number to display accordingly
*
* @param aJcas
* the JCas.
* @param aSentenceAddress
* the sentence ID.
* @return the sentence number.
* @deprecated use {@link AnnotatorState#getFirstVisibleSentenceNumber()} instead
*/
@Deprecated
public static int getFirstSentenceNumber(JCas aJcas, int aSentenceAddress)
{
int sentenceNumber = 0;
for (Sentence sentence : select(aJcas, Sentence.class)) {
if (getAddr(sentence) == aSentenceAddress) {
break;
}
sentenceNumber++;
}
return sentenceNumber;
}
/**
* Get the sentence number at this specific position
*
* @param aJcas
* the JCas.
* @param aBeginOffset
* the begin offset.
* @return the sentence number.
*/
public static int getSentenceNumber(JCas aJcas, int aBeginOffset)
{
int sentenceNumber = 0;
Collection<Sentence> sentences = select(aJcas, Sentence.class);
if (sentences.isEmpty()) {
throw new IndexOutOfBoundsException("No sentences");
}
for (Sentence sentence : select(aJcas, Sentence.class)) {
if (sentence.getBegin() <= aBeginOffset && aBeginOffset <= sentence.getEnd()) {
sentenceNumber++;
break;
}
sentenceNumber++;
}
return sentenceNumber;
}
public static int getSentenceCount(JCas aJcas)
{
return select(aJcas, Sentence.class).size();
}
/**
* Get Sentence address for this ordinal sentence number. Used to go to specific sentence number
*
* @param aJcas
* the JCas.
* @param aSentenceNumber
* the sentence number.
* @return the ID.
*/
public static int getSentenceAddress(JCas aJcas, int aSentenceNumber)
{
int i = 1;
int address = 0;
if (aSentenceNumber < 1) {
return 0;
}
for (Sentence sentence : select(aJcas, Sentence.class)) {
if (i == aSentenceNumber) {
address = getAddr(sentence);
break;
}
address = getAddr(sentence);
i++;
}
if (aSentenceNumber > i) {
return 0;
}
return address;
}
/**
* For a span annotation, if a sub-token is selected, display the whole text so that the user is
* aware of what is being annotated, based on
* {@link WebAnnoCasUtil#selectOverlapping(JCas, Class, int, int)} ISSUE - Affected text not
* correctly displayed in annotation dialog (Bug #272)
*
* @param aJcas
* the JCas.
* @param aBeginOffset
* the begin offset.
* @param aEndOffset
* the end offset.
* @return the selected text.
*/
public static String getSelectedText(JCas aJcas, int aBeginOffset, int aEndOffset)
{
List<Token> tokens = WebAnnoCasUtil.selectOverlapping(aJcas, Token.class, aBeginOffset,
aEndOffset);
StringBuilder seletedTextSb = new StringBuilder();
for (Token token : tokens) {
seletedTextSb.append(token.getCoveredText() + " ");
}
return seletedTextSb.toString();
}
public static <T> T getFeature(FeatureStructure aFS, String aFeatureName)
{
Feature feature = aFS.getType().getFeatureByBaseName(aFeatureName);
if (feature == null) {
throw new IllegalArgumentException("Type [" + aFS.getType().getName()
+ "] has no feature called [" + aFeatureName + "]");
}
switch (feature.getRange().getName()) {
case CAS.TYPE_NAME_STRING:
return (T) aFS.getStringValue(feature);
case CAS.TYPE_NAME_BOOLEAN:
return (T) (Boolean) aFS.getBooleanValue(feature);
case CAS.TYPE_NAME_FLOAT:
return (T) (Float) aFS.getFloatValue(feature);
case CAS.TYPE_NAME_INTEGER:
return (T) (Integer) aFS.getIntValue(feature);
default:
throw new IllegalArgumentException("Cannot get value of feature [" + feature.getName()
+ "] with type [" + feature.getRange().getName() + "]");
}
}
public static <T> T getFeature(FeatureStructure aFS, AnnotationFeature aFeature)
{
Feature feature = aFS.getType().getFeatureByBaseName(aFeature.getName());
switch (aFeature.getMultiValueMode()) {
case NONE: {
// Sanity check
if (!ObjectUtils.equals(aFeature.getType(), feature.getRange().getName())) {
throw new IllegalArgumentException("Actual feature type ["
+ feature.getRange().getName() + "]does not match expected feature type ["
+ aFeature.getType() + "].");
}
// switch (aFeature.getType()) {
// case CAS.TYPE_NAME_STRING:
// return (T) aFS.getStringValue(feature);
// case CAS.TYPE_NAME_BOOLEAN:
// return (T) (Boolean) aFS.getBooleanValue(feature);
// case CAS.TYPE_NAME_FLOAT:
// return (T) (Float) aFS.getFloatValue(feature);
// case CAS.TYPE_NAME_INTEGER:
// return (T) (Integer) aFS.getIntValue(feature);
// default:
// throw new IllegalArgumentException("Cannot get value of feature ["
// + aFeature.getName() + "] with type [" + feature.getRange().getName() + "]");
// }
return getFeature(aFS, aFeature.getName());
}
case ARRAY: {
switch (aFeature.getLinkMode()) {
case WITH_ROLE: {
// Get type and features - we need them later in the loop
Feature linkFeature = aFS.getType().getFeatureByBaseName(aFeature.getName());
Type linkType = aFS.getCAS().getTypeSystem().getType(aFeature.getLinkTypeName());
Feature roleFeat = linkType.getFeatureByBaseName(aFeature
.getLinkTypeRoleFeatureName());
Feature targetFeat = linkType.getFeatureByBaseName(aFeature
.getLinkTypeTargetFeatureName());
List<LinkWithRoleModel> links = new ArrayList<>();
ArrayFS array = (ArrayFS) aFS.getFeatureValue(linkFeature);
if (array != null) {
for (FeatureStructure link : array.toArray()) {
LinkWithRoleModel m = new LinkWithRoleModel();
m.role = link.getStringValue(roleFeat);
m.targetAddr = getAddr(link.getFeatureValue(targetFeat));
m.label = ((AnnotationFS) link.getFeatureValue(targetFeat))
.getCoveredText();
links.add(m);
}
}
return (T) links;
}
default:
throw new IllegalArgumentException("Cannot get value of feature ["
+ aFeature.getName() + "] with link mode [" + aFeature.getMultiValueMode()
+ "]");
}
}
default:
throw new IllegalArgumentException("Unsupported multi-value mode ["
+ aFeature.getMultiValueMode() + "] on feature [" + aFeature.getName() + "]");
}
}
/**
* Set a feature value.
*
* @param aFS
* the feature structure.
* @param aFeature
* the feature within the annotation whose value to set. If this parameter is
* {@code null} then nothing happens.
* @param aValue
* the feature value.
*/
public static void setFeature(FeatureStructure aFS, AnnotationFeature aFeature, Object aValue)
{
if (aFeature == null) {
return;
}
Feature feature = aFS.getType().getFeatureByBaseName(aFeature.getName());
switch (aFeature.getMultiValueMode()) {
case NONE: {
// Sanity check
if (!ObjectUtils.equals(aFeature.getType(), feature.getRange().getName())) {
throw new IllegalArgumentException("On [" + aFS.getType().getName() + "] feature ["
+ aFeature.getName() + "] actual type [" + feature.getRange().getName()
+ "] does not match expected feature type [" + aFeature.getType() + "].");
}
switch (aFeature.getType()) {
case CAS.TYPE_NAME_STRING:
aFS.setStringValue(feature, (String) aValue);
break;
case CAS.TYPE_NAME_BOOLEAN:
aFS.setBooleanValue(feature, aValue != null ? (boolean) aValue : false);
break;
case CAS.TYPE_NAME_FLOAT:
aFS.setFloatValue(feature, aValue != null ? (float) aValue : 0.0f);
break;
case CAS.TYPE_NAME_INTEGER:
aFS.setIntValue(feature, aValue != null ? (int) aValue : 0);
break;
default:
throw new IllegalArgumentException("Cannot set value of feature ["
+ aFeature.getName() + "] with type [" + feature.getRange().getName()
+ "] to [" + aValue + "]");
}
break;
}
case ARRAY: {
switch (aFeature.getLinkMode()) {
case WITH_ROLE: {
// Get type and features - we need them later in the loop
setLinkFeature(aFS, aFeature, (List<LinkWithRoleModel>) aValue, feature);
break;
}
default:
throw new IllegalArgumentException("Unsupported link mode ["
+ aFeature.getLinkMode() + "] on feature [" + aFeature.getName() + "]");
}
break;
}
default:
throw new IllegalArgumentException("Unsupported multi-value mode ["
+ aFeature.getMultiValueMode() + "] on feature [" + aFeature.getName() + "]");
}
}
private static void setLinkFeature(FeatureStructure aFS, AnnotationFeature aFeature,
List<LinkWithRoleModel> aValue, Feature feature)
{
Type linkType = aFS.getCAS().getTypeSystem().getType(aFeature.getLinkTypeName());
Feature roleFeat = linkType.getFeatureByBaseName(aFeature
.getLinkTypeRoleFeatureName());
Feature targetFeat = linkType.getFeatureByBaseName(aFeature
.getLinkTypeTargetFeatureName());
// Create all the links
// FIXME: actually we could re-use existing link link feature structures
List<FeatureStructure> linkFSes = new ArrayList<>();
List<LinkWithRoleModel> linksList = aValue;
if (linksList != null) {
// remove duplicate links
Set<LinkWithRoleModel> links = new HashSet<>(linksList);
for (LinkWithRoleModel e : links) {
// Skip links that have been added in the UI but where the target has not
// yet been
// set
if (e.targetAddr == -1) {
continue;
}
FeatureStructure link = aFS.getCAS().createFS(linkType);
link.setStringValue(roleFeat, e.role);
link.setFeatureValue(targetFeat, selectByAddr(aFS.getCAS(), e.targetAddr));
linkFSes.add(link);
}
}
setLinkFeatureValue(aFS, feature, linkFSes);
}
public static void setLinkFeatureValue(FeatureStructure aFS, Feature aFeature,
List<FeatureStructure> linkFSes)
{
// Create a new array if size differs otherwise re-use existing one
ArrayFS array = (ArrayFS) WebAnnoCasUtil.getFeatureFS(aFS, aFeature.getShortName());
if (array == null || (array.size() != linkFSes.size())) {
array = aFS.getCAS().createArrayFS(linkFSes.size());
}
// Fill in links
array.copyFromArray(linkFSes.toArray(new FeatureStructure[linkFSes.size()]), 0, 0,
linkFSes.size());
aFS.setFeatureValue(aFeature, array);
}
/**
* Set a feature value.
*
* @param aFS
* the feature structure.
* @param aFeatureName
* the feature within the annotation whose value to set.
* @param aValue
* the feature value.
*/
public static void setFeatureFS(FeatureStructure aFS, String aFeatureName,
FeatureStructure aValue)
{
Feature labelFeature = aFS.getType().getFeatureByBaseName(aFeatureName);
aFS.setFeatureValue(labelFeature, aValue);
}
/**
* Get a feature value.
*
* @param aFS
* the feature structure.
* @param aFeatureName
* the feature within the annotation whose value to set.
* @return the feature value.
*/
public static FeatureStructure getFeatureFS(FeatureStructure aFS, String aFeatureName)
{
return aFS.getFeatureValue(aFS.getType().getFeatureByBaseName(aFeatureName));
}
public static boolean isRequiredFeatureMissing(AnnotationFeature aFeature, FeatureStructure aFS)
{
return aFeature.isRequired() && CAS.TYPE_NAME_STRING.equals(aFeature.getType())
&& StringUtils.isBlank(FSUtil.getFeature(aFS, aFeature.getName(), String.class));
}
}