RelationRenderer extends Renderer_ImplBase<RelationAdapter> { @Override public void render(final CAS aCas, List<AnnotationFeature> aFeatures, VDocument aResponse, int aWindowBegin, int aWindowEnd) { RelationAdapter typeAdapter = getTypeAdapter(); Type type; Type spanType; try { type = getType(aCas, typeAdapter.getAnnotationTypeName()); spanType = getType(aCas, typeAdapter.getAttachTypeName()); } catch (IllegalArgumentException e) { return; } List<AnnotationFeature> visibleFeatures = aFeatures.stream() .filter(f -> f.isVisible() && f.isEnabled()).collect(Collectors.toList()); Feature dependentFeature = type.getFeatureByBaseName(typeAdapter.getTargetFeatureName()); Feature governorFeature = type.getFeatureByBaseName(typeAdapter.getSourceFeatureName()); Feature arcSpanFeature = spanType.getFeatureByBaseName(typeAdapter.getAttachFeatureName()); FeatureStructure dependentFs; FeatureStructure governorFs; Map<Integer, Set<Integer>> relationLinks = getRelationLinks(aCas, aWindowBegin, aWindowEnd, type, dependentFeature, governorFeature, arcSpanFeature); List<Integer> yieldDeps = new ArrayList<>(); Map<AnnotationFS, VArc> annoToArcIdx = new HashMap<>(); for (AnnotationFS fs : selectCovered(aCas, 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); } String bratTypeName = typeAdapter.getEncodedTypeName(); Map<String, String> features = renderLabelFeatureValues(typeAdapter, fs, visibleFeatures); if (dependentFs == null || governorFs == null) { StringBuilder message = new StringBuilder(); message.append("Relation [" + typeAdapter.getLayer().getName() + "] with id [" + getAddr(fs) + "] has loose ends - cannot render."); if (typeAdapter.getAttachFeatureName() != null) { message.append("\nRelation [" + typeAdapter.getLayer().getName() + "] attached to feature [" + typeAdapter.getAttachFeatureName() + "]."); } message.append("\nDependent: " + dependentFs); message.append("\nGovernor: " + governorFs); RequestCycle requestCycle = RequestCycle.get(); IPageRequestHandler handler = PageRequestHandlerTracker .getLastHandler(requestCycle); Page page = (Page) handler.getPage(); page.warn(message.toString()); continue; } VArc arc = new VArc(typeAdapter.getLayer(), fs, bratTypeName, governorFs, dependentFs, features); arc.addLazyDetails(getLazyDetails(typeAdapter, fs, aFeatures)); annoToArcIdx.put(fs, arc); aResponse.add(arc); renderRequiredFeatureErrors(visibleFeatures, fs, aResponse); if (relationLinks.keySet().contains(getAddr(governorFs)) && !yieldDeps.contains(getAddr(governorFs))) { yieldDeps.add(getAddr(governorFs)); List<Integer> sortedDepFs = new ArrayList<>(relationLinks.get(getAddr(governorFs))); sortedDepFs .sort(comparingInt(arg0 -> selectAnnotationByAddr(aCas, arg0).getBegin())); String cm = getYieldMessage(aCas, sortedDepFs); aResponse.add(new VComment(governorFs, VCommentType.YIELD, cm)); } } for (RelationLayerBehavior behavior : behaviors) { behavior.onRender(typeAdapter, aResponse, annoToArcIdx); } } RelationRenderer(RelationAdapter aTypeAdapter,
            LayerSupportRegistry aLayerSupportRegistry,
            FeatureSupportRegistry aFeatureSupportRegistry, List<RelationLayerBehavior> aBehaviors); @Override void render(final CAS aCas, List<AnnotationFeature> aFeatures,
            VDocument aResponse, int aWindowBegin, int aWindowEnd);  }
