diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/DeleteSpanAnnotationRequest.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/DeleteSpanAnnotationRequest.java new file mode 100644 index 0000000000..a4da40a5f7 --- /dev/null +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/DeleteSpanAnnotationRequest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * 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.inception.annotation.layer.span; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.text.AnnotationFS; + +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; + +public class DeleteSpanAnnotationRequest + extends SpanAnnotationRequest_ImplBase +{ + private final AnnotationFS annotation; + + public DeleteSpanAnnotationRequest(SourceDocument aDocument, String aDocumentOwner, CAS aCas, + AnnotationFS aAnnotation) + { + this(null, aDocument, aDocumentOwner, aCas, aAnnotation); + } + + private DeleteSpanAnnotationRequest(DeleteSpanAnnotationRequest aOriginal, + SourceDocument aDocument, String aDocumentOwner, CAS aCas, AnnotationFS aAnnotation) + { + super(null, aDocument, aDocumentOwner, aCas, aAnnotation.getBegin(), aAnnotation.getEnd()); + annotation = aAnnotation; + } + + public AnnotationFS getAnnotation() + { + return annotation; + } + + @Override + public DeleteSpanAnnotationRequest changeSpan(int aBegin, int aEnd) + { + throw new UnsupportedOperationException("Cannot change span when deleting span annotation"); + } +} diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SegmentationUnitAdapter.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SegmentationUnitAdapter.java new file mode 100644 index 0000000000..1a21e7908a --- /dev/null +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SegmentationUnitAdapter.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * 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.inception.annotation.layer.span; + +import static de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter.SpanOption.TRIM; + +import java.util.Set; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.jcas.tcas.Annotation; + +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.exception.IllegalPlacementException; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence; +import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; + +public class SegmentationUnitAdapter +{ + private static final Set SEGMENTATION_TYPES = Set.of(Token._TypeName, + Sentence._TypeName); + + private final SpanAdapter spanAdapter; + + public boolean accepts(String aTypeName) + { + return SEGMENTATION_TYPES.contains(aTypeName); + } + + public SegmentationUnitAdapter(SpanAdapter aSpanAdapter) + { + spanAdapter = aSpanAdapter; + } + + public AnnotationFS handle(CreateSpanAnnotationRequest aRequest) throws AnnotationException + { + if (Token._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + return splitUnit(aRequest, Token.class); + } + + if (Sentence._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + return splitUnit(aRequest, Sentence.class); + } + + throw new IllegalPlacementException( + "Annotation type not supported: " + spanAdapter.getAnnotationTypeName()); + } + + public AnnotationFS handle(MoveSpanAnnotationRequest aRequest) throws AnnotationException + { + var ann = aRequest.getAnnotation(); + if (aRequest.getBegin() == ann.getBegin() && aRequest.getEnd() == ann.getEnd()) { + // NOP + return ann; + } + + if (Token._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + return handleTokenMove(aRequest); + } + + if (Sentence._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + return handleSentenceMove(aRequest); + } + + throw new IllegalPlacementException( + "Annotation type not supported: " + spanAdapter.getAnnotationTypeName()); + } + + public void handle(DeleteSpanAnnotationRequest aRequest) throws AnnotationException + { + if (Token._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + deleteAndMergeUnit(aRequest.getDocument(), aRequest.getDocumentOwner(), + aRequest.getCas(), (Annotation) aRequest.getAnnotation(), Token.class); + } + + if (Sentence._TypeName.equals(spanAdapter.getAnnotationTypeName())) { + deleteAndMergeUnit(aRequest.getDocument(), aRequest.getDocumentOwner(), + aRequest.getCas(), (Annotation) aRequest.getAnnotation(), Sentence.class); + } + + throw new IllegalPlacementException( + "Annotation type not supported: " + spanAdapter.getAnnotationTypeName()); + } + + private void deleteAndMergeUnit(SourceDocument aDocument, + String aDocumentOwner, CAS aCas, Annotation aUnit, Class aClass) + throws AnnotationException + { + // First try to merge with the preceding unit + var precedingUnit = aCas.select(aClass).preceding(aUnit).limit(1).singleOrNull(); + if (precedingUnit != null) { + var oldBegin = precedingUnit.getBegin(); + var oldEnd = precedingUnit.getEnd(); + spanAdapter.moveSpanAnnotation(aCas, precedingUnit, precedingUnit.getBegin(), + aUnit.getEnd(), TRIM); + spanAdapter.publishEvent(() -> new SpanMovedEvent(this, aDocument, aDocumentOwner, + spanAdapter.getLayer(), precedingUnit, oldBegin, oldEnd)); + return; + } + + // Then try to merge with the following unit + var followingUnit = aCas.select(aClass).preceding(aUnit).limit(1).singleOrNull(); + if (followingUnit != null) { + var oldBegin = followingUnit.getBegin(); + var oldEnd = followingUnit.getEnd(); + spanAdapter.moveSpanAnnotation(aCas, followingUnit, aUnit.getBegin(), + followingUnit.getEnd(), TRIM); + spanAdapter.publishEvent(() -> new SpanMovedEvent(this, aDocument, aDocumentOwner, + spanAdapter.getLayer(), followingUnit, oldBegin, oldEnd)); + return; + } + + throw new IllegalPlacementException("The last unit cannot be deleted."); + } + + private AnnotationFS handleSentenceMove(MoveSpanAnnotationRequest aRequest) + throws AnnotationException + { + throw new IllegalPlacementException("Moving/resizing units currently not supported"); + } + + private AnnotationFS handleTokenMove(MoveSpanAnnotationRequest aRequest) + throws AnnotationException + { + throw new IllegalPlacementException("Moving/resizing units currently not supported"); + + // var cas = aRequest.getCas(); + // var ann = (Annotation) aRequest.getAnnotation(); + // + // if (aRequest.getBegin() != ann.getBegin() && aRequest.getEnd() != ann.getEnd()) { + // throw new IllegalPlacementException( + // "Can only resize at start or at end. Cannot move or resize at both ends at the same + // time."); + // } + // + // if (aRequest.getBegin() != ann.getBegin()) { + // // Expand at begin + // if (aRequest.getBegin() < ann.getBegin()) { + // + // } + // + // // Reduce at begin + // if (aRequest.getBegin() > ann.getBegin()) { + // + // } + // } + // + // if (aRequest.getEnd() != ann.getEnd()) { + // // Expand at end + // if (aRequest.getEnd() > ann.getEnd()) { + // + // } + // + // // Reduce at end + // if (aRequest.getEnd() < ann.getEnd()) { + // var followingToken = cas.select(Token.class).following(ann).singleOrNull(); + // if (followingToken == null) { + // // We make the last token smaller, so we need to add a new last token in order + // // to keep the entire text covered in tokens. + // } + // else { + // // We have a following token that we can enlarge + // } + // } + // } + // + // return ann; + } + + private AnnotationFS splitUnit(CreateSpanAnnotationRequest aRequest, + Class unitType) + throws AnnotationException + { + if (aRequest.getBegin() != aRequest.getEnd()) { + throw new IllegalPlacementException( + "Can only split unit, not create an entirely new one."); + } + + var cas = aRequest.getCas(); + var unit = cas.select(unitType) // + .covering(aRequest.getBegin(), aRequest.getBegin()) // + .singleOrNull(); + + var oldBegin = unit.getBegin(); + var oldEnd = unit.getEnd(); + var head = spanAdapter.moveSpanAnnotation(cas, unit, unit.getBegin(), aRequest.getBegin(), + TRIM); + spanAdapter.publishEvent(() -> new SpanMovedEvent(this, aRequest.getDocument(), + aRequest.getDocumentOwner(), spanAdapter.getLayer(), head, oldBegin, oldEnd)); + + var tail = spanAdapter.createSpanAnnotation(cas, aRequest.getEnd(), oldEnd, TRIM); + spanAdapter.publishEvent(() -> new SpanCreatedEvent(this, aRequest.getDocument(), + aRequest.getDocumentOwner(), spanAdapter.getLayer(), tail)); + return tail; + } +} diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java index 76b1d63ad1..4edff8c5e4 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanAdapter.java @@ -18,7 +18,6 @@ package de.tudarmstadt.ukp.inception.annotation.layer.span; import static de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter.SpanOption.TRIM; -import static de.tudarmstadt.ukp.inception.support.uima.ICasUtil.selectAnnotationByAddr; import static de.tudarmstadt.ukp.inception.support.uima.ICasUtil.selectByAddr; import static java.lang.System.currentTimeMillis; import static java.util.Arrays.asList; @@ -49,8 +48,6 @@ import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; -import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence; -import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.inception.annotation.layer.TypeAdapter_ImplBase; import de.tudarmstadt.ukp.inception.rendering.selection.Selection; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; @@ -75,6 +72,8 @@ public enum SpanOption private final List behaviors; + private final SegmentationUnitAdapter segmentationUnitAdapter; + public SpanAdapter(LayerSupportRegistry aLayerSupportRegistry, FeatureSupportRegistry aFeatureSupportRegistry, ApplicationEventPublisher aEventPublisher, AnnotationLayer aLayer, @@ -92,6 +91,8 @@ public SpanAdapter(LayerSupportRegistry aLayerSupportRegistry, .sorted(AnnotationAwareOrderComparator.INSTANCE) // .toList(); } + + segmentationUnitAdapter = new SegmentationUnitAdapter(this); } /** @@ -120,12 +121,8 @@ public AnnotationFS add(SourceDocument aDocument, String aDataOwner, CAS aCas, i public AnnotationFS handle(CreateSpanAnnotationRequest aRequest) throws AnnotationException { - if (Token._TypeName.equals(getAnnotationTypeName())) { - return splitUnit(aRequest, Token.class); - } - - if (Sentence._TypeName.equals(getAnnotationTypeName())) { - return splitUnit(aRequest, Sentence.class); + if (segmentationUnitAdapter.accepts(getAnnotationTypeName())) { + return segmentationUnitAdapter.handle(aRequest); } var request = aRequest; @@ -175,6 +172,10 @@ public AnnotationFS move(SourceDocument aDocument, String aDocumentOwner, CAS aC public AnnotationFS handle(MoveSpanAnnotationRequest aRequest) throws AnnotationException { + if (segmentationUnitAdapter.accepts(getAnnotationTypeName())) { + return segmentationUnitAdapter.handle(aRequest); + } + var ann = aRequest.getAnnotation(); if (aRequest.getBegin() == ann.getBegin() && aRequest.getEnd() == ann.getEnd()) { // NOP @@ -183,14 +184,6 @@ public AnnotationFS handle(MoveSpanAnnotationRequest aRequest) throws Annotation var request = aRequest; - if (Token._TypeName.equals(getAnnotationTypeName())) { - return handleTokenMove(aRequest); - } - - if (Sentence._TypeName.equals(getAnnotationTypeName())) { - return handleSentenceMove(aRequest); - } - // Adjust the move request (e.g. adjust offsets to the configured granularity) or // reject the request (e.g. reject cross-sentence annotations) for (var behavior : behaviors) { @@ -210,88 +203,7 @@ public AnnotationFS handle(MoveSpanAnnotationRequest aRequest) throws Annotation return request.getAnnotation(); } - private AnnotationFS handleSentenceMove(MoveSpanAnnotationRequest aRequest) - throws AnnotationException - { - throw new IllegalPlacementException("Moving/resizing units currently not supported"); - } - - private AnnotationFS handleTokenMove(MoveSpanAnnotationRequest aRequest) - throws AnnotationException - { - throw new IllegalPlacementException("Moving/resizing units currently not supported"); - - // var cas = aRequest.getCas(); - // var ann = (Annotation) aRequest.getAnnotation(); - // - // if (aRequest.getBegin() != ann.getBegin() && aRequest.getEnd() != ann.getEnd()) { - // throw new IllegalPlacementException( - // "Can only resize at start or at end. Cannot move or resize at both ends at the same - // time."); - // } - // - // if (aRequest.getBegin() != ann.getBegin()) { - // // Expand at begin - // if (aRequest.getBegin() < ann.getBegin()) { - // - // } - // - // // Reduce at begin - // if (aRequest.getBegin() > ann.getBegin()) { - // - // } - // } - // - // if (aRequest.getEnd() != ann.getEnd()) { - // // Expand at end - // if (aRequest.getEnd() > ann.getEnd()) { - // - // } - // - // // Reduce at end - // if (aRequest.getEnd() < ann.getEnd()) { - // var followingToken = cas.select(Token.class).following(ann).singleOrNull(); - // if (followingToken == null) { - // // We make the last token smaller, so we need to add a new last token in order - // // to keep the entire text covered in tokens. - // } - // else { - // // We have a following token that we can enlarge - // } - // } - // } - // - // return ann; - } - - private AnnotationFS splitUnit(CreateSpanAnnotationRequest aRequest, - Class unitType) - throws AnnotationException - { - if (aRequest.getBegin() != aRequest.getEnd()) { - throw new IllegalPlacementException( - "Can only split unit, not create an entirely new one."); - } - - var cas = aRequest.getCas(); - var unit = cas.select(unitType) // - .covering(aRequest.getBegin(), aRequest.getBegin()) // - .singleOrNull(); - - var oldBegin = unit.getBegin(); - var oldEnd = unit.getEnd(); - var head = moveSpanAnnotation(cas, unit, unit.getBegin(), aRequest.getBegin(), TRIM); - publishEvent(() -> new SpanMovedEvent(this, aRequest.getDocument(), - aRequest.getDocumentOwner(), getLayer(), head, oldBegin, oldEnd)); - - var tail = createSpanAnnotation(cas, aRequest.getEnd(), oldEnd, TRIM); - publishEvent(() -> new SpanCreatedEvent(this, aRequest.getDocument(), - aRequest.getDocumentOwner(), getLayer(), tail)); - return tail; - } - - private AnnotationFS createSpanAnnotation(CAS aCas, int aBegin, int aEnd, - SpanOption... aOptions) + AnnotationFS createSpanAnnotation(CAS aCas, int aBegin, int aEnd, SpanOption... aOptions) throws AnnotationException { var type = CasUtil.getType(aCas, getAnnotationTypeName()); @@ -315,8 +227,8 @@ private AnnotationFS createSpanAnnotation(CAS aCas, int aBegin, int aEnd, return newAnnotation; } - private AnnotationFS moveSpanAnnotation(CAS aCas, AnnotationFS aAnnotation, int aBegin, - int aEnd, SpanOption... aOptions) + AnnotationFS moveSpanAnnotation(CAS aCas, AnnotationFS aAnnotation, int aBegin, int aEnd, + SpanOption... aOptions) { var oldCoveredText = aAnnotation.getCoveredText(); var oldBegin = aAnnotation.getBegin(); @@ -343,54 +255,25 @@ private AnnotationFS moveSpanAnnotation(CAS aCas, AnnotationFS aAnnotation, int public void delete(SourceDocument aDocument, String aDocumentOwner, CAS aCas, VID aVid) throws AnnotationException { - if (Token._TypeName.equals(getAnnotationTypeName())) { - deleteAndMergeUnit(aDocument, aDocumentOwner, aCas, aVid, Token.class); - } - - if (Sentence._TypeName.equals(getAnnotationTypeName())) { - deleteAndMergeUnit(aDocument, aDocumentOwner, aCas, aVid, Sentence.class); - } - var fs = selectByAddr(aCas, AnnotationFS.class, aVid.getId()); - aCas.removeFsFromIndexes(fs); - - // delete associated attachFeature - if (getAttachTypeName() != null) { - detatch(aCas, fs); - } - - publishEvent(() -> new SpanDeletedEvent(this, aDocument, aDocumentOwner, getLayer(), fs)); + handle(new DeleteSpanAnnotationRequest(aDocument, aDocumentOwner, aCas, fs)); } - private void deleteAndMergeUnit(SourceDocument aDocument, String aDocumentOwner, - CAS aCas, VID aVid, Class aClass) - throws AnnotationException + public void handle(DeleteSpanAnnotationRequest aRequest) throws AnnotationException { - var unit = selectAnnotationByAddr(aCas, aVid.getId()); - - // First try to merge with the preceding unit - var precedingUnit = aCas.select(aClass).preceding(unit).limit(1).singleOrNull(); - if (precedingUnit != null) { - var oldBegin = precedingUnit.getBegin(); - var oldEnd = precedingUnit.getEnd(); - moveSpanAnnotation(aCas, precedingUnit, precedingUnit.getBegin(), unit.getEnd(), TRIM); - publishEvent(() -> new SpanMovedEvent(this, aDocument, aDocumentOwner, getLayer(), - precedingUnit, oldBegin, oldEnd)); - return; + if (segmentationUnitAdapter.accepts(getAnnotationTypeName())) { + segmentationUnitAdapter.handle(aRequest); } - // Then try to merge with the following unit - var followingUnit = aCas.select(aClass).preceding(unit).limit(1).singleOrNull(); - if (followingUnit != null) { - var oldBegin = followingUnit.getBegin(); - var oldEnd = followingUnit.getEnd(); - moveSpanAnnotation(aCas, followingUnit, unit.getBegin(), followingUnit.getEnd(), TRIM); - publishEvent(() -> new SpanMovedEvent(this, aDocument, aDocumentOwner, getLayer(), - followingUnit, oldBegin, oldEnd)); - return; + aRequest.getCas().removeFsFromIndexes(aRequest.getAnnotation()); + + // delete associated attachFeature + if (getAttachTypeName() != null) { + detatch(aRequest.getCas(), aRequest.getAnnotation()); } - throw new IllegalPlacementException("The last unit cannot be deleted."); + publishEvent(() -> new SpanDeletedEvent(this, aRequest.getDocument(), + aRequest.getDocumentOwner(), getLayer(), aRequest.getAnnotation())); } public AnnotationFS restore(SourceDocument aDocument, String aDocumentOwner, CAS aCas, VID aVid)