/*
* 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.brat.adapter;
import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.getAddr;
import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.WebAnnoCasUtil.selectByAddr;
import static java.util.Arrays.asList;
import static org.apache.uima.fit.util.CasUtil.getType;
import static org.apache.uima.fit.util.CasUtil.selectCovered;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.jcas.JCas;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.adapter.ArcAdapter;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.coloring.ColoringStrategy;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.model.AnnotatorState;
import de.tudarmstadt.ukp.clarin.webanno.api.annotation.util.TypeUtil;
import de.tudarmstadt.ukp.clarin.webanno.brat.message.GetDocumentResponse;
import de.tudarmstadt.ukp.clarin.webanno.brat.render.model.Argument;
import de.tudarmstadt.ukp.clarin.webanno.brat.render.model.Comment;
import de.tudarmstadt.ukp.clarin.webanno.brat.render.model.Relation;
import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature;
/**
* A class that is used to create Brat Arc to CAS relations and vice-versa
*/
public class BratArcRenderer
implements TypeRenderer
{
private final Logger log = LoggerFactory.getLogger(getClass());
private ArcAdapter typeAdapter;
public BratArcRenderer(ArcAdapter aTypeAdapter)
{
typeAdapter = aTypeAdapter;
}
/**
* Add arc annotations from the CAS, which is controlled by the window size, to the brat
* response {@link GetDocumentResponse}
*
* @param aJcas
* The JCAS object containing annotations
* @param aResponse
* A brat response containing annotations in brat protocol
* @param aBratAnnotatorModel
* Data model for brat annotations
* @param aColoringStrategy
* the coloring strategy to render this layer
*/
@Override
public void render(final JCas aJcas, List<AnnotationFeature> aFeatures,
GetDocumentResponse aResponse, AnnotatorState aBratAnnotatorModel,
ColoringStrategy aColoringStrategy)
{
Type type = getType(aJcas.getCas(), typeAdapter.getAnnotationTypeName());
int windowBegin = aBratAnnotatorModel.getWindowBeginOffset();
int windowEnd = aBratAnnotatorModel.getWindowEndOffset();
Feature dependentFeature = type.getFeatureByBaseName(typeAdapter.getTargetFeatureName());
Feature governorFeature = type.getFeatureByBaseName(typeAdapter.getSourceFeatureName());
Type spanType = getType(aJcas.getCas(), typeAdapter.getAttachTypeName());
Feature arcSpanFeature = spanType.getFeatureByBaseName(typeAdapter.getAttachFeatureName());
FeatureStructure dependentFs;
FeatureStructure governorFs;
Map<Integer, Set<Integer>> relationLinks = getRelationLinks(aJcas, windowBegin, windowEnd,
type, dependentFeature, governorFeature, arcSpanFeature);
// if this is a governor for more than one dependent, avoid duplicate yield
List<Integer> yieldDeps = new ArrayList<>();
for (AnnotationFS fs : selectCovered(aJcas.getCas(), type, windowBegin, windowEnd)) {
if (typeAdapter.getAttachFeatureName() != null) {
dependentFs = fs.getFeatureValue(dependentFeature).getFeatureValue(arcSpanFeature);
governorFs = fs.getFeatureValue(governorFeature).getFeatureValue(arcSpanFeature);
}
else {
dependentFs = fs.getFeatureValue(dependentFeature);
governorFs = fs.getFeatureValue(governorFeature);
}
String bratLabelText = TypeUtil.getUiLabelText(typeAdapter, fs, aFeatures);
String bratTypeName = TypeUtil.getUiTypeName(typeAdapter);
String color = aColoringStrategy.getColor(fs, bratLabelText);
if (dependentFs == null || governorFs == null) {
log.warn("Relation [" + typeAdapter.getLayer().getName() + "] with id ["
+ getAddr(fs) + "] has loose ends - cannot render");
if (typeAdapter.getAttachFeatureName() != null) {
log.warn("Relation [" + typeAdapter.getLayer().getName()
+ "] attached to feature [" + typeAdapter.getAttachFeatureName() + "]");
}
log.warn("Dependent: " + dependentFs);
log.warn("Governor: " + governorFs);
continue;
}
List<Argument> argumentList = getArgument(governorFs, dependentFs);
aResponse.addRelation(new Relation(getAddr(fs), bratTypeName, argumentList,
bratLabelText, color));
// Render errors if required features are missing
renderRequiredFeatureErrors(aFeatures, fs, aResponse);
if (relationLinks.keySet().contains(getAddr(governorFs))
&& !yieldDeps.contains(getAddr(governorFs))) {
yieldDeps.add(getAddr(governorFs));
// sort the annotations (begin, end)
List<Integer> sortedDepFs = new ArrayList<>(relationLinks.get(getAddr(governorFs)));
Collections.sort(sortedDepFs, new Comparator<Integer>()
{
@Override
public int compare(Integer arg0, Integer arg1)
{
return selectByAddr(aJcas, arg0).getBegin()
- selectByAddr(aJcas, arg1).getBegin();
}
});
StringBuffer cm = getYieldMessage(aJcas, sortedDepFs);
aResponse.addComment(new Comment(getAddr(governorFs), "Yield of relation", cm
.toString()));
}
}
}
/**
* Argument lists for the arc annotation
*/
private List<Argument> getArgument(FeatureStructure aGovernorFs, FeatureStructure aDependentFs)
{
return asList(new Argument("Arg1", getAddr(aGovernorFs)), new Argument("Arg2",
getAddr(aDependentFs)));
}
/**
* The relations yield message
*/
private StringBuffer getYieldMessage(JCas aJCas, List<Integer> sortedDepFs)
{
StringBuffer cm = new StringBuffer();
int end = -1;
for (Integer depFs : sortedDepFs) {
if (end == -1) {
cm.append(selectByAddr(aJCas, depFs).getCoveredText());
end = selectByAddr(aJCas, depFs).getEnd();
}
// if no space between token and punct
else if (end==selectByAddr(aJCas, depFs).getBegin()){
cm.append(selectByAddr(aJCas, depFs).getCoveredText());
end = selectByAddr(aJCas, depFs).getEnd();
}
else if (end + 1 != selectByAddr(aJCas, depFs).getBegin()) {
cm.append(" ... " + selectByAddr(aJCas, depFs).getCoveredText());
end = selectByAddr(aJCas, depFs).getEnd();
}
else {
cm.append(" " + selectByAddr(aJCas, depFs).getCoveredText());
end = selectByAddr(aJCas, depFs).getEnd();
}
}
return cm;
}
/**
* Get relation links to display in relation yield
*/
private Map<Integer, Set<Integer>> getRelationLinks(JCas aJcas, int aWindowBegin,
int aWindowEnd, Type type, Feature dependentFeature, Feature governorFeature,
Feature arcSpanFeature)
{
FeatureStructure dependentFs;
FeatureStructure governorFs;
Map<Integer, Set<Integer>> relations = new ConcurrentHashMap<>();
for (AnnotationFS fs : selectCovered(aJcas.getCas(), type, aWindowBegin, aWindowEnd)) {
if (typeAdapter.getAttachFeatureName() != null) {
dependentFs = fs.getFeatureValue(dependentFeature).getFeatureValue(arcSpanFeature);
governorFs = fs.getFeatureValue(governorFeature).getFeatureValue(arcSpanFeature);
}
else {
dependentFs = fs.getFeatureValue(dependentFeature);
governorFs = fs.getFeatureValue(governorFeature);
}
if (dependentFs == null || governorFs == null) {
log.warn("Relation [" + typeAdapter.getLayer().getName() + "] with id ["
+ getAddr(fs) + "] has loose ends - cannot render.");
continue;
}
Set<Integer> links = relations.get(getAddr(governorFs));
if (links == null) {
links = new ConcurrentSkipListSet<>();
}
links.add(getAddr(dependentFs));
relations.put(getAddr(governorFs), links);
}
// Update other subsequent links
for (int i = 0; i < relations.keySet().size(); i++) {
for (Integer fs : relations.keySet()) {
updateLinks(relations, fs);
}
}
// to start displaying the text from the governor, include it
for (Integer fs : relations.keySet()) {
relations.get(fs).add(fs);
}
return relations;
}
private void updateLinks(Map<Integer, Set<Integer>> aRelLinks, Integer aGov)
{
for (Integer dep : aRelLinks.get(aGov)) {
if (aRelLinks.containsKey(dep) && !aRelLinks.get(aGov).containsAll(aRelLinks.get(dep))) {
aRelLinks.get(aGov).addAll(aRelLinks.get(dep));
updateLinks(aRelLinks, dep);
}
else {
continue;
}
}
}
}