/* * Copyright 2015 * Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology * Technische Universität Darmstadt * <p> * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.ui.curation.util; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.TypeUtil.getAdapter; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.getAddr; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.getFeature; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.selectByAddr; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.setFeature; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.uima.cas.ArrayFS; import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; import org.apache.uima.fit.util.JCasUtil; import org.apache.uima.jcas.JCas; import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationSchemaService; import de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.adapter.SpanAdapter; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.adapter.TypeAdapter; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.exception.AnnotationException; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.LinkWithRoleModel; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.ConfigurationSet; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff2.Position; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode; import de.tudarmstadt.ukp.clarin.webanno.model.MultiValueMode; import de.tudarmstadt.ukp.dkpro.core.api.lexmorph.type.pos.POS; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Lemma; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Stem; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; /** * Do a merge CAS out of multiple user annotations */ public class MergeCas { /** * Using {@code DiffResult}, determine the annotations to be deleted from the randomly generated * MergeCase. The initial Merge CAs is stored under a name {@code CurationPanel#CURATION_USER}. * <p> * Any similar annotations stacked in a {@code CasDiff2.Position} will be assumed a difference * <p> * Any two annotation with different value will be assumed a difference * <p> * Any non stacked empty/null annotations are assumed agreement * <p> * Any non stacked annotations with similar values for each of the features are assumed * agreement * <p> * Any two link mode / slotable annotations which agree on the base features are assumed * agreement * * @param aDiff * the {@code CasDiff2.DiffResult} * @param aJCases * a map of{@code JCas}s for each users and the random merge * @return the actual merge {@code JCas} */ public static JCas geMergeCas(DiffResult aDiff, Map<String, JCas> aJCases) { Set<FeatureStructure> slotFeaturesToReset = new HashSet<>(); Set<FeatureStructure> annotationsToDelete = new HashSet<>(); Set<String> users = aJCases.keySet(); for (Position position : aDiff.getPositions()) { Map<String, List<FeatureStructure>> annosPerUser = new HashMap<>(); ConfigurationSet cfgs = aDiff.getConfigurtionSet(position); if (cfgs.getConfigurations(WebAnnoConst.CURATION_USER).size() == 0) { // incomplete // annotations continue; } FeatureStructure mergeAnno = cfgs.getConfigurations(WebAnnoConst.CURATION_USER).get(0) .getFs(WebAnnoConst.CURATION_USER, aJCases); // Get Annotations per user in this position getAllAnnosOnPosition(aJCases, annosPerUser, users, mergeAnno); for (FeatureStructure mergeFs : annosPerUser.get(WebAnnoConst.CURATION_USER)) { // incomplete annotations if (aJCases.size() != annosPerUser.size()) { annotationsToDelete.add(mergeFs); } // agreed and not stacked else if (isAgree(mergeFs, annosPerUser)) { Type t = mergeFs.getType(); Feature sourceFeat = t.getFeatureByBaseName(WebAnnoConst.FEAT_REL_SOURCE); Feature targetFeat = t.getFeatureByBaseName(WebAnnoConst.FEAT_REL_TARGET); // Is this a relation? if (sourceFeat != null && targetFeat != null) { FeatureStructure source = mergeFs.getFeatureValue(sourceFeat); FeatureStructure target = mergeFs.getFeatureValue(targetFeat); // all span anno on this source positions Map<String, List<FeatureStructure>> sourceAnnosPerUser = new HashMap<>(); // all span anno on this target positions Map<String, List<FeatureStructure>> targetAnnosPerUser = new HashMap<>(); getAllAnnosOnPosition(aJCases, sourceAnnosPerUser, users, source); getAllAnnosOnPosition(aJCases, targetAnnosPerUser, users, target); if (isAgree(source, sourceAnnosPerUser) && isAgree(target, targetAnnosPerUser)) { slotFeaturesToReset.add(mergeFs); } else { annotationsToDelete.add(mergeFs); } } else { slotFeaturesToReset.add(mergeFs); } } // disagree or stacked annotations else { annotationsToDelete.add(mergeFs); } // remove dangling rels // setDanglingRelToDel(aJCases.get(CurationPanel.CURATION_USER), // mergeFs, annotationsToDelete); } } // remove annotations that do not agree or are a stacked ones for (FeatureStructure fs : annotationsToDelete) { if (!slotFeaturesToReset.contains(fs)) { JCas megerCas = aJCases.get(WebAnnoConst.CURATION_USER); // Check if this difference is on POS, STEM and LEMMA (so remove from the token too) Type type = fs.getType(); int fsBegin = ((AnnotationFS) fs).getBegin(); int fsEnd = ((AnnotationFS) fs).getEnd(); if (type.getName().equals(POS.class.getName())) { megerCas.removeFsFromIndexes(fs); Token t = JCasUtil.selectCovered(megerCas, Token.class, fsBegin, fsEnd).get(0); t.setPos(null); } if (type.getName().equals(Stem.class.getName())) { megerCas.removeFsFromIndexes(fs); Token t = JCasUtil.selectCovered(megerCas, Token.class, fsBegin, fsEnd).get(0); t.setStem(null); } if (type.getName().equals(Lemma.class.getName())) { megerCas.removeFsFromIndexes(fs); Token t = JCasUtil.selectCovered(megerCas, Token.class, fsBegin, fsEnd).get(0); t.setLemma(null); } megerCas.removeFsFromIndexes(fs); } } // if slot bearing annotation, clean for (FeatureStructure baseFs : slotFeaturesToReset) { for (Feature roleFeature : baseFs.getType().getFeatures()) { if (isLinkMode(baseFs, roleFeature)) { // FeatureStructure roleFs = baseFs.getFeatureValue(f); ArrayFS roleFss = (ArrayFS) WebAnnoCasUtil.getFeatureFS(baseFs, roleFeature.getShortName()); if (roleFss == null) { continue; } Map<String, ArrayFS> roleAnnosPerUser = new HashMap<>(); setAllRoleAnnosOnPosition(aJCases, roleAnnosPerUser, users, baseFs, roleFeature); List<FeatureStructure> linkFSes = new LinkedList<>( Arrays.asList(roleFss.toArray())); for (FeatureStructure roleFs : roleFss.toArray()) { if (isRoleAgree(roleFs, roleAnnosPerUser)) { for (Feature targetFeature : roleFs.getType().getFeatures()) { if (isBasicFeature(targetFeature)) { continue; } if (!targetFeature.getShortName().equals("target")) { continue; } FeatureStructure targetFs = roleFs.getFeatureValue(targetFeature); if (targetFs == null) { continue; } Map<String, List<FeatureStructure>> targetAnnosPerUser = new HashMap<>(); getAllAnnosOnPosition(aJCases, targetAnnosPerUser, users, targetFs); // do not agree on targets if (!isAgree(targetFs, targetAnnosPerUser)) { linkFSes.remove(roleFs); } } } // do not agree on some role features else { linkFSes.remove(roleFs); } } ArrayFS array = baseFs.getCAS().createArrayFS(linkFSes.size()); array.copyFromArray(linkFSes.toArray(new FeatureStructure[linkFSes.size()]), 0, 0, linkFSes.size()); baseFs.setFeatureValue(roleFeature, array); } } } return aJCases.get(WebAnnoConst.CURATION_USER); } /** * Do not check on agreement on Position and SOfa feature - already checked */ private static boolean isBasicFeature(Feature aFeature) { return aFeature.getName().equals(CAS.FEATURE_FULL_NAME_SOFA) || aFeature.toString().equals("uima.cas.AnnotationBase:sofa"); } private static void getAllAnnosOnPosition(Map<String, JCas> aJCases, Map<String, List<FeatureStructure>> aAnnosPerUser, Set<String> aUsers, FeatureStructure aMergeAnno) { for (String usr : aUsers) { if (!aAnnosPerUser.containsKey(usr)) { List<FeatureStructure> fssAtThisPosition = getFSAtPosition(aJCases, aMergeAnno, usr); aAnnosPerUser.put(usr, fssAtThisPosition); } else { List<FeatureStructure> fssAtThisPosition = getFSAtPosition(aJCases, aMergeAnno, usr); aAnnosPerUser.get(usr).addAll(fssAtThisPosition); } } } private static void setAllRoleAnnosOnPosition(Map<String, JCas> aJCases, Map<String, ArrayFS> slotAnnosPerUser, Set<String> aUsers, FeatureStructure aBaseAnno, Feature aFeature) { Type t = aBaseAnno.getType(); int begin = ((AnnotationFS) aBaseAnno).getBegin(); int end = ((AnnotationFS) aBaseAnno).getEnd(); for (String usr : aUsers) { for (FeatureStructure baseFS : CasUtil.selectCovered(aJCases.get(usr).getCas(), t, begin, end)) { // if non eqal stacked annotations with slot feature exists, get // the right one if (isSameAnno(aBaseAnno, baseFS)) { ArrayFS roleFs = (ArrayFS) WebAnnoCasUtil.getFeatureFS(baseFS, aFeature.getShortName()); slotAnnosPerUser.put(usr, roleFs); break; } } } } /** * Returns list of Annotations on this particular position (basically when stacking is allowed). */ private static List<FeatureStructure> getFSAtPosition(Map<String, JCas> aJCases, FeatureStructure fs, String aUser) { Type t = fs.getType(); int begin = ((AnnotationFS) fs).getBegin(); int end = ((AnnotationFS) fs).getEnd(); List<FeatureStructure> fssAtThisPosition = new ArrayList<>(); CasUtil.selectCovered(aJCases.get(aUser).getCas(), t, begin, end) .forEach(fss -> fssAtThisPosition.add(fss)); return fssAtThisPosition; } /** * Returns true if a span annotation agrees on all features values (including null/empty as * agreement) and no stacking is found in this position */ public static boolean isAgree(FeatureStructure aMergeFs, Map<String, List<FeatureStructure>> aAnnosPerUser) { for (String usr : aAnnosPerUser.keySet()) { boolean agree = false; for (FeatureStructure usrFs : aAnnosPerUser.get(usr)) { // same on all non slot feature values if (isSameAnno(aMergeFs, usrFs)) { if (!agree) { // this anno is the same with the others agree = true; } else if (agree) { // this is a stacked annotation return false; } } } // do not match in at least one user annotation in this position if (!agree) { return false; } } return true; } public static boolean isRoleAgree(FeatureStructure aMergeFs, Map<String, ArrayFS> aAnnosPerUser) { for (String usr : aAnnosPerUser.keySet()) { boolean agree = false; if (aAnnosPerUser.get(usr) == null) { return false; } for (FeatureStructure usrFs : aAnnosPerUser.get(usr).toArray()) { // same on all non slot feature values if (isSameAnno(aMergeFs, usrFs)) { if (!agree) { // this anno is the same with the others agree = true; } } } // do not match in at least one user annotation in this position if (!agree) { return false; } } return true; } /** * Return true if these two annotations agree on every non slot features */ public static boolean isSameAnno(FeatureStructure aFirstFS, FeatureStructure aSeconFS) { for (Feature f : getAllFeatures(aFirstFS)) { // the annotations are already in the same position if (isBasicFeature(f)) { continue; } if (!isLinkMode(aFirstFS, f)) { // check if attache type exists try { FeatureStructure attachFs1 = aFirstFS.getFeatureValue(f); FeatureStructure attachFs2 = aSeconFS.getFeatureValue(f); if (!isSameAnno(attachFs1, attachFs2)) { return false; } } catch (Exception e) { // no attach tyep -- continue } // assume null as equal if (getFeatureValue(aFirstFS, f) == null && getFeatureValue(aSeconFS, f) == null) { continue; } if (getFeatureValue(aFirstFS, f) == null && getFeatureValue(aSeconFS, f) != null) { return false; } if (getFeatureValue(aFirstFS, f) != null && getFeatureValue(aSeconFS, f) == null) { return false; } if (!getFeatureValue(aFirstFS, f).equals(getFeatureValue(aSeconFS, f))) { return false; } } } return true; } public static Feature[] getAllFeatures(FeatureStructure aFS) { Feature[] cachedSortedFeatures = new Feature[aFS.getType().getNumberOfFeatures()]; int i = 0; for (Feature f : aFS.getType().getFeatures()) { cachedSortedFeatures[i] = f; i++; } return cachedSortedFeatures; } /** * Returns true if this is slot feature */ private static boolean isLinkMode(FeatureStructure aFs, Feature aFeature) { try { ArrayFS slotFs = (ArrayFS) WebAnnoCasUtil.getFeatureFS(aFs, aFeature.getShortName()); return true; } catch (Exception e) { return false; } } /** * Get the feature value of this {@code Feature} on this annotation */ public static Object getFeatureValue(FeatureStructure aFS, Feature aFeature) { switch (aFeature.getRange().getName()) { case CAS.TYPE_NAME_STRING: return aFS.getFeatureValueAsString(aFeature); case CAS.TYPE_NAME_BOOLEAN: return aFS.getBooleanValue(aFeature); case CAS.TYPE_NAME_FLOAT: return aFS.getFloatValue(aFeature); case CAS.TYPE_NAME_INTEGER: return aFS.getIntValue(aFeature); case CAS.TYPE_NAME_BYTE: return aFS.getByteValue(aFeature); case CAS.TYPE_NAME_DOUBLE: return aFS.getDoubleValue(aFeature); case CAS.TYPE_NAME_LONG: aFS.getLongValue(aFeature); case CAS.TYPE_NAME_SHORT: aFS.getShortValue(aFeature); default: return null; // return aFS.getFeatureValue(aFeature); } } public static void setFeatureValue(FeatureStructure aFS, Feature aFeature, Object aValue) { switch (aFeature.getRange().getName()) { case CAS.TYPE_NAME_STRING: aFS.setStringValue(aFeature, aValue == null ? null : aValue.toString()); break; case CAS.TYPE_NAME_BOOLEAN: aFS.setBooleanValue(aFeature, Boolean.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_FLOAT: aFS.setFloatValue(aFeature, Float.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_INTEGER: aFS.setIntValue(aFeature, Integer.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_BYTE: aFS.setByteValue(aFeature, Byte.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_DOUBLE: aFS.setDoubleValue(aFeature, Double.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_LONG: aFS.setLongValue(aFeature, Long.valueOf(aValue.toString())); break; case CAS.TYPE_NAME_SHORT: aFS.setShortValue(aFeature, Short.valueOf(aValue.toString())); break; default: return; // return aFS.getFeatureValue(aFeature); } } public static boolean existsSameAnnoOnPosition(AnnotationFS aFs, JCas aJcas) { for (AnnotationFS annotationFS : getAnnosOnPosition(aFs, aJcas)) { if (isSameAnno(aFs, annotationFS)) { return true; } } return false; } public static List<AnnotationFS> getAnnosOnPosition(AnnotationFS aFs, JCas aJcas) { Type type = aFs.getType(); return CasUtil.selectCovered(aJcas.getCas(), type, aFs.getBegin(), aFs.getEnd()); } public static List<AnnotationFS> getRelAnnosOnPosition(AnnotationFS aFs, AnnotationFS aOriginFs, AnnotationFS aTargetFs, JCas aJcas) { Type type = aFs.getType(); Feature sourceFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_SOURCE); Feature targetFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_TARGET); return CasUtil.selectCovered(aJcas.getCas(), type, aFs.getBegin(), aFs.getEnd()).stream() .filter(fs -> fs.getFeatureValue(sourceFeat).equals(aOriginFs) && fs.getFeatureValue(targetFeat).equals(aTargetFs)) .collect(Collectors.toList()); } /** * Copy this same annotation from the user annotation to the mergeview */ public static void copySpanAnnotation(AnnotationSchemaService aAnnotationService, AnnotationLayer aAnnotationLayer, AnnotationFS aOldFs, JCas aJCas) throws AnnotationException { SpanAdapter adapter = (SpanAdapter) getAdapter(aAnnotationService, aAnnotationLayer); // Create the annotation - this also takes care of attaching to an annotation if necessary int id = adapter.add(aJCas, aOldFs.getBegin(), aOldFs.getEnd(), null, null); List<AnnotationFeature> features = aAnnotationService .listAnnotationFeature(adapter.getLayer()); // Copy the features for (AnnotationFeature feature : features) { Type oldType = adapter.getAnnotationType(aOldFs.getCAS()); Feature oldFeature = oldType.getFeatureByBaseName(feature.getName()); if (isLinkOrBasicFeatures(aOldFs, oldFeature)) { continue; } Object value = SpanAdapter.getFeatureValue(aOldFs, feature); adapter.updateFeature(aJCas, feature, id, value); } } public static void copyRelationAnnotation(AnnotationFS aOldFs, AnnotationFS asourceFS, AnnotationFS aTargetFs, JCas aJCas) { Feature[] features = getAllFeatures(aOldFs); Type type = aOldFs.getType(); Feature sourceFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_SOURCE); Feature targetFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_TARGET); AnnotationFS newFs = aJCas.getCas().createAnnotation(type, aOldFs.getBegin(), aOldFs.getEnd()); for (Feature f : features) { if (isLinkOrBasicFeatures(aOldFs, f)) { continue; } if (f.equals(sourceFeat)) { newFs.setFeatureValue(f, asourceFS); } else if (f.equals(targetFeat)) { newFs.setFeatureValue(f, aTargetFs); } else { setFeatureValue(newFs, f, getFeatureValue(aOldFs, f)); } } aJCas.addFsToIndexes(newFs); } /** * Modify existing non-stackable annotations from one of the users annotation */ public static void modifySpanAnnotation(AnnotationFS aOldFs, AnnotationFS aNewFs, JCas aJCas) { Feature[] features = getAllFeatures(aOldFs); for (Feature f : features) { if (isLinkOrBasicFeatures(aOldFs, f)) { continue; } setFeatureValue(aNewFs, f, getFeatureValue(aOldFs, f)); } aJCas.addFsToIndexes(aNewFs); } public static void modifyRelationAnnotation(AnnotationFS aOldFs, AnnotationFS aNewFs, JCas aJCas) { Feature[] features = getAllFeatures(aOldFs); Type type = aOldFs.getType(); Feature sourceFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_SOURCE); Feature targetFeat = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_TARGET); for (Feature f : features) { if (isLinkOrBasicFeatures(aOldFs, f)) { continue; } if (f.equals(sourceFeat)) { continue; } else if (f.equals(targetFeat)) { continue; } setFeatureValue(aNewFs, f, getFeatureValue(aOldFs, f)); } aJCas.addFsToIndexes(aNewFs); } public static Stream<AnnotationFS> getMergeFS(AnnotationFS aOldFs, JCas aJCas) { Type type = aOldFs.getType(); return CasUtil.selectCovered(aJCas.getCas(), type, aOldFs.getBegin(), aOldFs.getEnd()) .stream().filter(fs -> isSameAnno(fs, aOldFs)); } private static boolean isLinkOrBasicFeatures(FeatureStructure aOldFs, Feature aFeature) { if (isLinkMode(aOldFs, aFeature)) { return true; } if (isBasicFeature(aFeature)) { return true; } if (aFeature.getName().equals(CAS.FEATURE_FULL_NAME_BEGIN) || aFeature.getName().equals(CAS.FEATURE_FULL_NAME_END)) { return true; } return false; } public static void addSpanAnnotation(AnnotationSchemaService aAnnotationService, AnnotationLayer aAnnotationLayer, JCas aMergeJCas, AnnotationFS aFSClicked, boolean aAllowStacking) throws AnnotationException { if (MergeCas.existsSameAnnoOnPosition(aFSClicked, aMergeJCas)) { throw new AnnotationException( "Same Annotation exists on the mergeview. Please add it manually."); } // a) if stacking allowed add this new annotation to the mergeview List<AnnotationFS> existingAnnos = MergeCas.getAnnosOnPosition(aFSClicked, aMergeJCas); if (existingAnnos.size() == 0 || aAllowStacking) { MergeCas.copySpanAnnotation(aAnnotationService, aAnnotationLayer, aFSClicked, aMergeJCas); } // b) if stacking is not allowed, modify the existing annotation with this one else { MergeCas.modifySpanAnnotation(aFSClicked, existingAnnos.get(0), aMergeJCas); } } public static void addArcAnnotation(TypeAdapter aAdapter, JCas aJcas, Integer aAddressOriginClicked, Integer aAddressTargetClicked, String aFSArcaddress, JCas aClickedJCas, AnnotationFS aClickedFS) throws AnnotationException { AnnotationFS originFsClicked = selectByAddr(aClickedJCas, aAddressOriginClicked); AnnotationFS targetFsClicked = selectByAddr(aClickedJCas, aAddressTargetClicked); // this is a slot arc if (aFSArcaddress.contains(".")) { addSlotArcAnnotation((SpanAdapter) aAdapter, aJcas, aFSArcaddress, aClickedJCas, aClickedFS); } // normal relation annotation arc is clicked else { AnnotationLayer layer = aAdapter.getLayer(); addRelationArcAnnotation(aJcas, aClickedFS, layer.getAttachType() != null, layer.isAllowStacking(), originFsClicked, targetFsClicked); } } public static void addRelationArcAnnotation(JCas aJcas, AnnotationFS aClickedFS, boolean aIsAttachType, boolean aIsAllowStacking, AnnotationFS originFsClicked, AnnotationFS targetFsClicked) throws AnnotationException { AnnotationFS originFs; AnnotationFS targetFs; List<AnnotationFS> merges = MergeCas.getMergeFS(aClickedFS, aJcas) .collect(Collectors.toList()); List<AnnotationFS> origins = MergeCas.getMergeFS(originFsClicked, aJcas) .collect(Collectors.toList()); List<AnnotationFS> targets = MergeCas.getMergeFS(targetFsClicked, aJcas) .collect(Collectors.toList()); // check if target/source exists in the mergeview if (origins.size() == 0 || targets.size() == 0) { throw new AnnotationException("Both the source and target annotation" + " should exist on the mergeview. Please first copy/create them"); } originFs = origins.get(0); targetFs = targets.get(0); if (origins.size() > 1) { throw new AnnotationException( "Stacked sources exist in mergeview. " + "Cannot copy this relation."); } if (targets.size() > 1) { throw new AnnotationException( "Stacked targets exist in mergeview. " + "Cannot copy this relation."); } if (merges.size() > 0) { throw new AnnotationException("The annotation already exists on the mergeview. " + "Add this manually to have stacked annotations"); } // TODO: DKpro Dependency layer-> It should be done differently if (aIsAttachType) { Type type = aClickedFS.getType(); Feature sourceFeature = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_SOURCE); originFsClicked = (AnnotationFS) aClickedFS.getFeatureValue(sourceFeature); Feature targetFeature = type.getFeatureByBaseName(WebAnnoConst.FEAT_REL_TARGET); targetFsClicked = (AnnotationFS) aClickedFS.getFeatureValue(targetFeature); origins = MergeCas.getMergeFS(originFsClicked, aJcas).collect(Collectors.toList()); targets = MergeCas.getMergeFS(targetFsClicked, aJcas).collect(Collectors.toList()); originFs = origins.get(0); targetFs = targets.get(0); } List<AnnotationFS> existingAnnos = MergeCas.getRelAnnosOnPosition(aClickedFS, originFs, targetFs, aJcas); if (existingAnnos.size() == 0 || aIsAllowStacking) { MergeCas.copyRelationAnnotation(aClickedFS, originFs, targetFs, aJcas); } else { MergeCas.modifyRelationAnnotation(aClickedFS, existingAnnos.get(0), aJcas); } } public static void addSlotArcAnnotation(SpanAdapter aAdapter, JCas aJcas, String aFSArcaddress, JCas aClickedJCas, AnnotationFS aClickedFS) throws AnnotationException { List<AnnotationFS> merges = MergeCas.getMergeFS(aClickedFS, aJcas) .collect(Collectors.toList()); AnnotationFS targetFs; if (merges.size() == 0) { throw new AnnotationException( "The base annotation do not exist. Please add it first. "); } AnnotationFS mergeFs = merges.get(0); Integer fiIndex = Integer.parseInt(aFSArcaddress.split("\\.")[1]); Integer liIndex = Integer.parseInt(aFSArcaddress.split("\\.")[2]); AnnotationFeature slotFeature = null; LinkWithRoleModel linkRole = null; int fi = 0; f: for (AnnotationFeature feat : aAdapter.listFeatures()) { if (MultiValueMode.ARRAY.equals(feat.getMultiValueMode()) && LinkMode.WITH_ROLE.equals(feat.getLinkMode())) { List<LinkWithRoleModel> links = getFeature(aClickedFS, feat); for (int li = 0; li < links.size(); li++) { LinkWithRoleModel link = links.get(li); if (fi == fiIndex && li == liIndex) { slotFeature = feat; List<AnnotationFS> targets = checkAndGetTargets(aJcas, aClickedJCas, selectByAddr(aClickedJCas, link.targetAddr)); targetFs = targets.get(0); link.targetAddr = getAddr(targetFs); linkRole = link; break f; } } } fi++; } List<LinkWithRoleModel> links = getFeature(mergeFs, slotFeature); LinkWithRoleModel duplicateLink = null; // for (LinkWithRoleModel lr : links) { if (lr.targetAddr == linkRole.targetAddr) { duplicateLink = lr; break; } } links.add(linkRole); links.remove(duplicateLink); setFeature(mergeFs, slotFeature, links); } private static List<AnnotationFS> checkAndGetTargets(JCas aJcas, JCas aClickedJCas, AnnotationFS aOldTraget) throws AnnotationException { List<AnnotationFS> targets = MergeCas.getMergeFS(aOldTraget, aJcas) .collect(Collectors.toList()); if (targets.size() == 0) { throw new AnnotationException( "This target annotation do not exist." + " Copy or create the target first "); } if (targets.size() > 1) { throw new AnnotationException("There are multiple targets on the mergeview." + " Can not copy this slot annotation."); } return targets; } }