Java tutorial
/******************************************************************************* * Copyright 2015 * Ubiquitous Knowledge Processing (UKP) Lab and FG Language Technology * Technische Universitt 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.annotation.component; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.getAddr; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.getFeature; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.getLastSentenceAddressInDisplayWindow; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.getSentenceBeginAddress; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.getSentenceNumber; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.isSame; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.selectAt; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.selectByAddr; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.selectSentenceAt; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil.setFeature; import static de.tudarmstadt.ukp.clarin.webanno.brat.controller.TypeUtil.getAdapter; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.NoResultException; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.uima.UIMAException; import org.apache.uima.cas.CAS; import org.apache.uima.cas.CASRuntimeException; 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.jcas.JCas; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.form.AjaxFormValidatingBehavior; import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.AbstractTextComponent; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.RefreshingView; import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.codehaus.plexus.util.StringUtils; import com.googlecode.wicket.jquery.core.Options; import com.googlecode.wicket.jquery.core.template.IJQueryTemplate; import com.googlecode.wicket.jquery.ui.widget.tooltip.TooltipBehavior; import com.googlecode.wicket.kendo.ui.form.NumberTextField; import com.googlecode.wicket.kendo.ui.form.TextField; import com.googlecode.wicket.kendo.ui.form.combobox.ComboBox; import de.tudarmstadt.ukp.clarin.webanno.api.AnnotationService; import de.tudarmstadt.ukp.clarin.webanno.api.RepositoryService; import de.tudarmstadt.ukp.clarin.webanno.api.WebAnnoConst; import de.tudarmstadt.ukp.clarin.webanno.brat.annotation.BratAnnotatorModel; import de.tudarmstadt.ukp.clarin.webanno.brat.annotation.command.Selection; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.ArcAdapter; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAjaxCasUtil; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.BratAnnotationException; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.ChainAdapter; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.SpanAdapter; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.TypeAdapter; import de.tudarmstadt.ukp.clarin.webanno.brat.controller.TypeUtil; import de.tudarmstadt.ukp.clarin.webanno.brat.display.model.VID; import de.tudarmstadt.ukp.clarin.webanno.constraints.evaluator.Evaluator; import de.tudarmstadt.ukp.clarin.webanno.constraints.evaluator.PossibleValue; import de.tudarmstadt.ukp.clarin.webanno.constraints.evaluator.RulesIndicator; import de.tudarmstadt.ukp.clarin.webanno.constraints.evaluator.ValuesGenerator; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.Mode; import de.tudarmstadt.ukp.clarin.webanno.model.MultiValueMode; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState; import de.tudarmstadt.ukp.clarin.webanno.model.Tag; import de.tudarmstadt.ukp.clarin.webanno.model.TagSet; import de.tudarmstadt.ukp.clarin.webanno.support.DefaultFocusBehavior; import de.tudarmstadt.ukp.clarin.webanno.support.DefaultFocusBehavior2; import de.tudarmstadt.ukp.clarin.webanno.support.DescriptionTooltipBehavior; import de.tudarmstadt.ukp.dkpro.core.api.lexmorph.type.pos.POS; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.dependency.Dependency; /** * Annotation Detail Editor Panel. * */ public class AnnotationDetailEditorPanel extends Panel { private static final long serialVersionUID = 7324241992353693848L; private static final Log LOG = LogFactory.getLog(AnnotationDetailEditorPanel.class); @SpringBean(name = "documentRepository") private RepositoryService repository; @SpringBean(name = "annotationService") private AnnotationService annotationService; private AnnotationFeatureForm annotationFeatureForm; private Label selectedTextLabel; private CheckBox forwardAnnotationCheck; private AjaxButton deleteButton; private AjaxButton reverseButton; private LayerSelector layer; private TextField<String> forwardAnnotationText; private Label selectedAnnotationLayer; private ModalWindow deleteModal; private List<AnnotationLayer> annotationLayers = new ArrayList<AnnotationLayer>(); private List<FeatureModel> featureModels; BratAnnotatorModel bModel; /** *Function to return tooltip using jquery *Docs for the JQuery tooltip widget that we configure below: *https://api.jqueryui.com/tooltip/ */ private final String functionForTooltip = "function() { return " + "'<div class=\"tooltip-title\">'+($(this).text() " + "? $(this).text() : 'no title')+'</div>" + "<div class=\"tooltip-content tooltip-pre\">'+($(this).attr('title') " + "? $(this).attr('title') : 'no description' )+'</div>' }"; public AnnotationDetailEditorPanel(String id, IModel<BratAnnotatorModel> aModel) { super(id, aModel); bModel = aModel.getObject(); annotationFeatureForm = new AnnotationFeatureForm("annotationFeatureForm", aModel.getObject()) { private static final long serialVersionUID = 8081614428845920047L; @Override protected void onConfigure() { super.onConfigure(); // Avoid reversing in read-only layers setEnabled(bModel.getDocument() != null && !isAnnotationFinished()); } }; annotationFeatureForm.setOutputMarkupId(true); annotationFeatureForm.add(new AjaxFormValidatingBehavior(annotationFeatureForm, "onsubmit") { private static final long serialVersionUID = -5642108496844056023L; @Override protected void onSubmit(AjaxRequestTarget aTarget) { try { actionAnnotate(aTarget, bModel, false); } catch (UIMAException | ClassNotFoundException | IOException | BratAnnotationException e) { error(e.getMessage()); } } }); add(annotationFeatureForm); } public boolean isAnnotationFinished() { if (bModel.getMode().equals(Mode.CURATION)) { return bModel.getDocument().getState().equals(SourceDocumentState.CURATION_FINISHED); } else { return repository.getAnnotationDocument(bModel.getDocument(), bModel.getUser()).getState() .equals(AnnotationDocumentState.FINISHED); } } private class AnnotationFeatureForm extends Form<BratAnnotatorModel> { private static final long serialVersionUID = 3635145598405490893L; private WebMarkupContainer featureEditorsContainer; public AnnotationFeatureForm(String id, BratAnnotatorModel aBModel) { super(id, new CompoundPropertyModel<BratAnnotatorModel>(aBModel)); featureModels = new ArrayList<>(); add(forwardAnnotationCheck = new CheckBox("forwardAnnotation") { private static final long serialVersionUID = 8908304272310098353L; @Override protected void onConfigure() { super.onConfigure(); setEnabled(isForwardable()); updateForwardAnnotation(bModel); } }); forwardAnnotationCheck.add(new AjaxFormComponentUpdatingBehavior("onchange") { private static final long serialVersionUID = 5179816588460867471L; @Override protected void onUpdate(AjaxRequestTarget aTarget) { updateForwardAnnotation(getModelObject()); } }); forwardAnnotationCheck.setOutputMarkupId(true); add(deleteButton = new AjaxButton("delete") { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { super.onConfigure(); setVisible(bModel.getSelection().getAnnotation().isSet()); // Avoid deleting in read-only layers setEnabled(bModel.getSelectedAnnotationLayer() != null && !bModel.getSelectedAnnotationLayer().isReadonly()); } @Override public void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { try { JCas jCas = getCas(bModel); AnnotationFS fs = selectByAddr(jCas, bModel.getSelection().getAnnotation().getId()); AnnotationLayer layer = bModel.getSelectedAnnotationLayer(); TypeAdapter adapter = getAdapter(annotationService, layer); if (adapter instanceof SpanAdapter && getAttachedRels(jCas, fs, layer).size() > 0) { deleteModal.setTitle( "Are you sure you like to delete all attached relations to this span annotation?"); deleteModal.setContent(new DeleteOrReplaceAnnotationModalPanel( deleteModal.getContentId(), bModel, deleteModal, AnnotationDetailEditorPanel.this, bModel.getSelectedAnnotationLayer(), false)); deleteModal.show(aTarget); } else { actionDelete(aTarget, bModel); } } catch (UIMAException | ClassNotFoundException | IOException | CASRuntimeException | BratAnnotationException e) { error(e.getMessage()); } } }); add(reverseButton = new AjaxButton("reverse") { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { super.onConfigure(); setVisible(bModel.getSelection().isRelationAnno() && bModel.getSelection().getAnnotation().isSet() && bModel.getSelectedAnnotationLayer().getType().equals(WebAnnoConst.RELATION_TYPE)); // Avoid reversing in read-only layers setEnabled(bModel.getSelectedAnnotationLayer() != null && !bModel.getSelectedAnnotationLayer().isReadonly()); } @Override public void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { aTarget.addChildren(getPage(), FeedbackPanel.class); try { actionReverse(aTarget, bModel); } catch (BratAnnotationException e) { aTarget.prependJavaScript("alert('" + e.getMessage() + "')"); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (UIMAException e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (Exception e) { error(e.getMessage()); LOG.error(e.getMessage(), e); } } }); reverseButton.setOutputMarkupPlaceholderTag(true); add(new AjaxButton("clear") { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { super.onConfigure(); setVisible(bModel.getSelection().getAnnotation().isSet()); } @Override public void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { aTarget.addChildren(getPage(), FeedbackPanel.class); try { actionClear(aTarget, bModel); } catch (UIMAException e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (Exception e) { error(e.getMessage()); LOG.error(e.getMessage(), e); } } }); add(layer = new LayerSelector("defaultAnnotationLayer", annotationLayers)); RefreshingView<FeatureModel> featureValues = new FeatureEditorPanelContent("featureValues"); featureEditorsContainer = new WebMarkupContainer("featureEditorsContainer") { private static final long serialVersionUID = 8908304272310098353L; @Override protected void onConfigure() { super.onConfigure(); setVisible(!featureModels.isEmpty() && bModel.getSelection().getAnnotation().isSet()); } }; // Add placeholder since wmc might start out invisible. Without the placeholder we // cannot make it visible in an AJAX call featureEditorsContainer.setOutputMarkupPlaceholderTag(true); featureEditorsContainer.setOutputMarkupId(true); forwardAnnotationText = new TextField<String>("forwardAnno"); forwardAnnotationText.setOutputMarkupId(true); forwardAnnotationText.add(new AjaxFormComponentUpdatingBehavior("onkeyup") { private static final long serialVersionUID = 4554834769861958396L; @Override protected void onUpdate(AjaxRequestTarget aTarget) { featureModels.get(0).value = getKeyBindValue(forwardAnnotationText.getModelObject(), getBindTags()); aTarget.add(forwardAnnotationText); aTarget.add(featureValues.get(0)); } }); forwardAnnotationText.setOutputMarkupId(true); forwardAnnotationText.add(new AttributeAppender("style", "opacity:0", ";")); // forwardAnno.add(new AttributeAppender("style", "filter:alpha(opacity=0)", ";")); featureEditorsContainer.add(forwardAnnotationText); featureEditorsContainer.add(featureValues); // the selected text for annotationa selectedTextLabel = new Label("selectedText", PropertyModel.of(getModelObject(), "selection.text")); selectedTextLabel.setOutputMarkupId(true); featureEditorsContainer.add(selectedTextLabel); featureEditorsContainer.add(new Label("layerName", "Layer") { private static final long serialVersionUID = 6084341323607243784L; @Override protected void onConfigure() { super.onConfigure(); setVisible(bModel.getPreferences().isDefaultLayer()); } }); // the annotation layer for the selected annotation selectedAnnotationLayer = new Label("selectedAnnotationLayer", new Model<String>()) { private static final long serialVersionUID = 4059460390544343324L; @Override protected void onConfigure() { super.onConfigure(); setOutputMarkupId(true); setVisible(bModel.getPreferences().isDefaultLayer()); } }; selectedAnnotationLayer.setOutputMarkupId(true); featureEditorsContainer.add(selectedAnnotationLayer); add(featureEditorsContainer); add(deleteModal = new ModalWindow("yesNoModal")); deleteModal.setOutputMarkupId(true); deleteModal.setInitialWidth(600); deleteModal.setInitialHeight(50); deleteModal.setResizable(true); deleteModal.setWidthUnit("px"); deleteModal.setHeightUnit("px"); deleteModal.setTitle("Are you sure you want to delete the existing annotation?"); } } public void actionAnnotate(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel, boolean aIsForwarded) throws UIMAException, ClassNotFoundException, IOException, BratAnnotationException { // If there is no annotation yet, create one. During creation, the adapter // may notice that it would create a duplicate and return the address of // an existing annotation instead of a new one. JCas jCas = getCas(aBModel); actionAnnotate(aTarget, aBModel, jCas, aIsForwarded); } public void actionAnnotate(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel, JCas jCas, boolean aIsForwarded) throws UIMAException, ClassNotFoundException, IOException, BratAnnotationException { if (aBModel.getSelectedAnnotationLayer() == null) { error("No layer is selected. First select a layer."); aTarget.addChildren(getPage(), FeedbackPanel.class); return; } if (aBModel.getSelectedAnnotationLayer().isReadonly()) { error("Layer is not editable."); aTarget.addChildren(getPage(), FeedbackPanel.class); return; } // Verify if input is valid according to tagset for (int i = 0; i < featureModels.size(); i++) { AnnotationFeature feature = featureModels.get(i).feature; if (CAS.TYPE_NAME_STRING.equals(feature.getType())) { String value = (String) featureModels.get(i).value; // Check if tag is necessary, set, and correct if (feature.getTagset() != null && !feature.getTagset().isCreateTag() && !annotationService.existsTag(value, feature.getTagset())) { error("[" + value + "] is not in the tag list. Please choose form the existing tags"); return; } } } TypeAdapter adapter = getAdapter(annotationService, aBModel.getSelectedAnnotationLayer()); Selection selection = aBModel.getSelection(); if (selection.getAnnotation().isNotSet()) { if (bModel.getSelection().isRelationAnno()) { AnnotationFS originFs = selectByAddr(jCas, selection.getOrigin()); AnnotationFS targetFs = selectByAddr(jCas, selection.getTarget()); if (adapter instanceof ArcAdapter) { Sentence sentence = selectSentenceAt(jCas, bModel.getSentenceBeginOffset(), bModel.getSentenceEndOffset()); int start = sentence.getBegin(); int end = selectByAddr(jCas, Sentence.class, getLastSentenceAddressInDisplayWindow(jCas, getAddr(sentence), bModel.getPreferences().getWindowSize())).getEnd(); AnnotationFS arc = ((ArcAdapter) adapter).add(originFs, targetFs, jCas, start, end, null, null); selection.setAnnotation(new VID(getAddr(arc))); } else { selection.setAnnotation( new VID(((ChainAdapter) adapter).addArc(jCas, originFs, targetFs, null, null))); } selection.setBegin(originFs.getBegin()); } else if (adapter instanceof SpanAdapter) { for (FeatureModel fm : featureModels) { if (((SpanAdapter) adapter).getSpan(jCas, selection.getBegin(), selection.getEnd(), fm.feature, null) != null) { actionClear(aTarget, bModel); throw new BratAnnotationException("Cannot create another annotation of layer [" + "" + bModel.getSelectedAnnotationLayer().getUiName() + " at this" + " location - stacking is not enabled for this layer."); } } selection.setAnnotation(new VID( ((SpanAdapter) adapter).add(jCas, selection.getBegin(), selection.getEnd(), null, null))); } else { for (FeatureModel fm : featureModels) { if (((ChainAdapter) adapter).getSpan(jCas, selection.getBegin(), selection.getEnd(), fm.feature, null) != null) { actionClear(aTarget, bModel); throw new BratAnnotationException("Cannot create another annotation of layer [" + "" + bModel.getSelectedAnnotationLayer().getUiName() + " at this" + " location - stacking is not enabled for this layer."); } } selection.setAnnotation(new VID(((ChainAdapter) adapter).addSpan(jCas, selection.getBegin(), selection.getEnd(), null, null))); } } // Set feature values List<AnnotationFeature> features = new ArrayList<AnnotationFeature>(); for (FeatureModel fm : featureModels) { features.add(fm.feature); // For string features with extensible tagsets, extend the tagset if (CAS.TYPE_NAME_STRING.equals(fm.feature.getType())) { String value = (String) fm.value; if (fm.feature.getTagset() != null && fm.feature.getTagset().isCreateTag() && !annotationService.existsTag(value, fm.feature.getTagset())) { // Persist only if the feature value is actually set if (value != null) { Tag selectedTag = new Tag(); selectedTag.setName(value); selectedTag.setTagSet(fm.feature.getTagset()); annotationService.createTag(selectedTag, aBModel.getUser()); } } } adapter.updateFeature(jCas, fm.feature, aBModel.getSelection().getAnnotation().getId(), fm.value); } // Update progress information int sentenceNumber = getSentenceNumber(jCas, aBModel.getSelection().getBegin()); aBModel.setSentenceNumber(sentenceNumber); aBModel.getDocument().setSentenceAccessed(sentenceNumber); // persist changes repository.writeCas(aBModel.getMode(), aBModel.getDocument(), aBModel.getUser(), jCas); if (aBModel.getPreferences().isScrollPage()) { autoScroll(jCas, aBModel); } if (bModel.getSelection().isRelationAnno()) { aBModel.setRememberedArcLayer(aBModel.getSelectedAnnotationLayer()); aBModel.setRememberedArcFeatures(featureModels); } else { aBModel.setRememberedSpanLayer(aBModel.getSelectedAnnotationLayer()); aBModel.setRememberedSpanFeatures(featureModels); } aBModel.getSelection().setAnnotate(true); if (aBModel.getSelection().getAnnotation().isSet()) { String bratLabelText = TypeUtil.getBratLabelText(adapter, selectByAddr(jCas, aBModel.getSelection().getAnnotation().getId()), features); info(generateMessage(aBModel.getSelectedAnnotationLayer(), bratLabelText, false)); } if (aBModel.isForwardAnnotation() && !aIsForwarded && featureModels.get(0).value != null) { onAutoForward(aTarget, aBModel); } onAnnotate(aTarget, aBModel, selection.getBegin(), selection.getEnd()); onChange(aTarget, aBModel); } public void actionDelete(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel) throws IOException, UIMAException, ClassNotFoundException, CASRuntimeException, BratAnnotationException { JCas jCas = getCas(aBModel); AnnotationFS fs = selectByAddr(jCas, aBModel.getSelection().getAnnotation().getId()); // TODO We assume here that the selected annotation layer corresponds to the type of the // FS to be deleted. It would be more robust if we could get the layer from the FS itself. AnnotationLayer layer = aBModel.getSelectedAnnotationLayer(); TypeAdapter adapter = getAdapter(annotationService, layer); // == DELETE ATTACHED RELATIONS == // If the deleted FS is a span, we must delete all relations that // point to it directly or indirectly via the attachFeature. // // NOTE: It is important that this happens before UNATTACH SPANS since the attach feature // is no longer set after UNATTACH SPANS! if (adapter instanceof SpanAdapter) { for (AnnotationFS attachedFs : getAttachedRels(jCas, fs, layer)) { jCas.getCas().removeFsFromIndexes(attachedFs); info("The attached annotation for relation type [" + annotationService .getLayer(attachedFs.getType().getName(), bModel.getProject()).getUiName() + "] is deleted"); } } // == DELETE ATTACHED SPANS == // This case is currently not implemented because WebAnno currently does not allow to // create spans that attach to other spans. The only span type for which this is relevant // is the Token type which cannot be deleted. // == UNATTACH SPANS == // If the deleted FS is a span that is attached to another span, the // attachFeature in the other span must be set to null. Typical example: POS is deleted, so // the pos feature of Token must be set to null. This is a quick case, because we only need // to look at span annotations that have the same offsets as the FS to be deleted. if (adapter instanceof SpanAdapter && layer.getAttachType() != null) { Type spanType = CasUtil.getType(jCas.getCas(), layer.getAttachType().getName()); Feature attachFeature = spanType.getFeatureByBaseName(layer.getAttachFeature().getName()); for (AnnotationFS attachedFs : selectAt(jCas.getCas(), spanType, fs.getBegin(), fs.getEnd())) { if (isSame(attachedFs.getFeatureValue(attachFeature), fs)) { attachedFs.setFeatureValue(attachFeature, null); LOG.debug("Unattached [" + attachFeature.getShortName() + "] on annotation [" + getAddr(attachedFs) + "]"); } } } // == CLEAN UP LINK FEATURES == // If the deleted FS is a span that is the target of a link feature, we must unset that // link and delete the slot if it is a multi-valued link. Here, we have to scan all // annotations from layers that have link features that could point to the FS // to be deleted: the link feature must be the type of the FS or it must be generic. if (adapter instanceof SpanAdapter) { for (AnnotationFeature linkFeature : annotationService.listAttachedLinkFeatures(layer)) { Type linkType = CasUtil.getType(jCas.getCas(), linkFeature.getLayer().getName()); for (AnnotationFS linkFS : CasUtil.select(jCas.getCas(), linkType)) { List<LinkWithRoleModel> links = getFeature(linkFS, linkFeature); Iterator<LinkWithRoleModel> i = links.iterator(); boolean modified = false; while (i.hasNext()) { LinkWithRoleModel link = i.next(); if (link.targetAddr == getAddr(fs)) { i.remove(); LOG.debug("Cleared slot [" + link.role + "] in feature [" + linkFeature.getName() + "] on annotation [" + getAddr(linkFS) + "]"); modified = true; } } if (modified) { setFeature(linkFS, linkFeature, links); } } } } // If the deleted FS is a relation, we don't have to do anything. Nothing can point to a // relation. if (adapter instanceof ArcAdapter) { // Do nothing ;) } // Actually delete annotation adapter.delete(jCas, aBModel.getSelection().getAnnotation()); // Store CAS again repository.writeCas(aBModel.getMode(), aBModel.getDocument(), aBModel.getUser(), jCas); // Update progress information int sentenceNumber = getSentenceNumber(jCas, aBModel.getSelection().getBegin()); aBModel.setSentenceNumber(sentenceNumber); aBModel.getDocument().setSentenceAccessed(sentenceNumber); // Auto-scroll if (aBModel.getPreferences().isScrollPage()) { autoScroll(jCas, aBModel); } aBModel.setRememberedSpanLayer(aBModel.getSelectedAnnotationLayer()); aBModel.getSelection().setAnnotate(false); info(generateMessage(aBModel.getSelectedAnnotationLayer(), null, true)); // A hack to remember the visual DropDown display value aBModel.setRememberedSpanLayer(aBModel.getSelectedAnnotationLayer()); aBModel.setRememberedSpanFeatures(featureModels); aBModel.getSelection().clear(); // after delete will follow annotation bModel.getSelection().setAnnotate(true); aTarget.add(annotationFeatureForm); aTarget.add(deleteButton); aTarget.add(reverseButton); onChange(aTarget, aBModel); onDelete(aTarget, aBModel, fs); } private void actionReverse(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel) throws IOException, UIMAException, ClassNotFoundException, BratAnnotationException { JCas jCas; jCas = getCas(aBModel); AnnotationFS idFs = selectByAddr(jCas, aBModel.getSelection().getAnnotation().getId()); jCas.removeFsFromIndexes(idFs); AnnotationFS originFs = selectByAddr(jCas, aBModel.getSelection().getOrigin()); AnnotationFS targetFs = selectByAddr(jCas, aBModel.getSelection().getTarget()); TypeAdapter adapter = getAdapter(annotationService, aBModel.getSelectedAnnotationLayer()); Sentence sentence = selectSentenceAt(jCas, bModel.getSentenceBeginOffset(), bModel.getSentenceEndOffset()); int start = sentence.getBegin(); int end = selectByAddr(jCas, Sentence.class, getLastSentenceAddressInDisplayWindow(jCas, getAddr(sentence), bModel.getPreferences().getWindowSize())).getEnd(); if (adapter instanceof ArcAdapter) { for (FeatureModel fm : featureModels) { AnnotationFS arc = ((ArcAdapter) adapter).add(targetFs, originFs, jCas, start, end, fm.feature, fm.value); aBModel.getSelection().setAnnotation(new VID(getAddr(arc))); } } else { error("chains cannot be reversed"); return; } // persist changes repository.writeCas(aBModel.getMode(), aBModel.getDocument(), aBModel.getUser(), jCas); int sentenceNumber = getSentenceNumber(jCas, originFs.getBegin()); aBModel.setSentenceNumber(sentenceNumber); aBModel.getDocument().setSentenceAccessed(sentenceNumber); if (aBModel.getPreferences().isScrollPage()) { autoScroll(jCas, aBModel); } info("The arc has been reversed"); aBModel.setRememberedArcLayer(aBModel.getSelectedAnnotationLayer()); aBModel.setRememberedArcFeatures(featureModels); // in case the user re-reverse it int temp = aBModel.getSelection().getOrigin(); aBModel.getSelection().setOrigin(aBModel.getSelection().getTarget()); aBModel.getSelection().setTarget(temp); onChange(aTarget, aBModel); } public void actionClear(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel) throws IOException, UIMAException, ClassNotFoundException, BratAnnotationException { aBModel.getSelection().clear(); aTarget.add(annotationFeatureForm); onChange(aTarget, aBModel); } public JCas getCas(BratAnnotatorModel aBModel) throws UIMAException, IOException, ClassNotFoundException { if (aBModel.getMode().equals(Mode.ANNOTATION) || aBModel.getMode().equals(Mode.AUTOMATION) || aBModel.getMode().equals(Mode.CORRECTION) || aBModel.getMode().equals(Mode.CORRECTION_MERGE)) { return repository.readAnnotationCas(aBModel.getDocument(), aBModel.getUser()); } else { return repository.readCurationCas(aBModel.getDocument()); } } private void autoScroll(JCas jCas, BratAnnotatorModel aBModel) { int address = getAddr( selectSentenceAt(jCas, aBModel.getSentenceBeginOffset(), aBModel.getSentenceEndOffset())); aBModel.setSentenceAddress(getSentenceBeginAddress(jCas, address, aBModel.getSelection().getBegin(), aBModel.getProject(), aBModel.getDocument(), aBModel.getPreferences().getWindowSize())); Sentence sentence = selectByAddr(jCas, Sentence.class, aBModel.getSentenceAddress()); aBModel.setSentenceBeginOffset(sentence.getBegin()); aBModel.setSentenceEndOffset(sentence.getEnd()); Sentence firstSentence = selectSentenceAt(jCas, aBModel.getSentenceBeginOffset(), aBModel.getSentenceEndOffset()); int lastAddressInPage = getLastSentenceAddressInDisplayWindow(jCas, getAddr(firstSentence), aBModel.getPreferences().getWindowSize()); // the last sentence address in the display window Sentence lastSentenceInPage = (Sentence) selectByAddr(jCas, FeatureStructure.class, lastAddressInPage); aBModel.setFSN(BratAjaxCasUtil.getSentenceNumber(jCas, firstSentence.getBegin())); aBModel.setLSN(BratAjaxCasUtil.getSentenceNumber(jCas, lastSentenceInPage.getBegin())); } @SuppressWarnings("unchecked") public void setSlot(AjaxRequestTarget aTarget, JCas aJCas, final BratAnnotatorModel aBModel, int aAnnotationId) { // Set an armed slot if (!bModel.getSelection().isRelationAnno() && aBModel.isSlotArmed()) { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) getFeatureModel( aBModel.getArmedFeature()).value; LinkWithRoleModel link = links.get(aBModel.getArmedSlot()); link.targetAddr = aAnnotationId; link.label = selectByAddr(aJCas, aAnnotationId).getCoveredText(); aBModel.clearArmedSlot(); } // Auto-commit if working on existing annotation if (bModel.getSelection().getAnnotation().isSet()) { try { actionAnnotate(aTarget, bModel, aJCas, false); } catch (BratAnnotationException e) { error(e.getMessage()); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (Exception e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } } } private void setLayerAndFeatureModels(AjaxRequestTarget aTarget, JCas aJCas, final BratAnnotatorModel aBModel) throws BratAnnotationException { if (aBModel.getSelection().isRelationAnno()) { long layerId = TypeUtil.getLayerId(aBModel.getSelection().getOriginType()); AnnotationLayer spanLayer = annotationService.getLayer(layerId); if (aBModel.getPreferences().isDefaultLayer() && !aBModel.getDefaultAnnotationLayer().equals(spanLayer)) { throw new BratAnnotationException("No relation annotation allowed on the " + "selected span layer"); } // If we drag an arc between POS annotations, then the relation must be a dependency // relation. // FIXME - Actually this case should be covered by the last case - the database lookup! if (spanLayer.isBuiltIn() && spanLayer.getName().equals(POS.class.getName())) { AnnotationLayer depLayer = annotationService.getLayer(Dependency.class.getName(), aBModel.getProject()); if (aBModel.getAnnotationLayers().contains(depLayer)) { aBModel.setSelectedAnnotationLayer(depLayer); } else { aBModel.setSelectedAnnotationLayer(null); } } // If we drag an arc in a chain layer, then the arc is of the same layer as the span // Chain layers consist of arcs and spans else if (spanLayer.getType().equals(WebAnnoConst.CHAIN_TYPE)) { // one layer both for the span and arc annotation aBModel.setSelectedAnnotationLayer(spanLayer); } // Otherwise, look up the possible relation layer(s) in the database. else { for (AnnotationLayer layer : annotationService.listAnnotationLayer(aBModel.getProject())) { if (layer.getAttachType() != null && layer.getAttachType().equals(spanLayer)) { if (aBModel.getAnnotationLayers().contains(layer)) { aBModel.setSelectedAnnotationLayer(layer); } else { aBModel.setSelectedAnnotationLayer(null); } break; } } } // populate feature value if (aBModel.getSelection().getAnnotation().isSet()) { AnnotationFS annoFs = selectByAddr(aJCas, aBModel.getSelection().getAnnotation().getId()); populateFeatures(annoFs); } // Avoid creation of arcs on locked layers else if (aBModel.getSelectedAnnotationLayer() != null && aBModel.getSelectedAnnotationLayer().isReadonly()) { aBModel.setSelectedAnnotationLayer(new AnnotationLayer()); } aBModel.setDefaultAnnotationLayer(spanLayer); } else if (aBModel.getSelection().getAnnotation().isSet()) { AnnotationFS annoFs = selectByAddr(aJCas, aBModel.getSelection().getAnnotation().getId()); String type = annoFs.getType().getName(); // Might have been reset if we didn't find the layer above. Btw. this can happen if // somebody imports a CAS that has subtypes of layers, e.g. DKPro Core pipelines // like to produce subtypes of POS for individual postags. We do not support such // "elevated types" in WebAnno at this time. if (aBModel.getSelection().getAnnotation().isSet()) { if (type.endsWith(ChainAdapter.CHAIN)) { type = type.substring(0, type.length() - ChainAdapter.CHAIN.length()); } else if (type.endsWith(ChainAdapter.LINK)) { type = type.substring(0, type.length() - ChainAdapter.LINK.length()); } try { aBModel.setSelectedAnnotationLayer(annotationService.getLayer(type, aBModel.getProject())); } catch (NoResultException e) { reset(aTarget); throw new IllegalStateException("Unknown layer [" + type + "]", e); } // populate feature value for (AnnotationFeature feature : annotationService .listAnnotationFeature(aBModel.getSelectedAnnotationLayer())) { if (!feature.isEnabled()) { continue; } if (WebAnnoConst.CHAIN_TYPE.equals(feature.getLayer().getType())) { if (WebAnnoConst.COREFERENCE_TYPE_FEATURE.equals(feature.getName())) { featureModels.add(new FeatureModel(feature, (Serializable) BratAjaxCasUtil.getFeature(annoFs, feature))); } } else { featureModels.add(new FeatureModel(feature, (Serializable) BratAjaxCasUtil.getFeature(annoFs, feature))); } } } } } protected void onChange(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel) { // Overriden in BratAnnotator } protected void onAutoForward(AjaxRequestTarget aTarget, BratAnnotatorModel aBModel) { // Overriden in BratAnnotator } public void onAnnotate(AjaxRequestTarget aTarget, BratAnnotatorModel aModel, int aStart, int aEnd) { // Overriden in AutomationPage } public void onDelete(AjaxRequestTarget aTarget, BratAnnotatorModel aModel, AnnotationFS aFs) { // Overriden in AutomationPage } public void setAnnotationLayers(BratAnnotatorModel aBModel) { setInitSpanLayers(aBModel); if (annotationLayers.size() == 0) { aBModel.setSelectedAnnotationLayer(new AnnotationLayer()); } else if (aBModel.getSelectedAnnotationLayer() == null) { if (aBModel.getRememberedSpanLayer() == null) { aBModel.setSelectedAnnotationLayer(annotationLayers.get(0)); } else { aBModel.setSelectedAnnotationLayer(aBModel.getRememberedSpanLayer()); } } populateFeatures(null); setDefaultLayer(); } private void setInitSpanLayers(BratAnnotatorModel aBModel) { annotationLayers.clear(); AnnotationLayer l = null; for (AnnotationLayer layer : aBModel.getAnnotationLayers()) { if (!layer.isEnabled() || layer.isReadonly() || layer.getName().equals(Token.class.getName())) { continue; } if (layer.getType().equals(WebAnnoConst.SPAN_TYPE)) { annotationLayers.add(layer); l = layer; } // manage chain type else if (layer.getType().equals(WebAnnoConst.CHAIN_TYPE)) { for (AnnotationFeature feature : annotationService.listAnnotationFeature(layer)) { if (!feature.isEnabled()) { continue; } if (feature.getName().equals(WebAnnoConst.COREFERENCE_TYPE_FEATURE)) { annotationLayers.add(layer); } } } // chain } if (bModel.getDefaultAnnotationLayer() != null) { bModel.setSelectedAnnotationLayer(bModel.getDefaultAnnotationLayer()); } else if (l != null) { bModel.setSelectedAnnotationLayer(l); } } public class FeatureEditorPanelContent extends RefreshingView<FeatureModel> { private static final long serialVersionUID = -8359786805333207043L; public FeatureEditorPanelContent(String aId) { super(aId); setOutputMarkupId(true); } @SuppressWarnings("rawtypes") @Override protected void populateItem(final Item<FeatureModel> item) { // Feature editors that allow multiple values may want to update themselves, // e.g. to add another slot. item.setOutputMarkupId(true); final FeatureModel fm = item.getModelObject(); final FeatureEditor frag; switch (fm.feature.getMultiValueMode()) { case NONE: { switch (fm.feature.getType()) { case CAS.TYPE_NAME_INTEGER: { frag = new NumberFeatureEditor("editor", "numberFeatureEditor", item, fm); break; } case CAS.TYPE_NAME_FLOAT: { frag = new NumberFeatureEditor("editor", "numberFeatureEditor", item, fm); break; } case CAS.TYPE_NAME_BOOLEAN: { frag = new BooleanFeatureEditor("editor", "booleanFeatureEditor", item, fm); break; } case CAS.TYPE_NAME_STRING: { frag = new TextFeatureEditor("editor", "textFeatureEditor", item, fm); break; } default: throw new IllegalArgumentException("Unsupported type [" + fm.feature.getType() + "] on feature [" + fm.feature.getName() + "]"); } break; } case ARRAY: { switch (fm.feature.getLinkMode()) { case WITH_ROLE: { // If it is none of the primitive types, it must be a link feature frag = new LinkFeatureEditor("editor", "linkFeatureEditor", item, fm); break; } default: throw new IllegalArgumentException("Unsupported link mode [" + fm.feature.getLinkMode() + "] on feature [" + fm.feature.getName() + "]"); } break; } default: throw new IllegalArgumentException("Unsupported multi-value mode [" + fm.feature.getMultiValueMode() + "] on feature [" + fm.feature.getName() + "]"); } item.add(frag); if (bModel.isForwardAnnotation()) { forwardAnnotationText.add(new DefaultFocusBehavior2()); } else { // Put focus on first feature if (item.getIndex() == item.size() - 1) { frag.getFocusComponent().add(new DefaultFocusBehavior()); } } if (!fm.feature.getLayer().isReadonly()) { // whenever it is updating an annotation, it updates automatically when a component // for the feature lost focus - but updating is for every component edited // LinkFeatureEditors must be excluded because the auto-update will break the // ability to add slots. Adding a slot is NOT an annotation action. // TODO annotate every time except when position is at (0,0) if (bModel.getSelection().getAnnotation().isSet() && !(frag instanceof LinkFeatureEditor)) { if (frag.isDropOrchoice()) { addAnnotateActionBehavior(frag, "onchange"); } else { addAnnotateActionBehavior(frag, "onblur"); } } else if (!(frag instanceof LinkFeatureEditor)) { if (frag.isDropOrchoice()) { storeFeatureValue(frag, "onchange"); } else { storeFeatureValue(frag, "onblur"); } } /* * if (item.getIndex() == 0) { // Put focus on first feature * frag.getFocusComponent().add(new DefaultFocusBehavior()); } */ // Add tooltip on label Component labelComponent = frag.getLabelComponent(); labelComponent.add(new AttributeAppender("style", "cursor: help", ";")); labelComponent .add(new DescriptionTooltipBehavior(fm.feature.getUiName(), fm.feature.getDescription())); } else { frag.getFocusComponent().setEnabled(false); } } private void storeFeatureValue(final FeatureEditor aFrag, String aEvent) { aFrag.getFocusComponent().add(new AjaxFormComponentUpdatingBehavior(aEvent) { private static final long serialVersionUID = 5179816588460867471L; @Override protected void onUpdate(AjaxRequestTarget aTarget) { aTarget.add(annotationFeatureForm); } }); } private void addAnnotateActionBehavior(final FeatureEditor aFrag, String aEvent) { aFrag.getFocusComponent().add(new AjaxFormComponentUpdatingBehavior(aEvent) { private static final long serialVersionUID = 5179816588460867471L; @Override protected void onUpdate(AjaxRequestTarget aTarget) { try { if (bModel.getConstraints() != null) { // Make sure we update the feature editor panel because due to // constraints the contents may have to be re-rendered aTarget.add(annotationFeatureForm); } actionAnnotate(aTarget, bModel, false); } catch (BratAnnotationException e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (Exception e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } } }); } @Override protected Iterator<IModel<FeatureModel>> getItemModels() { ModelIteratorAdapter<FeatureModel> i = new ModelIteratorAdapter<FeatureModel>(featureModels) { @Override protected IModel<FeatureModel> model(FeatureModel aObject) { return Model.of(aObject); } }; return i; } } public static abstract class FeatureEditor extends Fragment { private static final long serialVersionUID = -7275181609671919722L; public FeatureEditor(String aId, String aMarkupId, MarkupContainer aMarkupProvider, IModel<?> aModel) { super(aId, aMarkupId, aMarkupProvider, aModel); } public Component getLabelComponent() { return get("feature"); } abstract public Component getFocusComponent(); abstract public boolean isDropOrchoice(); } public static class NumberFeatureEditor<T extends Number> extends FeatureEditor { private static final long serialVersionUID = -2426303638953208057L; @SuppressWarnings("rawtypes") private final NumberTextField field; public NumberFeatureEditor(String aId, String aMarkupId, MarkupContainer aMarkupProvider, FeatureModel aModel) { super(aId, aMarkupId, aMarkupProvider, new CompoundPropertyModel<FeatureModel>(aModel)); add(new Label("feature", aModel.feature.getUiName())); switch (aModel.feature.getType()) { case CAS.TYPE_NAME_INTEGER: { field = new NumberTextField<Integer>("value", Integer.class); add(field); break; } case CAS.TYPE_NAME_FLOAT: { field = new NumberTextField<Float>("value", Float.class); add(field); break; } default: throw new IllegalArgumentException( "Type [" + aModel.feature.getType() + "] cannot be rendered as a numeric input field"); } } @SuppressWarnings("rawtypes") @Override public NumberTextField getFocusComponent() { return field; } @Override public boolean isDropOrchoice() { return false; } }; public static class BooleanFeatureEditor extends FeatureEditor { private static final long serialVersionUID = 5104979547245171152L; private final CheckBox field; public BooleanFeatureEditor(String aId, String aMarkupId, MarkupContainer aMarkupProvider, FeatureModel aModel) { super(aId, aMarkupId, aMarkupProvider, new CompoundPropertyModel<FeatureModel>(aModel)); add(new Label("feature", aModel.feature.getUiName())); field = new CheckBox("value"); add(field); } @Override public Component getFocusComponent() { return field; } @Override public boolean isDropOrchoice() { return true; } }; public class TextFeatureEditor extends FeatureEditor { private static final long serialVersionUID = 7763348613632105600L; @SuppressWarnings("rawtypes") private final AbstractTextComponent field; private boolean isDrop; //For showing the status of Constraints rules kicking in. private RulesIndicator indicator = new RulesIndicator(); public TextFeatureEditor(String aId, String aMarkupId, MarkupContainer aMarkupProvider, FeatureModel aModel) { super(aId, aMarkupId, aMarkupProvider, new CompoundPropertyModel<FeatureModel>(aModel)); String featureLabelText = aModel.feature.getUiName(); if (aModel.feature.getTagset() != null) { featureLabelText += " (" + aModel.feature.getTagset().getName() + ")"; } add(new Label("feature", featureLabelText)); indicator.reset(); //reset the indicator if (aModel.feature.getTagset() != null) { List<Tag> tagset = null; BratAnnotatorModel model = bModel; // verification to check whether constraints exist for this project or NOT if (model.getConstraints() != null && model.getSelection().getAnnotation().isSet()) { // indicator.setRulesExist(true); tagset = populateTagsBasedOnRules(model, aModel); } else { // indicator.setRulesExist(false); // Earlier behavior, tagset = annotationService.listTags(aModel.feature.getTagset()); } field = new StyledComboBox<Tag>("value", tagset); field.setOutputMarkupId(true); Options options = new Options(DescriptionTooltipBehavior.makeTooltipOptions()); options.set("content", functionForTooltip); //Avoiding leak, instead of setting on document level, setting it to specific component field.add(new TooltipBehavior("#value", options)); isDrop = true; } else { field = new TextField<String>("value"); } //Shows whether constraints are triggered or not //also shows state of constraints use. Component constraintsInUseIndicator = new WebMarkupContainer("textIndicator") { private static final long serialVersionUID = 4346767114287766710L; /* (non-Javadoc) * @see org.apache.wicket.Component#isVisible() */ @Override public boolean isVisible() { return indicator.isAffected(); } }.add(new AttributeAppender("class", new Model<String>() { //adds symbol to indicator private static final long serialVersionUID = -7683195283137223296L; @Override public String getObject() { StringBuffer path = new StringBuffer(); path.append(indicator.getStatusSymbol()); return path.toString(); } })).add(new AttributeAppender("style", new Model<String>() { //adds color to indicator private static final long serialVersionUID = -5255873539738210137L; @Override public String getObject() { StringBuffer path = new StringBuffer(); path.append("; color: "); path.append(indicator.getStatusColor()); return path.toString(); } })); add(constraintsInUseIndicator); add(field); } /** * Adds and sorts tags based on Constraints rules */ private List<Tag> populateTagsBasedOnRules(BratAnnotatorModel model, FeatureModel aModel) { // Add values from rules String restrictionFeaturePath; switch (aModel.feature.getLinkMode()) { case WITH_ROLE: restrictionFeaturePath = aModel.feature.getName() + "." + aModel.feature.getLinkTypeRoleFeatureName(); break; case NONE: restrictionFeaturePath = aModel.feature.getName(); break; default: throw new IllegalArgumentException("Unsupported link mode [" + aModel.feature.getLinkMode() + "] on feature [" + aModel.feature.getName() + "]"); } List<Tag> valuesFromTagset = annotationService.listTags(aModel.feature.getTagset()); try { JCas jCas = getCas(model); FeatureStructure featureStructure = selectByAddr(jCas, model.getSelection().getAnnotation().getId()); Evaluator evaluator = new ValuesGenerator(); //Only show indicator if this feature can be affected by Constraint rules! indicator.setAffected(evaluator.isThisAffectedByConstraintRules(featureStructure, restrictionFeaturePath, model.getConstraints())); List<PossibleValue> possibleValues = evaluator.generatePossibleValues(featureStructure, restrictionFeaturePath, model.getConstraints()); LOG.debug("Possible values for [" + featureStructure.getType().getName() + "] [" + restrictionFeaturePath + "]: " + possibleValues); // only adds tags which are suggested by rules and exist in tagset. List<Tag> tagset = compareSortAndAdd(possibleValues, valuesFromTagset, indicator); // add remaining tags addRemainingTags(tagset, valuesFromTagset); return tagset; } catch (IOException | ClassNotFoundException | UIMAException e) { error(ExceptionUtils.getRootCause(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } return valuesFromTagset; } @Override public Component getFocusComponent() { return field; } @Override public boolean isDropOrchoice() { return isDrop; } }; public class LinkFeatureEditor extends FeatureEditor { private static final long serialVersionUID = 7469241620229001983L; private WebMarkupContainer content; //For showing the status of Constraints rules kicking in. private RulesIndicator indicator = new RulesIndicator(); @SuppressWarnings("rawtypes") private final AbstractTextComponent newRole; private boolean isDrop; @SuppressWarnings("unchecked") public LinkFeatureEditor(String aId, String aMarkupId, MarkupContainer aMarkupProvider, final FeatureModel aModel) { super(aId, aMarkupId, aMarkupProvider, new CompoundPropertyModel<FeatureModel>(aModel)); String featureLabelText = aModel.feature.getUiName(); if (aModel.feature.getTagset() != null) { featureLabelText += " (" + aModel.feature.getTagset().getName() + ")"; } add(new Label("feature", featureLabelText)); // Most of the content is inside this container such that we can refresh it independently // from the rest of the form content = new WebMarkupContainer("content"); content.setOutputMarkupId(true); add(content); content.add(new RefreshingView<LinkWithRoleModel>("slots", Model.of((List<LinkWithRoleModel>) aModel.value)) { private static final long serialVersionUID = 5475284956525780698L; @Override protected Iterator<IModel<LinkWithRoleModel>> getItemModels() { ModelIteratorAdapter<LinkWithRoleModel> i = new ModelIteratorAdapter<LinkWithRoleModel>( (List<LinkWithRoleModel>) LinkFeatureEditor.this.getModelObject().value) { @Override protected IModel<LinkWithRoleModel> model(LinkWithRoleModel aObject) { return Model.of(aObject); } }; return i; } @Override protected void populateItem(final Item<LinkWithRoleModel> aItem) { aItem.setModel(new CompoundPropertyModel<LinkWithRoleModel>(aItem.getModelObject())); Label role = new Label("role"); aItem.add(role); final Label label; if (aItem.getModelObject().targetAddr == -1 && bModel.isArmedSlot(aModel.feature, aItem.getIndex())) { label = new Label("label", "<Select to fill>"); } else { label = new Label("label"); } label.add(new AjaxEventBehavior("click") { private static final long serialVersionUID = 7633309278417475424L; @Override protected void onEvent(AjaxRequestTarget aTarget) { if (bModel.isArmedSlot(aModel.feature, aItem.getIndex())) { bModel.clearArmedSlot(); aTarget.add(content); } else { bModel.setArmedSlot(aModel.feature, aItem.getIndex()); // Need to re-render the whole form because a slot in another // link editor might get unarmed aTarget.add(annotationFeatureForm); } } }); label.add(new AttributeAppender("style", new Model<String>() { private static final long serialVersionUID = 1L; @Override public String getObject() { BratAnnotatorModel model = bModel; if (model.isArmedSlot(aModel.feature, aItem.getIndex())) { return "; background: orange"; } else { return ""; } } })); aItem.add(label); } }); if (aModel.feature.getTagset() != null) { List<Tag> tagset = null; //reset the indicator indicator.reset(); if (bModel.getConstraints() != null && bModel.getSelection().getAnnotation().isSet()) { // indicator.setRulesExist(true); //Constraint rules exist! tagset = addTagsBasedOnRules(bModel, aModel); } else { // indicator.setRulesExist(false); //No constraint rules. // add tagsets only, earlier behavior tagset = annotationService.listTags(aModel.feature.getTagset()); } newRole = new StyledComboBox<Tag>("newRole", Model.of(""), tagset) { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { super.onConfigure(); if (bModel.isSlotArmed() && aModel.feature.equals(bModel.getArmedFeature())) { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this .getModelObject().value; setModelObject(links.get(bModel.getArmedSlot()).role); } else { setModelObject(""); } } }; content.add(newRole); isDrop = true; } else { content.add(newRole = new TextField<String>("newRole", Model.of("")) { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { super.onConfigure(); if (bModel.isSlotArmed() && aModel.feature.equals(bModel.getArmedFeature())) { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this .getModelObject().value; setModelObject(links.get(bModel.getArmedSlot()).role); } else { setModelObject(""); } } }); } //Shows whether constraints are triggered or not //also shows state of constraints use. Component constraintsInUseIndicator = new WebMarkupContainer("linkIndicator") { private static final long serialVersionUID = 4346767114287766710L; /* (non-Javadoc) * @see org.apache.wicket.Component#isVisible() */ @Override public boolean isVisible() { return indicator.isAffected(); } }.add(new AttributeAppender("class", new Model<String>() { //adds symbol to indicator private static final long serialVersionUID = -7683195283137223296L; @Override public String getObject() { StringBuffer path = new StringBuffer(); path.append(indicator.getStatusSymbol()); return path.toString(); } })).add(new AttributeAppender("style", new Model<String>() { //adds color to indicator private static final long serialVersionUID = -5255873539738210137L; @Override public String getObject() { StringBuffer path = new StringBuffer(); path.append("; color: "); path.append(indicator.getStatusColor()); return path.toString(); } })); content.add(constraintsInUseIndicator); // Add a new empty slot with the specified role content.add(new AjaxButton("add") { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { BratAnnotatorModel model = bModel; setVisible(!(model.isSlotArmed() && aModel.feature.equals(model.getArmedFeature()))); // setEnabled(!(model.isSlotArmed() // && aModel.feature.equals(model.getArmedFeature()))); } @Override protected void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { if (StringUtils.isBlank((String) newRole.getModelObject())) { error("Must set slot label before adding!"); aTarget.addChildren(getPage(), FeedbackPanel.class); } else { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this .getModelObject().value; LinkWithRoleModel m = new LinkWithRoleModel(); m.role = (String) newRole.getModelObject(); links.add(m); bModel.setArmedSlot(LinkFeatureEditor.this.getModelObject().feature, links.size() - 1); // Need to re-render the whole form because a slot in another // link editor might get unarmed aTarget.add(annotationFeatureForm); } } }); // Allows user to update slot content.add(new AjaxButton("set") { private static final long serialVersionUID = 7923695373085126646L; @Override protected void onConfigure() { BratAnnotatorModel model = bModel; setVisible(model.isSlotArmed() && aModel.feature.equals(model.getArmedFeature())); // setEnabled(model.isSlotArmed() // && aModel.feature.equals(model.getArmedFeature())); } @Override protected void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this .getModelObject().value; BratAnnotatorModel model = bModel; //Update the slot LinkWithRoleModel m = new LinkWithRoleModel(); m = links.get(model.getArmedSlot()); m.role = (String) newRole.getModelObject(); // int index = model.getArmedSlot(); //retain index // links.remove(model.getArmedSlot()); // model.clearArmedSlot(); // links.add(m); links.set(model.getArmedSlot(), m); //avoid reordering aTarget.add(content); try { actionAnnotate(aTarget, bModel, false); } catch (BratAnnotationException e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCause(e), e); } catch (Exception e) { error(e.getMessage()); LOG.error(ExceptionUtils.getRootCause(e), e); } } }); // Add a new empty slot with the specified role content.add(new AjaxButton("del") { private static final long serialVersionUID = 1L; @Override protected void onConfigure() { BratAnnotatorModel model = bModel; setVisible(model.isSlotArmed() && aModel.feature.equals(model.getArmedFeature())); // setEnabled(model.isSlotArmed() // && aModel.feature.equals(model.getArmedFeature())); } @Override protected void onSubmit(AjaxRequestTarget aTarget, Form<?> aForm) { List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this .getModelObject().value; BratAnnotatorModel model = bModel; links.remove(model.getArmedSlot()); model.clearArmedSlot(); aTarget.add(content); // Auto-commit if working on existing annotation if (bModel.getSelection().getAnnotation().isSet()) { try { actionAnnotate(aTarget, bModel, false); } catch (BratAnnotationException e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } catch (Exception e) { error(ExceptionUtils.getRootCauseMessage(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } } } }); } /** * Adds tagset based on Constraints rules, auto-adds tags which are marked important. * * @return List containing tags which exist in tagset and also suggested by rules, followed * by the remaining tags in tagset. */ private List<Tag> addTagsBasedOnRules(BratAnnotatorModel model, final FeatureModel aModel) { String restrictionFeaturePath = aModel.feature.getName() + "." + aModel.feature.getLinkTypeRoleFeatureName(); List<Tag> valuesFromTagset = annotationService.listTags(aModel.feature.getTagset()); try { JCas jCas = getCas(model); FeatureStructure featureStructure = selectByAddr(jCas, model.getSelection().getAnnotation().getId()); Evaluator evaluator = new ValuesGenerator(); //Only show indicator if this feature can be affected by Constraint rules! indicator.setAffected(evaluator.isThisAffectedByConstraintRules(featureStructure, restrictionFeaturePath, model.getConstraints())); List<PossibleValue> possibleValues = evaluator.generatePossibleValues(featureStructure, restrictionFeaturePath, model.getConstraints()); LOG.debug("Possible values for [" + featureStructure.getType().getName() + "] [" + restrictionFeaturePath + "]: " + possibleValues); // Only adds tags which are suggested by rules and exist in tagset. List<Tag> tagset = compareSortAndAdd(possibleValues, valuesFromTagset, indicator); removeAutomaticallyAddedUnusedEntries(); // Create entries for important tags. autoAddImportantTags(tagset, possibleValues); // Add remaining tags. addRemainingTags(tagset, valuesFromTagset); return tagset; } catch (ClassNotFoundException | UIMAException | IOException e) { error(ExceptionUtils.getRootCause(e)); LOG.error(ExceptionUtils.getRootCauseMessage(e), e); } return valuesFromTagset; } private void removeAutomaticallyAddedUnusedEntries() { // Remove unused (but auto-added) tags. @SuppressWarnings("unchecked") List<LinkWithRoleModel> list = (List<LinkWithRoleModel>) LinkFeatureEditor.this.getModelObject().value; Iterator<LinkWithRoleModel> existingLinks = list.iterator(); while (existingLinks.hasNext()) { LinkWithRoleModel link = existingLinks.next(); if (link.autoCreated && link.targetAddr == -1) { // remove it existingLinks.remove(); } } } private void autoAddImportantTags(List<Tag> aTagset, List<PossibleValue> possibleValues) { // Construct a quick index for tags Set<String> tagset = new HashSet<String>(); for (Tag t : aTagset) { tagset.add(t.getName()); } // Get links list and build role index @SuppressWarnings("unchecked") List<LinkWithRoleModel> links = (List<LinkWithRoleModel>) LinkFeatureEditor.this.getModelObject().value; Set<String> roles = new HashSet<String>(); for (LinkWithRoleModel l : links) { roles.add(l.role); } // Loop over values to see which of the tags are important and add them. for (PossibleValue value : possibleValues) { if (!value.isImportant() || !tagset.contains(value.getValue())) { continue; } // Check if there is already a slot with the given name if (roles.contains(value.getValue())) { continue; } // Add empty slot in UI with that name. LinkWithRoleModel m = new LinkWithRoleModel(); m.role = value.getValue(); // Marking so that can be ignored later. m.autoCreated = true; links.add(m); // NOT arming the slot here! } } public void setModelObject(FeatureModel aModel) { setDefaultModelObject(aModel); } public FeatureModel getModelObject() { return (FeatureModel) getDefaultModelObject(); } @Override public Component getFocusComponent() { return newRole; } @Override public boolean isDropOrchoice() { return isDrop; } }; public void populateFeatures(FeatureStructure aFS) { featureModels = new ArrayList<>(); if (aFS != null) { // Populate from feature structure for (AnnotationFeature feature : annotationService .listAnnotationFeature(bModel.getSelectedAnnotationLayer())) { if (!feature.isEnabled()) { continue; } if (WebAnnoConst.CHAIN_TYPE.equals(feature.getLayer().getType())) { if (bModel.getSelection().isRelationAnno()) { if (feature.getLayer().isLinkedListBehavior() && WebAnnoConst.COREFERENCE_RELATION_FEATURE.equals(feature.getName())) { featureModels.add(new FeatureModel(feature, (Serializable) BratAjaxCasUtil.getFeature(aFS, feature))); } } else { if (WebAnnoConst.COREFERENCE_TYPE_FEATURE.equals(feature.getName())) { featureModels.add(new FeatureModel(feature, (Serializable) BratAjaxCasUtil.getFeature(aFS, feature))); } } } else { featureModels.add( new FeatureModel(feature, (Serializable) BratAjaxCasUtil.getFeature(aFS, feature))); } } } else if (!bModel.getSelection().isRelationAnno() && bModel.getRememberedSpanFeatures() != null) { // Populate from remembered values for (AnnotationFeature feature : annotationService .listAnnotationFeature(bModel.getSelectedAnnotationLayer())) { if (!feature.isEnabled()) { continue; } if (WebAnnoConst.CHAIN_TYPE.equals(feature.getLayer().getType())) { if (WebAnnoConst.COREFERENCE_TYPE_FEATURE.equals(feature.getName())) { featureModels .add(new FeatureModel(feature, bModel.getRememberedSpanFeatures().get(feature))); } } else { featureModels.add(new FeatureModel(feature, bModel.getRememberedSpanFeatures().get(feature))); } } } else if (bModel.getSelection().isRelationAnno() && bModel.getRememberedArcFeatures() != null) { // Populate from remembered values for (AnnotationFeature feature : annotationService .listAnnotationFeature(bModel.getSelectedAnnotationLayer())) { if (!feature.isEnabled()) { continue; } if (WebAnnoConst.CHAIN_TYPE.equals(feature.getLayer().getType())) { if (feature.getLayer().isLinkedListBehavior() && WebAnnoConst.COREFERENCE_RELATION_FEATURE.equals(feature.getName())) { featureModels .add(new FeatureModel(feature, bModel.getRememberedArcFeatures().get(feature))); } } else { featureModels.add(new FeatureModel(feature, bModel.getRememberedArcFeatures().get(feature))); } } } } public void addRemainingTags(List<Tag> tagset, List<Tag> valuesFromTagset) { // adding the remaining part of tagset. for (Tag remainingTag : valuesFromTagset) { if (!tagset.contains(remainingTag)) { tagset.add(remainingTag); } } } /* * Compares existing tagset with possible values resulted from rule evaluation Adds only which * exist in tagset and is suggested by rules. The remaining values from tagset are added * afterwards. */ private static List<Tag> compareSortAndAdd(List<PossibleValue> possibleValues, List<Tag> valuesFromTagset, RulesIndicator rulesIndicator) { //if no possible values, means didn't satisfy conditions if (possibleValues.isEmpty()) { rulesIndicator.didntMatchAnyRule(); } List<Tag> returnList = new ArrayList<Tag>(); // Sorting based on important flag // possibleValues.sort(null); // Comparing to check which values suggested by rules exists in existing // tagset and adding them first in list. for (PossibleValue value : possibleValues) { for (Tag tag : valuesFromTagset) { if (value.getValue().equalsIgnoreCase(tag.getName())) { //Matching values found in tagset and shown in dropdown rulesIndicator.rulesApplied(); // HACK BEGIN tag.setReordered(true); // HACK END //Avoid duplicate entries if (!returnList.contains(tag)) { returnList.add(tag); } } } } //If no matching tags found if (returnList.isEmpty()) { rulesIndicator.didntMatchAnyTag(); } return returnList; } public class LayerSelector extends DropDownChoice<AnnotationLayer> { private static final long serialVersionUID = 2233133653137312264L; public LayerSelector(String aId, List<? extends AnnotationLayer> aChoices) { super(aId, aChoices); setOutputMarkupId(true); setChoiceRenderer(new ChoiceRenderer<AnnotationLayer>("uiName")); add(new AjaxFormComponentUpdatingBehavior("onchange") { private static final long serialVersionUID = 5179816588460867471L; @Override protected void onUpdate(AjaxRequestTarget aTarget) { if (!bModel.getSelectedAnnotationLayer().equals(getModelObject()) && bModel.getSelection().getAnnotation().isSet()) { if (bModel.getSelection().isRelationAnno()) { try { actionClear(aTarget, bModel); } catch (UIMAException | ClassNotFoundException | IOException | BratAnnotationException e) { error(e.getMessage()); } } else { deleteModal.setContent( new DeleteOrReplaceAnnotationModalPanel(deleteModal.getContentId(), bModel, deleteModal, AnnotationDetailEditorPanel.this, getModelObject(), true)); deleteModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { private static final long serialVersionUID = 4364820331676014559L; @Override public void onClose(AjaxRequestTarget target) { target.add(annotationFeatureForm); } }); deleteModal.show(aTarget); } } else { bModel.setSelectedAnnotationLayer(getModelObject()); selectedAnnotationLayer.setDefaultModelObject(getModelObject().getUiName()); aTarget.add(selectedAnnotationLayer); populateFeatures(null); aTarget.add(annotationFeatureForm); } } }); } } private FeatureModel getFeatureModel(AnnotationFeature aFeature) { for (FeatureModel f : featureModels) { if (f.feature.getId() == aFeature.getId()) { return f; } } return null; } /** * Represents a link with a role in the UI. */ public static class LinkWithRoleModel implements Serializable { private static final long serialVersionUID = 2027345278696308900L; public static final String CLICK_HINT = "<Click to activate>"; public String role; public String label = CLICK_HINT; public int targetAddr = -1; public boolean autoCreated; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((label == null) ? 0 : label.hashCode()); result = prime * result + ((role == null) ? 0 : role.hashCode()); result = prime * result + targetAddr; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } LinkWithRoleModel other = (LinkWithRoleModel) obj; if (label == null) { if (other.label != null) { return false; } } else if (!label.equals(other.label)) { return false; } if (role == null) { if (other.role != null) { return false; } } else if (!role.equals(other.role)) { return false; } if (targetAddr != other.targetAddr) { return false; } return true; } } private void updateForwardAnnotation(BratAnnotatorModel aBModel) { if (aBModel.getSelectedAnnotationLayer() != null && !aBModel.getSelectedAnnotationLayer().isLockToTokenOffset()) { aBModel.setForwardAnnotation(false);// no forwarding for // sub-/multitoken annotation } else { aBModel.setForwardAnnotation(aBModel.isForwardAnnotation()); } } public static class FeatureModel implements Serializable { private static final long serialVersionUID = 3512979848975446735L; public final AnnotationFeature feature; public Serializable value; public FeatureModel(AnnotationFeature aFeature, Serializable aValue) { feature = aFeature; value = aValue; // Avoid having null here because otherwise we have to handle null in zillion places! if (value == null && MultiValueMode.ARRAY.equals(aFeature.getMultiValueMode())) { value = new ArrayList<>(); } } } private Map<String, String> getBindTags() { AnnotationFeature f = annotationService.listAnnotationFeature(bModel.getSelectedAnnotationLayer()).get(0); TagSet tagSet = f.getTagset(); Map<Character, String> tagNames = new LinkedHashMap<>(); Map<String, String> bindTag2Key = new LinkedHashMap<>(); for (Tag tag : annotationService.listTags(tagSet)) { if (tagNames.containsKey(tag.getName().toLowerCase().charAt(0))) { String oldBinding = tagNames.get(tag.getName().toLowerCase().charAt(0)); String newBinding = oldBinding + tag.getName().toLowerCase().charAt(0); tagNames.put(tag.getName().toLowerCase().charAt(0), newBinding); bindTag2Key.put(newBinding, tag.getName()); } else { tagNames.put(tag.getName().toLowerCase().charAt(0), tag.getName().toLowerCase().substring(0, 1)); bindTag2Key.put(tag.getName().toLowerCase().substring(0, 1), tag.getName()); } } return bindTag2Key; } private String getKeyBindValue(String aKey, Map<String, String> aBindTags) { // check if all the key pressed are the same character // if not, just check a Tag for the last char pressed if (aKey == null) { return aBindTags.get(aBindTags.keySet().iterator().next()); } char prevC = aKey.charAt(0); for (char ch : aKey.toCharArray()) { if (ch != prevC) { break; } } if (aBindTags.get(aKey) != null) { return aBindTags.get(aKey); } // re-cycle suggestions if (aBindTags.containsKey(aKey.substring(0, 1))) { forwardAnnotationText.setModelObject(aKey.substring(0, 1)); return aBindTags.get(aKey.substring(0, 1)); } // set it to the first in the tag list , when arbitrary key is pressed return aBindTags.get(aBindTags.keySet().iterator().next()); } public void reload(AjaxRequestTarget aTarget) { aTarget.add(annotationFeatureForm); } public void reset(AjaxRequestTarget aTarget) { bModel.getSelection().clear(); bModel.getSelection().setBegin(0); bModel.getSelection().setEnd(0); featureModels = new ArrayList<>(); aTarget.add(annotationFeatureForm); } public void reloadLayer(AjaxRequestTarget aTarget) throws BratAnnotationException { try { featureModels = new ArrayList<>(); if (!bModel.getSelection().isRelationAnno()) { setInitSpanLayers(bModel); } setLayerAndFeatureModels(aTarget, getCas(bModel), bModel); if (featureModels.size() == 0) { populateFeatures(null); } else if (isFeatureModelChanged(bModel.getSelectedAnnotationLayer())) { populateFeatures(null); } setDefaultLayer(); aTarget.add(annotationFeatureForm); } catch (UIMAException | ClassNotFoundException | IOException e) { error(e.getMessage()); } } private void setDefaultLayer() { if (bModel.getPreferences().isDefaultLayer()) { if (bModel.getDefaultAnnotationLayer() == null) { bModel.setDefaultAnnotationLayer(bModel.getSelectedAnnotationLayer()); } } else if (!bModel.getSelection().isRelationAnno()) { bModel.setDefaultAnnotationLayer(bModel.getSelectedAnnotationLayer()); } selectedAnnotationLayer.setDefaultModelObject(bModel.getSelectedAnnotationLayer().getUiName()); } /** * remove this model, if new annotation is to be created */ public void clearArmedSlotModel() { for (FeatureModel fm : featureModels) { if (StringUtils.isNotBlank(fm.feature.getLinkTypeName())) { fm.value = new ArrayList<>(); } } } private Set<AnnotationFS> getAttachedRels(JCas aJCas, AnnotationFS aFs, AnnotationLayer aLayer) throws UIMAException, ClassNotFoundException, IOException { Set<AnnotationFS> toBeDeleted = new HashSet<AnnotationFS>(); for (AnnotationLayer relationLayer : annotationService.listAttachedRelationLayers(aLayer)) { ArcAdapter relationAdapter = (ArcAdapter) getAdapter(annotationService, relationLayer); Type relationType = CasUtil.getType(aJCas.getCas(), relationLayer.getName()); Feature sourceFeature = relationType.getFeatureByBaseName(relationAdapter.getSourceFeatureName()); Feature targetFeature = relationType.getFeatureByBaseName(relationAdapter.getTargetFeatureName()); // This code is already prepared for the day that relations can go between // different layers and may have different attach features for the source and // target layers. Feature relationSourceAttachFeature = null; Feature relationTargetAttachFeature = null; if (relationAdapter.getAttachFeatureName() != null) { relationSourceAttachFeature = sourceFeature.getRange() .getFeatureByBaseName(relationAdapter.getAttachFeatureName()); relationTargetAttachFeature = targetFeature.getRange() .getFeatureByBaseName(relationAdapter.getAttachFeatureName()); } for (AnnotationFS relationFS : CasUtil.select(aJCas.getCas(), relationType)) { // Here we get the annotations that the relation is pointing to in the UI FeatureStructure sourceFS; if (relationSourceAttachFeature != null) { sourceFS = relationFS.getFeatureValue(sourceFeature) .getFeatureValue(relationSourceAttachFeature); } else { sourceFS = relationFS.getFeatureValue(sourceFeature); } FeatureStructure targetFS; if (relationTargetAttachFeature != null) { targetFS = relationFS.getFeatureValue(targetFeature) .getFeatureValue(relationTargetAttachFeature); } else { targetFS = relationFS.getFeatureValue(targetFeature); } if (isSame(sourceFS, aFs) || isSame(targetFS, aFs)) { toBeDeleted.add(relationFS); LOG.debug("Deleted relation [" + getAddr(relationFS) + "] from layer [" + relationLayer.getName() + "]"); } } } return toBeDeleted; } public AnnotationFeatureForm getAnnotationFeatureForm() { return annotationFeatureForm; } public Label getSelectedAnnotationLayer() { return selectedAnnotationLayer; } private boolean isFeatureModelChanged(AnnotationLayer aLayer) { for (FeatureModel fM : featureModels) { if (!annotationService.listAnnotationFeature(aLayer).contains(fM.feature)) { return true; } } return false; } private boolean isForwardable() { if (bModel.getSelectedAnnotationLayer() == null) { return false; } if (bModel.getSelectedAnnotationLayer().getId() <= 0) { return false; } if (!bModel.getSelectedAnnotationLayer().getType().equals(WebAnnoConst.SPAN_TYPE)) { return false; } if (!bModel.getSelectedAnnotationLayer().isLockToTokenOffset()) { return false; } // no forward annotation for multifeature layers. if (annotationService.listAnnotationFeature(bModel.getSelectedAnnotationLayer()).size() > 1) { return false; } // we allow forward annotation only for a feature with a tagset if (annotationService.listAnnotationFeature(bModel.getSelectedAnnotationLayer()).get(0) .getTagset() == null) { return false; } TagSet tagSet = annotationService.listAnnotationFeature(bModel.getSelectedAnnotationLayer()).get(0) .getTagset(); // there should be at least one tag in the tagset if (annotationService.listTags(tagSet).size() == 0) { return false; } return true; } private static String generateMessage(AnnotationLayer aLayer, String aLabel, boolean aDeleted) { String action = aDeleted ? "deleted" : "created/updated"; String msg = "The [" + aLayer.getUiName() + "] annotation has been " + action + "."; if (StringUtils.isNotBlank(aLabel)) { msg += " Label: [" + aLabel + "]"; } return msg; } class StyledComboBox<T> extends ComboBox<T> { public StyledComboBox(String id, IModel<String> model, List<T> choices) { super(id, model, choices); } public StyledComboBox(String string, List<T> choices) { super(string, choices); } private static final long serialVersionUID = 1L; @Override protected IJQueryTemplate newTemplate() { return new IJQueryTemplate() { private static final long serialVersionUID = 1L; /** * Marks the reordered entries in bold. * Same as text feature editor. */ @Override public String getText() { // Some docs on how the templates work in Kendo, in case we need // more fancy dropdowns // http://docs.telerik.com/kendo-ui/framework/templates/overview StringBuilder sb = new StringBuilder(); sb.append("# if (data.reordered == 'true') { #"); sb.append("<div title=\"#: data.description #\"><b>#: data.name #</b></div>\n"); sb.append("# } else { #"); sb.append("<div title=\"#: data.description #\">#: data.name #</div>\n"); sb.append("# } #"); return sb.toString(); } @Override public List<String> getTextProperties() { return Arrays.asList("name", "description", "reordered"); } }; } } }