@Test public void thatRelationCrossSentenceBehaviorOnRenderGeneratesErrors() throws Exception { TokenBuilder<Token, Sentence> builder = new TokenBuilder<>(Token.class, Sentence.class); builder.buildTokens(jcas, "This is a test .\nThis is sentence two ."); for (Token t : select(jcas, Token.class)) { POS pos = new POS(jcas, t.getBegin(), t.getEnd()); t.setPos(pos); pos.addToIndexes(); } RelationAdapter adapter = new RelationAdapter(layerSupportRegistry, featureSupportRegistry, null, depLayer, FEAT_REL_TARGET, FEAT_REL_SOURCE, () -> asList(dependencyLayerGovernor, dependencyLayerDependent), behaviors); List<POS> posAnnotations = new ArrayList<>(select(jcas, POS.class)); POS source = posAnnotations.get(0); POS target = posAnnotations.get(posAnnotations.size() - 1); depLayer.setCrossSentence(true); AnnotationFS dep = adapter.add(document, username, source, target, jcas.getCas()); depLayer.setCrossSentence(false); RelationRenderer sut = new RelationRenderer(adapter, layerSupportRegistry, featureSupportRegistry, asList(new RelationCrossSentenceBehavior())); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .usingFieldByFieldElementComparator() .contains(new VComment(dep, ERROR, "Crossing sentence boundaries is not permitted.")); }
@Test public void thatRelationOverlapBehaviorOnRenderGeneratesErrors() throws Exception { TokenBuilder<Token, Sentence> builder = new TokenBuilder<>(Token.class, Sentence.class); builder.buildTokens(jcas, "This is a test .\nThis is sentence two ."); for (Token t : select(jcas, Token.class)) { POS pos = new POS(jcas, t.getBegin(), t.getEnd()); t.setPos(pos); pos.addToIndexes(); } RelationAdapter adapter = new RelationAdapter(layerSupportRegistry, featureSupportRegistry, null, depLayer, FEAT_REL_TARGET, FEAT_REL_SOURCE, () -> asList(dependencyLayerGovernor, dependencyLayerDependent), behaviors); List<POS> posAnnotations = new ArrayList<>(select(jcas, POS.class)); POS source = posAnnotations.get(0); POS target = posAnnotations.get(1); RelationRenderer sut = new RelationRenderer(adapter, layerSupportRegistry, featureSupportRegistry, asList(new RelationOverlapBehavior())); depLayer.setOverlapMode(ANY_OVERLAP); AnnotationFS dep1 = adapter.add(document, username, source, target, jcas.getCas()); AnnotationFS dep2 = adapter.add(document, username, source, target, jcas.getCas()); { depLayer.setOverlapMode(ANY_OVERLAP); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .filteredOn(c -> !YIELD.equals(c.getCommentType())) .isEmpty(); } { depLayer.setOverlapMode(STACKING_ONLY); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .filteredOn(c -> !YIELD.equals(c.getCommentType())) .isEmpty(); } { depLayer.setOverlapMode(OVERLAP_ONLY); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .filteredOn(c -> !YIELD.equals(c.getCommentType())) .usingFieldByFieldElementComparator() .contains( new VComment(dep1, ERROR, "Stacking is not permitted."), new VComment(dep2, ERROR, "Stacking is not permitted.")); } { depLayer.setOverlapMode(NO_OVERLAP); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .filteredOn(c -> !YIELD.equals(c.getCommentType())) .usingFieldByFieldElementComparator() .contains( new VComment(dep1, ERROR, "Stacking is not permitted."), new VComment(dep2, ERROR, "Stacking is not permitted.")); } adapter.delete(document, username, jcas.getCas(), new VID(dep2)); depLayer.setOverlapMode(ANY_OVERLAP); AnnotationFS dep3 = adapter.add(document, username, source, posAnnotations.get(2), jcas.getCas()); { depLayer.setOverlapMode(NO_OVERLAP); VDocument vdoc = new VDocument(); sut.render(jcas.getCas(), asList(), vdoc, 0, jcas.getDocumentText().length()); assertThat(vdoc.comments()) .filteredOn(c -> !YIELD.equals(c.getCommentType())) .usingFieldByFieldElementComparator() .contains( new VComment(dep1, ERROR, "Overlap is not permitted."), new VComment(dep3, ERROR, "Overlap is not permitted.")); } }