diff --git a/inception/inception-active-learning/pom.xml b/inception/inception-active-learning/pom.xml index f480ba3d900..3f9aeb1b55c 100644 --- a/inception/inception-active-learning/pom.xml +++ b/inception/inception-active-learning/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT .. inception-active-learning diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java index cf08e6f2fa8..9b3070d121a 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningServiceImpl.java @@ -28,15 +28,19 @@ import java.io.IOException; import java.io.Serializable; import java.lang.invoke.MethodHandles; +import java.util.Collection; import java.util.List; import java.util.Optional; +import org.apache.commons.lang3.ClassUtils; +import org.apache.uima.cas.CAS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; +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.clarin.webanno.security.UserDao; @@ -44,7 +48,6 @@ import de.tudarmstadt.ukp.inception.active.learning.config.ActiveLearningAutoConfiguration; import de.tudarmstadt.ukp.inception.active.learning.event.ActiveLearningRecommendationEvent; import de.tudarmstadt.ukp.inception.active.learning.strategy.ActiveLearningStrategy; -import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; @@ -54,6 +57,7 @@ import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup.Delta; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupport; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; /** @@ -191,7 +195,6 @@ public void acceptSpanSuggestion(SourceDocument aDocument, User aDataOwner, .orElseThrow(() -> new IllegalArgumentException( "No such layer: [" + aSuggestion.getLayerId() + "]")); var feature = schemaService.getFeature(aSuggestion.getFeature(), layer); - var adapter = (SpanAdapter) schemaService.getAdapter(layer); // Load CAS in which to create the annotation. This might be different from the one that // is currently viewed by the user, e.g. if the user switched to another document after @@ -202,7 +205,8 @@ public void acceptSpanSuggestion(SourceDocument aDocument, User aDataOwner, // Create AnnotationFeature and FeatureSupport var featureSupport = featureSupportRegistry.findExtension(feature).orElseThrow(); - var label = (String) featureSupport.unwrapFeatureValue(feature, cas, aValue); + + var label = unwrapLabel(aValue, feature, cas, featureSupport); // Clone of the original suggestion with the selected by the user var suggestionWithUserSelectedLabel = aSuggestion.toBuilder().withLabel(label).build(); @@ -236,6 +240,30 @@ public void acceptSpanSuggestion(SourceDocument aDocument, User aDataOwner, suggestionWithUserSelectedLabel.getFeature(), action, alternativeSuggestions)); } + private String unwrapLabel(Object aValue, AnnotationFeature feature, CAS cas, + FeatureSupport featureSupport) + { + Object rawLabel = featureSupport.unwrapFeatureValue(feature, cas, aValue); + if (rawLabel instanceof Collection collectionValue) { + rawLabel = collectionValue.iterator().next(); + } + + if (rawLabel == null) { + return null; + } + + if (ClassUtils.isPrimitiveOrWrapper(rawLabel.getClass())) { + return String.valueOf(rawLabel); + } + + if (rawLabel instanceof String) { + return (String) rawLabel; + } + + throw new IllegalArgumentException( + "Non-primitive suggestions are not supported: [" + rawLabel.getClass() + "]"); + } + @Override @Transactional public void rejectSpanSuggestion(String aSessionOwner, User aDataOwner, AnnotationLayer aLayer, diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.html b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.html index 037eebd6671..0f5acfb325c 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.html +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.html @@ -24,7 +24,7 @@
-
+
@@ -41,9 +41,9 @@
no Recommenders -
-
- +
+
+
@@ -54,7 +54,7 @@
-
+
@@ -111,7 +111,7 @@
-
diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java index 3ec92ac04de..763678c7593 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/sidebar/ActiveLearningSidebar.java @@ -863,10 +863,7 @@ private void loadSuggestionInActiveLearningSidebar(AjaxRequestTarget aTarget, private Form createLearningHistory() { - Form learningHistoryForm = new Form(CID_LEARNING_HISTORY_FORM) - { - private static final long serialVersionUID = -961690443085882064L; - }; + var learningHistoryForm = new Form(CID_LEARNING_HISTORY_FORM); learningHistoryForm.add(LambdaBehavior.onConfigure( component -> component.setVisible(alStateModel.getObject().isSessionActive()))); learningHistoryForm.setOutputMarkupPlaceholderTag(true); @@ -878,8 +875,7 @@ private Form createLearningHistory() private ListView createLearningHistoryListView() { - ListView learningHistory = new ListView( - CID_HISTORY_LISTVIEW) + var learningHistory = new ListView(CID_HISTORY_LISTVIEW) { private static final long serialVersionUID = 5594228545985423567L; @@ -1385,7 +1381,7 @@ private void refreshAvailableSuggestions() public void onDocumentOpenedEvent(DocumentOpenedEvent aEvent) { // If active learning is not active, update the sidebar in case the session auto-terminated - ActiveLearningUserState alState = alStateModel.getObject(); + var alState = alStateModel.getObject(); if (!alState.isSessionActive()) { aEvent.getRequestTarget().ifPresent(target -> target.add(alMainContainer)); return; diff --git a/inception/inception-agreement/pom.xml b/inception/inception-agreement/pom.xml index 207c9037929..e906835d969 100644 --- a/inception/inception-agreement/pom.xml +++ b/inception/inception-agreement/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-agreement INCEpTION - Core - Agreement @@ -52,6 +52,10 @@ de.tudarmstadt.ukp.inception.app inception-support + + de.tudarmstadt.ukp.inception.app + inception-annotation-storage + de.tudarmstadt.ukp.inception.app inception-model @@ -60,6 +64,10 @@ de.tudarmstadt.ukp.inception.app inception-security + + de.tudarmstadt.ukp.inception.app + inception-scheduling + de.tudarmstadt.ukp.inception.app inception-project-api @@ -76,6 +84,10 @@ de.tudarmstadt.ukp.inception.app inception-support-bootstrap + + de.tudarmstadt.ukp.inception.app + inception-annotation-storage-api + org.dkpro.statistics @@ -86,6 +98,10 @@ org.apache.commons commons-csv + + commons-io + commons-io + @@ -110,10 +126,6 @@ org.apache.wicket wicket-core - - org.apache.wicket - wicket-util - org.apache.wicket wicket-spring @@ -126,6 +138,10 @@ com.googlecode.wicket-jquery-ui wicket-jquery-ui-core + + org.wicketstuff + wicketstuff-annotationeventdispatcher + org.danekja jdk-serializable-functional diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementSummary.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementSummary.java new file mode 100644 index 00000000000..c6cb5bd659d --- /dev/null +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementSummary.java @@ -0,0 +1,280 @@ +/* + * 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.clarin.webanno.agreement; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.FullUnitizingAgreementResult; + +public class AgreementSummary + implements Serializable +{ + private static final long serialVersionUID = 5994827594084192896L; + + private final String type; + private final String feature; + private final boolean excludeIncomplete; + private final List casGroupIds = new ArrayList<>(); + + private final List agreements = new ArrayList<>(); + private final Set categories = new LinkedHashSet<>(); + private final Map itemCounts = new HashMap<>(); + private final Map nonNullContentCounts = new HashMap<>(); + private final Map allNull = new HashMap<>(); + + private boolean empty; + + private int incompleteSetsByPosition; + private int incompleteSetsByLabel; + private int pluralitySets; + private int relevantSetCount; + private int completeSetCount; + + public AgreementSummary remap(Map aMapping) + { + return new AgreementSummary(this, aMapping); + } + + private AgreementSummary(AgreementSummary aSummary, Map aMapping) + { + type = aSummary.type; + feature = aSummary.feature; + excludeIncomplete = aSummary.excludeIncomplete; + + aSummary.casGroupIds.stream().map(aMapping::get).sorted().forEach(casGroupIds::add); + + agreements.addAll(aSummary.agreements); + categories.addAll(aSummary.categories); + + for (var e : aSummary.itemCounts.entrySet()) { + itemCounts.put(aMapping.get(e.getKey()), e.getValue()); + } + + for (var e : aSummary.nonNullContentCounts.entrySet()) { + nonNullContentCounts.put(aMapping.get(e.getKey()), e.getValue()); + } + + for (var e : aSummary.allNull.entrySet()) { + allNull.put(aMapping.get(e.getKey()), e.getValue()); + } + + empty = aSummary.empty; + + incompleteSetsByPosition = aSummary.incompleteSetsByPosition; + incompleteSetsByLabel = aSummary.incompleteSetsByLabel; + pluralitySets = aSummary.pluralitySets; + relevantSetCount = aSummary.relevantSetCount; + completeSetCount = aSummary.completeSetCount; + } + + public void merge(AgreementSummary aResult) + { + if (!type.equals(aResult.type)) { + throw new IllegalArgumentException("All merged results must have the same type [" + type + + "] but encountered [" + aResult.type + "]"); + } + + if (!feature.equals(aResult.feature)) { + throw new IllegalArgumentException("All merged results must have the same feature [" + + feature + "] but encountered [" + aResult.feature + "]"); + } + + if (excludeIncomplete != aResult.excludeIncomplete) { + throw new IllegalArgumentException( + "All merged results must have the same excludeIncomplete [" + excludeIncomplete + + "] but encountered [" + aResult.excludeIncomplete + "]"); + } + + if (!casGroupIds.equals(aResult.casGroupIds)) { + throw new IllegalArgumentException("All merged results must have the same casGroupIds " + + casGroupIds + " but encountered " + aResult.casGroupIds); + } + + agreements.addAll(aResult.agreements); + categories.addAll(aResult.categories); + + for (var e : aResult.itemCounts.entrySet()) { + itemCounts.merge(e.getKey(), e.getValue(), Long::sum); + } + + for (var e : aResult.nonNullContentCounts.entrySet()) { + nonNullContentCounts.merge(e.getKey(), e.getValue(), Long::sum); + } + + for (var e : aResult.allNull.entrySet()) { + allNull.merge(e.getKey(), e.getValue(), Boolean::logicalOr); + } + + empty |= aResult.empty; + + if (incompleteSetsByPosition >= 0 && aResult.incompleteSetsByPosition >= 0) { + incompleteSetsByPosition += aResult.incompleteSetsByPosition; + } + + if (incompleteSetsByLabel >= 0 && aResult.incompleteSetsByLabel >= 0) { + incompleteSetsByLabel += aResult.incompleteSetsByLabel; + } + + if (pluralitySets >= 0 && aResult.pluralitySets >= 0) { + pluralitySets += aResult.pluralitySets; + } + + if (relevantSetCount >= 0 && aResult.relevantSetCount >= 0) { + relevantSetCount += aResult.relevantSetCount; + } + + if (completeSetCount >= 0 && aResult.completeSetCount >= 0) { + completeSetCount += aResult.completeSetCount; + } + } + + public static AgreementSummary of(Serializable aResult) + { + if (aResult instanceof FullCodingAgreementResult result) { + return new AgreementSummary(result); + } + + if (aResult instanceof FullUnitizingAgreementResult result) { + return new AgreementSummary(result); + } + + throw new IllegalArgumentException( + "Unsupported result type: [" + aResult.getClass().getName() + "]"); + } + + public AgreementSummary(FullUnitizingAgreementResult aResult) + { + this((FullAgreementResult_ImplBase) aResult); + + incompleteSetsByLabel = -1; + incompleteSetsByPosition = -1; + relevantSetCount = -1; + completeSetCount = -1; + pluralitySets = -1; + } + + public AgreementSummary(FullCodingAgreementResult aResult) + { + this((FullAgreementResult_ImplBase) aResult); + + incompleteSetsByLabel = aResult.getIncompleteSetsByLabel().size(); + incompleteSetsByPosition = aResult.getIncompleteSetsByPosition().size(); + pluralitySets = aResult.getPluralitySets().size(); + relevantSetCount = aResult.getRelevantSetCount(); + completeSetCount = aResult.getCompleteSetCount(); + } + + private AgreementSummary(FullAgreementResult_ImplBase aResult) + { + type = aResult.getType(); + feature = aResult.getFeature(); + excludeIncomplete = aResult.isExcludeIncomplete(); + aResult.casGroupIds.stream().sorted().forEach(casGroupIds::add); + agreements.add(aResult.agreement); + aResult.getCategories().forEach(categories::add); + empty = aResult.isEmpty(); + + for (var casGroupId : casGroupIds) { + itemCounts.put(casGroupId, aResult.getItemCount(casGroupId)); + nonNullContentCounts.put(casGroupId, aResult.getNonNullCount(casGroupId)); + allNull.put(casGroupId, aResult.isAllNull(casGroupId)); + } + } + + public List getCasGroupIds() + { + return casGroupIds; + } + + public double getAgreement() + { + return agreements.stream().mapToDouble(a -> a).average().orElse(Double.NaN); + } + + public String getType() + { + return type; + } + + public String getFeature() + { + return feature; + } + + public boolean isExcludeIncomplete() + { + return excludeIncomplete; + } + + public boolean isEmpty() + { + return empty; + } + + public long getItemCount(String aRater) + { + return itemCounts.getOrDefault(aRater, 0l); + } + + public int getCategoryCount() + { + return categories.size(); + } + + public Long getNonNullCount(String aRater) + { + return nonNullContentCounts.getOrDefault(aRater, 0l); + } + + public boolean isAllNull(String aRater) + { + return allNull.getOrDefault(aRater, true); + } + + public int getIncompleteSetsByPosition() + { + return incompleteSetsByPosition; + } + + public int getIncompleteSetsByLabel() + { + return incompleteSetsByLabel; + } + + public int getRelevantSetCount() + { + return relevantSetCount; + } + + public int getCompleteSetCount() + { + return completeSetCount; + } + + public int getPluralitySets() + { + return pluralitySets; + } +} diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementUtils.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementUtils.java index 8727d8e51cc..9df5fb015f8 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementUtils.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementUtils.java @@ -18,85 +18,75 @@ package de.tudarmstadt.ukp.clarin.webanno.agreement; import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.getFeature; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; +import static org.apache.commons.csv.CSVFormat.RFC4180; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.io.output.CloseShieldOutputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.uima.cas.ArrayFS; import org.apache.uima.cas.CAS; import org.apache.uima.cas.FeatureStructure; -import org.apache.uima.cas.TypeSystem; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; import org.apache.uima.fit.util.FSUtil; -import org.dkpro.statistics.agreement.IAnnotationUnit; import org.dkpro.statistics.agreement.coding.CodingAnnotationStudy; -import org.dkpro.statistics.agreement.coding.ICodingAnnotationItem; import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.Configuration; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.ConfigurationSet; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.LinkCompareBehavior; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.Position; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.relation.RelationDiffAdapter; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.relation.RelationPosition; public class AgreementUtils { - public static CodingAgreementResult makeCodingStudy(CasDiff aDiff, String aType, + public static FullCodingAgreementResult makeCodingStudy(CasDiff aDiff, String aType, String aFeature, Set aTagSet, boolean aExcludeIncomplete, - Map> aCasMap) + Map aCasMap) { return makeCodingStudy(aDiff, aCasMap.keySet(), aType, aFeature, aTagSet, aExcludeIncomplete, true, aCasMap); } - private static CAS findSomeCas(Map> aCasMap) + private static CAS findSomeCas(Map aCasMap) { - for (List l : aCasMap.values()) { - if (l != null) { - for (CAS cas : l) { - if (cas != null) { - return cas; - } - } + for (var cas : aCasMap.values()) { + if (cas != null) { + return cas; } } return null; } - private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection aUsers, - String aType, String aFeature, Set aTagSet, boolean aExcludeIncomplete, - boolean aNullLabelsAsEmpty, Map> aCasMap) + private static FullCodingAgreementResult makeCodingStudy(CasDiff aDiff, + Collection aUsers, String aType, String aFeature, Set aTagSet, + boolean aExcludeIncomplete, boolean aNullLabelsAsEmpty, Map aCasMap) { - List users = new ArrayList<>(aUsers); - Collections.sort(users); + var users = aUsers.stream().sorted().toList(); - List completeSets = new ArrayList<>(); - List setsWithDifferences = new ArrayList<>(); - List incompleteSetsByPosition = new ArrayList<>(); - List incompleteSetsByLabel = new ArrayList<>(); - List pluralitySets = new ArrayList<>(); - List irrelevantSets = new ArrayList<>(); - CodingAnnotationStudy study = new CodingAnnotationStudy(users.size()); + var completeSets = new ArrayList(); + var setsWithDifferences = new ArrayList(); + var incompleteSetsByPosition = new ArrayList(); + var incompleteSetsByLabel = new ArrayList(); + var pluralitySets = new ArrayList(); + var irrelevantSets = new ArrayList(); + var study = new CodingAnnotationStudy(users.size()); if (aTagSet != null) { aTagSet.forEach(study::addCategory); @@ -105,17 +95,18 @@ private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection irrelevantSets.add(aDiff.getConfigurationSet(p))); - return new CodingAgreementResult(aType, aFeature, aDiff.toResult(), study, users, + return new FullCodingAgreementResult(aType, aFeature, aDiff.toResult(), study, users, completeSets, irrelevantSets, setsWithDifferences, incompleteSetsByPosition, incompleteSetsByLabel, pluralitySets, aExcludeIncomplete); } - TypeSystem ts = someCas.getTypeSystem(); + + var ts = someCas.getTypeSystem(); // This happens in our test cases when we feed the process with uninitialized CASes. // We should just do the right thing here which is: do nothing @@ -123,7 +114,7 @@ private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection irrelevantSets.add(aDiff.getConfigurationSet(p))); - return new CodingAgreementResult(aType, aFeature, aDiff.toResult(), study, users, + return new FullCodingAgreementResult(aType, aFeature, aDiff.toResult(), study, users, completeSets, irrelevantSets, setsWithDifferences, incompleteSetsByPosition, incompleteSetsByLabel, pluralitySets, aExcludeIncomplete); } @@ -134,11 +125,11 @@ private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection sourceCandidates = CasUtil.selectAt(arc.getCAS(), source.getType(), source.getBegin(), source.getEnd()); @@ -220,7 +209,7 @@ private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection targetCandidates = CasUtil.selectAt(arc.getCAS(), target.getType(), target.getBegin(), target.getEnd()); @@ -231,7 +220,7 @@ private static CodingAgreementResult makeCodingStudy(CasDiff aDiff, Collection) aFs .getFeatureValue(aFs.getType().getFeatureByBaseName(aFeature)); - FeatureStructure link = links.get(aLinkIndex); + var link = links.get(aLinkIndex); switch (aLCB) { case LINK_TARGET_AS_LABEL: - // FIXME The target feature name should be obtained from the feature - // definition! - AnnotationFS target = (AnnotationFS) link + // FIXME The target feature name should be obtained from the feature definition! + var target = (AnnotationFS) link .getFeatureValue(link.getType().getFeatureByBaseName("target")); return target.getBegin() + "-" + target.getEnd() + " [" + target.getCoveredText() + "]"; case LINK_ROLE_AS_LABEL: - // FIXME The role feature name should be obtained from the feature - // definition! - String role = link.getStringValue(link.getType().getFeatureByBaseName("role")); - - return role; + // FIXME The role feature name should be obtained from the feature definition! + return link.getStringValue(link.getType().getFeatureByBaseName("role")); default: throw new IllegalStateException("Unknown link target comparison mode [" + aLCB + "]"); } } - private static void toCSV(CSVPrinter aOut, CodingAgreementResult aAgreement) throws IOException + private static void toCSV(CSVPrinter aOut, FullCodingAgreementResult aAgreement, + boolean aAddHeader) + throws IOException { try { - aOut.printComment(String.format("Category count: %d%n", - aAgreement.getStudy().getCategoryCount())); + aOut.printComment(String.format("Category count: %d%n", aAgreement.getCategoryCount())); } catch (Throwable e) { aOut.printComment( @@ -353,7 +339,7 @@ private static void toCSV(CSVPrinter aOut, CodingAgreementResult aAgreement) thr String.format("Relevant position count: %d%n", aAgreement.getRelevantSetCount())); // aOut.printf("%n== Complete sets: %d ==%n", aAgreement.getCompleteSets().size()); - configurationSetsWithItemsToCsv(aOut, aAgreement, aAgreement.getCompleteSets()); + configurationSetsWithItemsToCsv(aOut, aAgreement, aAgreement.getCompleteSets(), aAddHeader); // // aOut.printf("%n== Incomplete sets (by position): %d == %n", // aAgreement.getIncompleteSetsByPosition().size()); @@ -368,10 +354,10 @@ private static void toCSV(CSVPrinter aOut, CodingAgreementResult aAgreement) thr // dumpAgreementConfigurationSets(aOut, aAgreement, aAgreement.getPluralitySets()); } - public static void dumpAgreementStudy(PrintStream aOut, CodingAgreementResult aAgreement) + public static void dumpAgreementStudy(PrintStream aOut, FullCodingAgreementResult aAgreement) { try { - aOut.printf("Category count: %d%n", aAgreement.getStudy().getCategoryCount()); + aOut.printf("Category count: %d%n", aAgreement.getCategoryCount()); } catch (Throwable e) { aOut.printf("Category count: %s%n", ExceptionUtils.getRootCauseMessage(e)); @@ -401,25 +387,28 @@ public static void dumpAgreementStudy(PrintStream aOut, CodingAgreementResult aA } private static void configurationSetsWithItemsToCsv(CSVPrinter aOut, - AgreementResult aAgreement, List aSets) + FullAgreementResult_ImplBase aAgreement, + List aSets, boolean aIncludeHeader) throws IOException { - List headers = new ArrayList<>( - asList("Type", "Collection", "Document", "Layer", "Feature", "Position")); - headers.addAll(aAgreement.getCasGroupIds()); - aOut.printRecord(headers); + if (aIncludeHeader) { + var headers = new ArrayList<>( + asList("Type", "Collection", "Document", "Layer", "Feature", "Position")); + headers.addAll(aAgreement.getCasGroupIds()); + aOut.printRecord(headers); + } int i = 0; - for (ICodingAnnotationItem item : aAgreement.getStudy().getItems()) { - Position pos = aSets.get(i).getPosition(); - List values = new ArrayList<>(); + for (var item : aAgreement.getStudy().getItems()) { + var pos = aSets.get(i).getPosition(); + var values = new ArrayList(); values.add(pos.getClass().getSimpleName()); values.add(pos.getCollectionId()); values.add(pos.getDocumentId()); values.add(pos.getType()); values.add(aAgreement.getFeature()); values.add(aSets.get(i).getPosition().toMinimalString()); - for (IAnnotationUnit unit : item.getUnits()) { + for (var unit : item.getUnits()) { values.add(String.valueOf(unit.getCategory())); } aOut.printRecord(values); @@ -428,13 +417,14 @@ private static void configurationSetsWithItemsToCsv(CSVPrinter aOut, } private static void dumpAgreementConfigurationSetsWithItems(PrintStream aOut, - AgreementResult aAgreement, List aSets) + FullAgreementResult_ImplBase aAgreement, + List aSets) { int i = 0; - for (ICodingAnnotationItem item : aAgreement.getStudy().getItems()) { - StringBuilder sb = new StringBuilder(); + for (var item : aAgreement.getStudy().getItems()) { + var sb = new StringBuilder(); sb.append(aSets.get(i).getPosition()); - for (IAnnotationUnit unit : item.getUnits()) { + for (var unit : item.getUnits()) { if (sb.length() > 0) { sb.append(" \t"); } @@ -446,9 +436,10 @@ private static void dumpAgreementConfigurationSetsWithItems(PrintStream aOut, } private static void dumpAgreementConfigurationSets(PrintStream aOut, - AgreementResult aAgreement, List aSets) + FullAgreementResult_ImplBase aAgreement, + List aSets) { - for (ConfigurationSet cfgSet : aSets) { + for (var cfgSet : aSets) { StringBuilder sb = new StringBuilder(); sb.append(cfgSet.getPosition()); for (Configuration cfg : cfgSet.getConfigurations()) { @@ -461,14 +452,13 @@ private static void dumpAgreementConfigurationSets(PrintStream aOut, } } - public static InputStream generateCsvReport(CodingAgreementResult aResult) throws IOException + public static void generateCsvReport(OutputStream aOut, FullCodingAgreementResult aResult, + boolean aIncludeHeader) + throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - try (CSVPrinter printer = new CSVPrinter(new OutputStreamWriter(buf, "UTF-8"), - CSVFormat.RFC4180)) { - toCSV(printer, aResult); + try (var printer = new CSVPrinter( + new OutputStreamWriter(CloseShieldOutputStream.wrap(aOut), UTF_8), RFC4180)) { + toCSV(printer, aResult, aIncludeHeader); } - - return new ByteArrayInputStream(buf.toByteArray()); } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementResult.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/FullAgreementResult_ImplBase.java similarity index 74% rename from inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementResult.java rename to inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/FullAgreementResult_ImplBase.java index 7a5a795e09d..58f2ceda190 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/AgreementResult.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/FullAgreementResult_ImplBase.java @@ -25,9 +25,11 @@ import org.dkpro.statistics.agreement.IAnnotationStudy; -public abstract class AgreementResult +public abstract class FullAgreementResult_ImplBase implements Serializable { + private static final long serialVersionUID = 8969153082599376637L; + protected final String type; protected final String feature; protected final T study; @@ -36,8 +38,8 @@ public abstract class AgreementResult protected double agreement; - public AgreementResult(String aType, String aFeature, T aStudy, List aCasGroupIds, - boolean aExcludeIncomplete) + public FullAgreementResult_ImplBase(String aType, String aFeature, T aStudy, + List aCasGroupIds, boolean aExcludeIncomplete) { type = aType; feature = aFeature; @@ -80,4 +82,22 @@ public boolean isExcludeIncomplete() { return excludeIncomplete; } + + public abstract boolean isEmpty(); + + public abstract long getItemCount(String aRater); + + public int getCategoryCount() + { + return study.getCategoryCount(); + } + + public abstract boolean isAllNull(String aCasGroupId); + + public abstract long getNonNullCount(String aCasGroupId); + + public Iterable getCategories() + { + return study.getCategories(); + } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/PairwiseAnnotationResult.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/PairwiseAnnotationResult.java index a634f024a52..a92df757f93 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/PairwiseAnnotationResult.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/PairwiseAnnotationResult.java @@ -26,20 +26,19 @@ import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; -public class PairwiseAnnotationResult +public class PairwiseAnnotationResult implements Serializable { private static final long serialVersionUID = -6943850667308982795L; private final Set raters = new TreeSet<>(); - private final Map results = new HashMap<>(); + private final Map results = new HashMap<>(); private final AnnotationFeature feature; private final DefaultAgreementTraits traits; public PairwiseAnnotationResult(AnnotationFeature aFeature, DefaultAgreementTraits aTraits) { - super(); feature = aFeature; traits = aTraits; } @@ -59,16 +58,23 @@ public Set getRaters() return raters; } - public R getStudy(String aKey1, String aKey2) + public AgreementSummary getResult(String aAnnotator1, String aAnnotator2) { - return results.get(makeKey(aKey1, aKey2)); + return results.get(makeKey(aAnnotator1, aAnnotator2)); } - public void add(String aKey1, String aKey2, R aRes) + public void mergeResult(String aAnnotator1, String aAnnotator2, AgreementSummary aRes) { - raters.add(aKey1); - raters.add(aKey2); - results.put(makeKey(aKey1, aKey2), aRes); + raters.add(aAnnotator1); + raters.add(aAnnotator2); + + var existingRes = getResult(aAnnotator1, aAnnotator2); + if (existingRes != null) { + existingRes.merge(aRes); + } + else { + results.put(makeKey(aAnnotator1, aAnnotator2), aRes); + } } private String makeKey(String aKey1, String aKey2) diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasure.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasure.java index 9b86655a5fb..6c9107557f5 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasure.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasure.java @@ -18,7 +18,6 @@ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures; import java.io.Serializable; -import java.util.List; import java.util.Map; import org.apache.uima.cas.CAS; @@ -27,7 +26,7 @@ public interface AgreementMeasure { - R getAgreement(Map> aCasMap); + R getAgreement(Map aCasMap); AnnotationFeature getFeature(); diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport.java index 75eb84d10d3..b0999b79927 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport.java @@ -17,23 +17,19 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures; -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import org.apache.uima.cas.CAS; import org.apache.wicket.markup.html.panel.EmptyPanel; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.danekja.java.util.function.serializable.SerializableSupplier; import org.dkpro.statistics.agreement.IAnnotationStudy; import org.springframework.beans.factory.BeanNameAware; +import de.tudarmstadt.ukp.clarin.webanno.agreement.FullAgreementResult_ImplBase; +import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; public interface AgreementMeasureSupport, // S extends IAnnotationStudy> extends BeanNameAware { @@ -71,6 +67,5 @@ default Panel createTraitsEditor(String aId, IModel aFeature, T createTraits(); - Panel createResultsPanel(String aId, IModel aResults, - SerializableSupplier>> aCasMapSupplier); + Panel createResultsPanel(String aId, IModel aResults); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport_ImplBase.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport_ImplBase.java index 91164ec1c7e..ebee0bfea50 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport_ImplBase.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureSupport_ImplBase.java @@ -17,17 +17,16 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures; -import java.io.Serializable; - import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; import org.dkpro.statistics.agreement.IAnnotationStudy; +import de.tudarmstadt.ukp.clarin.webanno.agreement.FullAgreementResult_ImplBase; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; public abstract class AgreementMeasureSupport_ImplBase, // S extends IAnnotationStudy> implements AgreementMeasureSupport { diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasure.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasure.java index aefe94f6ff1..be30be11bb1 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasure.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasure.java @@ -24,19 +24,14 @@ import static java.util.stream.Collectors.toCollection; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.uima.cas.CAS; -import org.dkpro.statistics.agreement.IAgreementMeasure; import org.dkpro.statistics.agreement.coding.CohenKappaAgreement; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementMeasure_ImplBase; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.Tag; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -54,22 +49,21 @@ public CohenKappaAgreementMeasure(AnnotationFeature aFeature, DefaultAgreementTr } @Override - public CodingAgreementResult calculatePairAgreement(Map> aCasMap) + public FullCodingAgreementResult getAgreement(Map aCasMap) { - AnnotationFeature feature = getFeature(); - DefaultAgreementTraits traits = getTraits(); + var feature = getFeature(); + var traits = getTraits(); - List adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); + var adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); - CasDiff diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); + var diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); - Set tagset = annotationService.listTags(feature.getTagset()).stream() - .map(Tag::getName).collect(toCollection(LinkedHashSet::new)); + var tagset = annotationService.listTags(feature.getTagset()).stream() // + .map(Tag::getName) // + .collect(toCollection(LinkedHashSet::new)); - CodingAgreementResult agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), - feature.getName(), tagset, true, aCasMap); - - IAgreementMeasure agreement = new CohenKappaAgreement(agreementResult.getStudy()); + var agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), feature.getName(), + tagset, true, aCasMap); if (agreementResult.getStudy().getItemCount() == 0) { agreementResult.setAgreement(Double.NaN); @@ -78,7 +72,8 @@ else if (agreementResult.getObservedCategories().size() == 1) { agreementResult.setAgreement(1.0d); } else { - agreementResult.setAgreement(agreement.calculateAgreement()); + var measure = new CohenKappaAgreement(agreementResult.getStudy()); + agreementResult.setAgreement(measure.calculateAgreement()); } return agreementResult; diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasureSupport.java index 413c889ce37..2433a89a457 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/cohenkappa/CohenKappaAgreementMeasureSupport.java @@ -19,11 +19,10 @@ import org.springframework.stereotype.Component; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.AbstractCodingAgreementMeasureSupport; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -35,7 +34,6 @@ public class CohenKappaAgreementMeasureSupport public CohenKappaAgreementMeasureSupport(AnnotationSchemaService aAnnotationService) { - super(); annotationService = aAnnotationService; } @@ -46,8 +44,8 @@ public String getName() } @Override - public AgreementMeasure> createMeasure( - AnnotationFeature aFeature, DefaultAgreementTraits aTraits) + public AgreementMeasure createMeasure(AnnotationFeature aFeature, + DefaultAgreementTraits aTraits) { return new CohenKappaAgreementMeasure(aFeature, aTraits, annotationService); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasure.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasure.java index 27c67518e3a..f8d7730b0ae 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasure.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasure.java @@ -1,5 +1,5 @@ /* - * Licensed to the Technische Universität Darmstadt under one +# * 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 @@ -24,9 +24,7 @@ import static java.util.stream.Collectors.toCollection; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.uima.cas.CAS; import org.dkpro.statistics.agreement.coding.FleissKappaAgreement; @@ -34,9 +32,7 @@ import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementMeasure_ImplBase; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.Tag; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -54,32 +50,31 @@ public FleissKappaAgreementMeasure(AnnotationFeature aFeature, DefaultAgreementT } @Override - public CodingAgreementResult calculatePairAgreement(Map> aCasMap) + public FullCodingAgreementResult getAgreement(Map aCasMap) { - AnnotationFeature feature = getFeature(); - DefaultAgreementTraits traits = getTraits(); + var feature = getFeature(); + var traits = getTraits(); - List adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); + var adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); - CasDiff diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); + var diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); - Set tagset = annotationService.listTags(feature.getTagset()).stream() - .map(Tag::getName).collect(toCollection(LinkedHashSet::new)); + var tagset = annotationService.listTags(feature.getTagset()).stream() // + .map(Tag::getName) // + .collect(toCollection(LinkedHashSet::new)); - CodingAgreementResult agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), - feature.getName(), tagset, true, aCasMap); + var agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), feature.getName(), + tagset, true, aCasMap); - InspectableFleissKappaAgreement agreement = new InspectableFleissKappaAgreement( - agreementResult.getStudy()); - - if (agreementResult.getStudy().getItemCount() == 0) { + if (agreementResult.isEmpty()) { agreementResult.setAgreement(Double.NaN); } else if (agreementResult.getObservedCategories().size() == 1) { agreementResult.setAgreement(1.0d); } else { - agreementResult.setAgreement(agreement.calculateAgreement()); + var measure = new InspectableFleissKappaAgreement(agreementResult.getStudy()); + agreementResult.setAgreement(measure.calculateAgreement()); } return agreementResult; diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasureSupport.java index 857d3857dfa..19553554d68 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/fleisskappa/FleissKappaAgreementMeasureSupport.java @@ -19,11 +19,10 @@ import org.springframework.stereotype.Component; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.AbstractCodingAgreementMeasureSupport; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -46,8 +45,8 @@ public String getName() } @Override - public AgreementMeasure> createMeasure( - AnnotationFeature aFeature, DefaultAgreementTraits aTraits) + public AgreementMeasure createMeasure(AnnotationFeature aFeature, + DefaultAgreementTraits aTraits) { return new FleissKappaAgreementMeasure(aFeature, aTraits, annotationService); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasure.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasure.java index 55c551d4a73..7cca27ac3f3 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasure.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasure.java @@ -25,20 +25,15 @@ import static java.util.stream.Collectors.toCollection; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.uima.cas.CAS; -import org.dkpro.statistics.agreement.IAgreementMeasure; import org.dkpro.statistics.agreement.InsufficientDataException; import org.dkpro.statistics.agreement.coding.KrippendorffAlphaAgreement; import org.dkpro.statistics.agreement.distance.NominalDistanceFunction; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementMeasure_ImplBase; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.Tag; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -56,22 +51,42 @@ public KrippendorffAlphaAgreementMeasure(AnnotationFeature aFeature, } @Override - public CodingAgreementResult calculatePairAgreement(Map> aCasMap) + public FullCodingAgreementResult getAgreement(Map aCasMap) { - AnnotationFeature feature = getFeature(); - KrippendorffAlphaAgreementTraits traits = getTraits(); + var feature = getFeature(); + var traits = getTraits(); - List adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); + var adapters = getDiffAdapters(annotationService, asList(feature.getLayer())); - CasDiff diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); + var diff = doDiff(adapters, traits.getLinkCompareBehavior(), aCasMap); - Set tagset = annotationService.listTags(feature.getTagset()).stream() - .map(Tag::getName).collect(toCollection(LinkedHashSet::new)); + var tagset = annotationService.listTags(feature.getTagset()).stream() // + .map(Tag::getName) // + .collect(toCollection(LinkedHashSet::new)); - CodingAgreementResult agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), - feature.getName(), tagset, traits.isExcludeIncomplete(), aCasMap); + var agreementResult = makeCodingStudy(diff, feature.getLayer().getName(), feature.getName(), + tagset, traits.isExcludeIncomplete(), aCasMap); - IAgreementMeasure agreement = new KrippendorffAlphaAgreement(agreementResult.getStudy(), + var measure = createMeasure(agreementResult); + + if (agreementResult.isEmpty()) { + agreementResult.setAgreement(NaN); + } + else { + try { + agreementResult.setAgreement(measure.calculateAgreement()); + } + catch (InsufficientDataException e) { + agreementResult.setAgreement(NaN); + } + } + + return agreementResult; + } + + private KrippendorffAlphaAgreement createMeasure(FullCodingAgreementResult agreementResult) + { + return new KrippendorffAlphaAgreement(agreementResult.getStudy(), new NominalDistanceFunction()) { @Override @@ -91,19 +106,5 @@ public double calculateAgreement() return 1.0 - (D_O / D_E); } }; - - if (agreementResult.getStudy().getItemCount() > 0) { - try { - agreementResult.setAgreement(agreement.calculateAgreement()); - } - catch (InsufficientDataException e) { - agreementResult.setAgreement(NaN); - } - } - else { - agreementResult.setAgreement(NaN); - } - - return agreementResult; } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasureSupport.java index 7aa167dd7da..33951c2cabb 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalpha/KrippendorffAlphaAgreementMeasureSupport.java @@ -21,10 +21,9 @@ import org.apache.wicket.model.IModel; import org.springframework.stereotype.Component; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.AbstractCodingAgreementMeasureSupport; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -47,8 +46,8 @@ public String getName() } @Override - public AgreementMeasure> createMeasure( - AnnotationFeature aFeature, KrippendorffAlphaAgreementTraits aTraits) + public AgreementMeasure createMeasure(AnnotationFeature aFeature, + KrippendorffAlphaAgreementTraits aTraits) { return new KrippendorffAlphaAgreementMeasure(aFeature, aTraits, annotationService); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasure.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasure.java index 408968e24cc..5570eb53b60 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasure.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasure.java @@ -18,136 +18,80 @@ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalphaunitizing; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.Feature; -import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.FSUtil; -import org.dkpro.statistics.agreement.IAgreementMeasure; import org.dkpro.statistics.agreement.unitizing.KrippendorffAlphaUnitizingAgreement; import org.dkpro.statistics.agreement.unitizing.UnitizingAnnotationStudy; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure_ImplBase; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.UnitizingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.FullUnitizingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; -import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; public class KrippendorffAlphaUnitizingAgreementMeasure extends AgreementMeasure_ImplBase, // + FullUnitizingAgreementResult, // KrippendorffAlphaUnitizingAgreementTraits> { - private final AnnotationSchemaService annotationService; - public KrippendorffAlphaUnitizingAgreementMeasure(AnnotationFeature aFeature, - KrippendorffAlphaUnitizingAgreementTraits aTraits, - AnnotationSchemaService aAnnotationService) + KrippendorffAlphaUnitizingAgreementTraits aTraits) { super(aFeature, aTraits); - annotationService = aAnnotationService; } @Override - public PairwiseAnnotationResult getAgreement( - Map> aCasMap) - { - PairwiseAnnotationResult result = new PairwiseAnnotationResult<>( - getFeature(), getTraits()); - List>> entryList = new ArrayList<>(aCasMap.entrySet()); - for (int m = 0; m < entryList.size(); m++) { - for (int n = 0; n < entryList.size(); n++) { - // Triangle matrix mirrored - if (n < m) { - Map> pairwiseCasMap = new LinkedHashMap<>(); - pairwiseCasMap.put(entryList.get(m).getKey(), entryList.get(m).getValue()); - pairwiseCasMap.put(entryList.get(n).getKey(), entryList.get(n).getValue()); - UnitizingAgreementResult res = calculatePairAgreement(pairwiseCasMap); - result.add(entryList.get(m).getKey(), entryList.get(n).getKey(), res); - } - } - } - return result; - } - - public UnitizingAgreementResult calculatePairAgreement(Map> aCasMap) + public FullUnitizingAgreementResult getAgreement(Map aCasMap) { - String typeName = getFeature().getLayer().getName(); + var typeName = getFeature().getLayer().getName(); - // Calculate a character offset continuum over all CASses. We assume here that the documents + // Calculate a character offset continuum. We assume here that the documents // all have the same size - since the users cannot change the document sizes, this should be // an universally true assumption. - List firstUserCasses = aCasMap.values().stream().findFirst().get(); - int docCount = firstUserCasses.size(); - int[] docSizes = new int[docCount]; - Arrays.fill(docSizes, 0); - for (Entry> set : aCasMap.entrySet()) { - int i = 0; - for (CAS cas : set.getValue()) { - if (cas != null) { - assert docSizes[i] == 0 || docSizes[i] == cas.getDocumentText().length(); - - docSizes[i] = cas.getDocumentText().length(); - } - i++; - } - } - int continuumSize = Arrays.stream(docSizes).sum(); + var someCas = aCasMap.values().stream().filter(Objects::nonNull).findAny().get(); // Create a unitizing study for that continuum. - UnitizingAnnotationStudy study = new UnitizingAnnotationStudy(continuumSize); + var study = new UnitizingAnnotationStudy(someCas.getDocumentText().length()); // For each annotator, extract the feature values from all the annotator's CASses and add // them to the unitizing study based on character offsets. - for (Entry> set : aCasMap.entrySet()) { + for (Entry set : aCasMap.entrySet()) { int raterIdx = study.addRater(set.getKey()); - int docOffset = 0; - int i = 0; - for (CAS cas : set.getValue()) { - // If a user has never worked on a source document, its CAS is null here - we - // skip it. - if (cas != null) { - Type t = cas.getTypeSystem().getType(typeName); - Feature f = t.getFeatureByBaseName(getFeature().getName()); - int currentDocOffset = docOffset; - cas.select(t).map(fs -> (AnnotationFS) fs).forEach(fs -> { - Object featureValue = FSUtil.getFeature(fs, f, Object.class); - if (featureValue instanceof Collection) { - for (Object value : (Collection) featureValue) { - study.addUnit(currentDocOffset + fs.getBegin(), - fs.getEnd() - fs.getBegin(), raterIdx, value); - } + var cas = set.getValue(); + // If a user has never worked on a source document, its CAS is null here - we + // skip it. + if (cas != null) { + var t = cas.getTypeSystem().getType(typeName); + var f = t.getFeatureByBaseName(getFeature().getName()); + cas.select(t).map(fs -> (AnnotationFS) fs).forEach(fs -> { + var featureValue = FSUtil.getFeature(fs, f, Object.class); + if (featureValue instanceof Collection) { + for (var value : (Collection) featureValue) { + study.addUnit(fs.getBegin(), fs.getEnd() - fs.getBegin(), raterIdx, + value); } - else { - study.addUnit(currentDocOffset + fs.getBegin(), - fs.getEnd() - fs.getBegin(), raterIdx, featureValue); - } - }); - } - - docOffset += docSizes[i]; - i++; + } + else { + study.addUnit(fs.getBegin(), fs.getEnd() - fs.getBegin(), raterIdx, + featureValue); + } + }); } } - UnitizingAgreementResult result = new UnitizingAgreementResult(typeName, - getFeature().getName(), study, new ArrayList<>(aCasMap.keySet()), - getTraits().isExcludeIncomplete()); + var result = new FullUnitizingAgreementResult(typeName, getFeature().getName(), study, + new ArrayList<>(aCasMap.keySet()), getTraits().isExcludeIncomplete()); - IAgreementMeasure agreement = new KrippendorffAlphaUnitizingAgreement(study); - - if (result.getStudy().getUnitCount() > 0) { - result.setAgreement(agreement.calculateAgreement()); + if (result.isEmpty()) { + result.setAgreement(Double.NaN); } else { - result.setAgreement(Double.NaN); + var measure = new KrippendorffAlphaUnitizingAgreement(study); + result.setAgreement(measure.calculateAgreement()); } return result; diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasureSupport.java index 551ca6d638f..f74e20fb5f3 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/krippendorffalphaunitizing/KrippendorffAlphaUnitizingAgreementMeasureSupport.java @@ -17,21 +17,16 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalphaunitizing; -import java.util.List; -import java.util.Map; - -import org.apache.uima.cas.CAS; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.danekja.java.util.function.serializable.SerializableSupplier; import org.dkpro.statistics.agreement.unitizing.IUnitizingAnnotationStudy; import org.springframework.stereotype.Component; import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasureSupport_ImplBase; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.FullUnitizingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.PairwiseUnitizingAgreementTable; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.UnitizingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -40,16 +35,13 @@ public class KrippendorffAlphaUnitizingAgreementMeasureSupport extends AgreementMeasureSupport_ImplBase, // + FullUnitizingAgreementResult, // IUnitizingAnnotationStudy> { - private final AnnotationSchemaService annotationService; - public KrippendorffAlphaUnitizingAgreementMeasureSupport( AnnotationSchemaService aAnnotationService) { super(); - annotationService = aAnnotationService; } @Override @@ -71,10 +63,10 @@ public boolean accepts(AnnotationFeature aFeature) } @Override - public AgreementMeasure> createMeasure( - AnnotationFeature aFeature, KrippendorffAlphaUnitizingAgreementTraits aTraits) + public AgreementMeasure createMeasure(AnnotationFeature aFeature, + KrippendorffAlphaUnitizingAgreementTraits aTraits) { - return new KrippendorffAlphaUnitizingAgreementMeasure(aFeature, aTraits, annotationService); + return new KrippendorffAlphaUnitizingAgreementMeasure(aFeature, aTraits); } @Override @@ -91,9 +83,7 @@ public KrippendorffAlphaUnitizingAgreementTraits createTraits() } @Override - public Panel createResultsPanel(String aId, - IModel> aResults, - SerializableSupplier>> aCasMapSupplier) + public Panel createResultsPanel(String aId, IModel aResults) { return new PairwiseUnitizingAgreementTable(aId, aResults); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/AbstractCodingAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/AbstractCodingAgreementMeasureSupport.java index b838f0fa998..beeb8b98437 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/AbstractCodingAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/AbstractCodingAgreementMeasureSupport.java @@ -23,13 +23,8 @@ import static de.tudarmstadt.ukp.clarin.webanno.model.LinkMode.NONE; import static java.util.Arrays.asList; -import java.util.List; -import java.util.Map; - -import org.apache.uima.cas.CAS; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.danekja.java.util.function.serializable.SerializableSupplier; import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; @@ -42,8 +37,7 @@ import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; public abstract class AbstractCodingAgreementMeasureSupport - extends - AgreementMeasureSupport_ImplBase, ICodingAnnotationStudy> + extends AgreementMeasureSupport_ImplBase { @Override public boolean accepts(AnnotationFeature aFeature) @@ -59,10 +53,8 @@ && asList(SINGLE_TOKEN, TOKENS, SENTENCES).contains(layer.getAnchoringMode()) } @Override - public Panel createResultsPanel(String aId, - IModel> aResults, - SerializableSupplier>> aCasMapSupplier) + public Panel createResultsPanel(String aId, IModel aResults) { - return new PairwiseCodingAgreementTable(aId, aResults, aCasMapSupplier); + return new PairwiseCodingAgreementTable(aId, aResults); } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementMeasure_ImplBase.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementMeasure_ImplBase.java index 3208df6d91c..e56bcb00da5 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementMeasure_ImplBase.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementMeasure_ImplBase.java @@ -17,48 +17,15 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.uima.cas.CAS; - -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure_ImplBase; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; public abstract class CodingAgreementMeasure_ImplBase - extends AgreementMeasure_ImplBase, T> + extends AgreementMeasure_ImplBase { public CodingAgreementMeasure_ImplBase(AnnotationFeature aFeature, T aTraits) { super(aFeature, aTraits); } - - @Override - public PairwiseAnnotationResult getAgreement( - Map> aCasMap) - { - PairwiseAnnotationResult result = new PairwiseAnnotationResult<>( - getFeature(), getTraits()); - List>> entryList = new ArrayList<>(aCasMap.entrySet()); - for (int m = 0; m < entryList.size(); m++) { - for (int n = 0; n < entryList.size(); n++) { - // Triangle matrix mirrored - if (n < m) { - Map> pairwiseCasMap = new LinkedHashMap<>(); - pairwiseCasMap.put(entryList.get(m).getKey(), entryList.get(m).getValue()); - pairwiseCasMap.put(entryList.get(n).getKey(), entryList.get(n).getValue()); - CodingAgreementResult res = calculatePairAgreement(pairwiseCasMap); - result.add(entryList.get(m).getKey(), entryList.get(n).getKey(), res); - } - } - } - return result; - } - - public abstract CodingAgreementResult calculatePairAgreement(Map> aCasMap); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementResult.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/FullCodingAgreementResult.java similarity index 81% rename from inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementResult.java rename to inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/FullCodingAgreementResult.java index f3017a229ba..1a396425f98 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/CodingAgreementResult.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/FullCodingAgreementResult.java @@ -28,12 +28,12 @@ import org.dkpro.statistics.agreement.coding.ICodingAnnotationItem; import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.FullAgreementResult_ImplBase; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.ConfigurationSet; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; -public class CodingAgreementResult - extends AgreementResult +public class FullCodingAgreementResult + extends FullAgreementResult_ImplBase { private static final long serialVersionUID = -1262324752699430461L; @@ -45,7 +45,7 @@ public class CodingAgreementResult protected final List incompleteSetsByLabel; protected final List pluralitySets; - public CodingAgreementResult(String aType, String aFeature, DiffResult aDiff, + public FullCodingAgreementResult(String aType, String aFeature, DiffResult aDiff, ICodingAnnotationStudy aStudy, List aCasGroupIds, List aComplete, List aIrrelevantSets, List aSetsWithDifferences, @@ -118,7 +118,7 @@ public int getUnusableSetCount() + pluralitySets.size(); } - public Object getCompleteSetCount() + public int getCompleteSetCount() { return completeSets.size(); } @@ -141,7 +141,7 @@ public DiffResult getDiff() public Set getObservedCategories() { Set observedCategories = new HashSet<>(); - for (ICodingAnnotationItem item : getStudy().getItems()) { + for (ICodingAnnotationItem item : study.getItems()) { for (IAnnotationUnit unit : item.getUnits()) { Object category = unit.getCategory(); if (category != null) { @@ -152,6 +152,41 @@ public Set getObservedCategories() return observedCategories; } + @Override + public boolean isAllNull(String aCasGroupId) + { + for (ICodingAnnotationItem item : study.getItems()) { + if (item.getUnit(getCasGroupIds().indexOf(aCasGroupId)).getCategory() != null) { + return false; + } + } + return true; + } + + @Override + public long getNonNullCount(String aCasGroupId) + { + long i = 0; + for (var item : study.getItems()) { + if (item.getUnit(getCasGroupIds().indexOf(aCasGroupId)).getCategory() != null) { + i++; + } + } + return i; + } + + @Override + public boolean isEmpty() + { + return study.getItemCount() == 0; + } + + @Override + public long getItemCount(String aRater) + { + return study.getItemCount(); + } + @Override public String toString() { diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.html b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.html index 13f7b6aa45b..46490e3b519 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.html +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.html @@ -20,9 +20,11 @@
- - - +
+ +
@@ -33,14 +35,15 @@
+
diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.java index 276257b4e68..b49abf8752f 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/PairwiseCodingAgreementTable.java @@ -17,36 +17,21 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding; -import static de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementReportExportFormat.CSV; -import static de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementUtils.makeCodingStudy; -import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; -import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.enabledWhen; +import static de.tudarmstadt.ukp.inception.support.lambda.HtmlElementEvents.CLICK; import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.visibleWhen; -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toCollection; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; +import static org.apache.wicket.event.Broadcast.BUBBLE; + +import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.uima.cas.CAS; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.behavior.Behavior; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.DropDownChoice; -import org.apache.wicket.markup.html.form.EnumChoiceRenderer; import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.markup.html.panel.GenericPanel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.RefreshingView; import org.apache.wicket.model.IModel; @@ -55,107 +40,78 @@ import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.apache.wicket.util.resource.AbstractResourceStream; -import org.apache.wicket.util.resource.IResourceStream; -import org.apache.wicket.util.resource.ResourceStreamNotFoundException; -import org.danekja.java.util.function.serializable.SerializableSupplier; -import org.dkpro.statistics.agreement.coding.ICodingAnnotationItem; -import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig; import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig.Placement; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementReportExportFormat; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementUtils; import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; -import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; -import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; -import de.tudarmstadt.ukp.clarin.webanno.model.Tag; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.event.PairwiseAgreementScoreClickedEvent; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; +import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.bootstrap.PopoverBehavior; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.project.api.ProjectService; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; -import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxFormComponentUpdatingBehavior; -import de.tudarmstadt.ukp.inception.support.wicket.AjaxDownloadBehavior; -import de.tudarmstadt.ukp.inception.support.wicket.AjaxDownloadLink; import de.tudarmstadt.ukp.inception.support.wicket.DefaultRefreshingView; import de.tudarmstadt.ukp.inception.support.wicket.DescriptionTooltipBehavior; public class PairwiseCodingAgreementTable - extends Panel + extends GenericPanel { private static final long serialVersionUID = 571396822546125376L; - private final static Logger LOG = LoggerFactory.getLogger(PairwiseCodingAgreementTable.class); + private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @SpringBean AnnotationSchemaService annotationService; private @SpringBean DocumentService documentService; private @SpringBean ProjectService projectService; private @SpringBean UserDao userRepository; - private final RefreshingView rows; - private final AjaxDownloadLink exportAllButton; - private final DropDownChoice formatField; - - private final SerializableSupplier>> casMapSupplier; + private final RefreshingView rows; - public PairwiseCodingAgreementTable(String aId, - IModel> aModel, - SerializableSupplier>> aCasMapSupplier) + public PairwiseCodingAgreementTable(String aId, IModel aModel) { super(aId, aModel); - casMapSupplier = aCasMapSupplier; - setOutputMarkupId(true); - PopoverConfig config = new PopoverConfig().withPlacement(Placement.left).withHtml(true); - WebMarkupContainer legend = new WebMarkupContainer("legend"); + var config = new PopoverConfig().withPlacement(Placement.left).withHtml(true); + var legend = new WebMarkupContainer("legend"); legend.add(new PopoverBehavior(new ResourceModel("legend"), new StringResourceModel("legend.content", legend), config)); add(legend); // This model makes sure we add a "null" dummy rater which accounts for the header columns // of the table. - final IModel> ratersAdapter = LoadableDetachableModel.of(() -> { - List raters = new ArrayList<>(); + final IModel> ratersAdapter = LoadableDetachableModel.of(() -> { + var raters = new ArrayList(); if (getModelObject() != null) { raters.add(null); - raters.addAll(getModelObject().getRaters()); + for (var rater : getModelObject().getRaters()) { + var user = userRepository.get(rater); + if (user != null) { + raters.add(user); + } + } } return raters; }); - add(formatField = new DropDownChoice("exportFormat", - Model.of(CSV), asList(AgreementReportExportFormat.values()), - new EnumChoiceRenderer<>(this))); - formatField.add(new LambdaAjaxFormComponentUpdatingBehavior("change")); - - exportAllButton = new AjaxDownloadLink("exportAll", - () -> "agreement" + formatField.getModelObject().getExtension(), - this::exportAllAgreements); - exportAllButton.add(enabledWhen(() -> formatField.getModelObject() != null)); - add(exportAllButton); - - rows = new DefaultRefreshingView("rows", ratersAdapter) + rows = new DefaultRefreshingView("rows", ratersAdapter) { private static final long serialVersionUID = 1L; @Override - protected void populateItem(final Item aRowItem) + protected void populateItem(final Item aRowItem) { // Render regular row - aRowItem.add(new DefaultRefreshingView("cells", ratersAdapter) + aRowItem.add(new DefaultRefreshingView("cells", ratersAdapter) { private static final long serialVersionUID = 1L; @Override - protected void populateItem(Item aCellItem) + protected void populateItem(Item aCellItem) { aCellItem.setRenderBodyOnly(true); @@ -180,13 +136,11 @@ else if (aCellItem.getIndex() == 0) { } // Raters header horizontally else if (aRowItem.getIndex() == 0 && aCellItem.getIndex() != 0) { - cell.add(new Label("label", - userRepository.get(aCellItem.getModelObject()).getUiName())); + cell.add(new Label("label", aCellItem.getModelObject().getUiName())); } // Raters header vertically else if (aRowItem.getIndex() != 0 && aCellItem.getIndex() == 0) { - cell.add(new Label("label", - userRepository.get(aRowItem.getModelObject()).getUiName())); + cell.add(new Label("label", aRowItem.getModelObject().getUiName())); } // Upper diagonal else if (aCellItem.getIndex() > aRowItem.getIndex()) { @@ -211,59 +165,69 @@ else if (aCellItem.getIndex() < aRowItem.getIndex()) { (aRowItem.getIndex() % 2 == 0) ? "odd" : "even")); } }; + this.add(visibleWhen( () -> (getModelObject() != null && !getModelObject().getRaters().isEmpty()))); add(rows); } - private Label makeLowerDiagonalCellLabel(String aRater1, String aRater2) + private Label makeLowerDiagonalCellLabel(User aRater1, User aRater2) { - CodingAgreementResult result = getModelObject().getStudy(aRater1, aRater2); + var result = getModelObject().getResult(aRater1.getUsername(), aRater2.getUsername()); - String label = String.format("%d/%d", result.getCompleteSetCount(), + if (result == null) { + return new Label("label", "-"); + } + + var label = String.format("%d/%d", result.getCompleteSetCount(), result.getRelevantSetCount()); - String tooltipTitle = "Details about annotations excluded from agreement calculation"; + var tooltipTitle = "Details about annotations excluded from agreement calculation"; - StringBuilder tooltipContent = new StringBuilder(); + var tooltipContent = new StringBuilder(); if (result.isExcludeIncomplete()) { tooltipContent.append(String.format("- Incomplete (missing): %d%n", - result.getIncompleteSetsByPosition().size())); + result.getIncompleteSetsByPosition())); tooltipContent.append(String.format("- Incomplete (not labeled): %d%n", - result.getIncompleteSetsByLabel().size())); + result.getIncompleteSetsByLabel())); } - tooltipContent.append(String.format("- Plurality: %d", result.getPluralitySets().size())); + tooltipContent.append(String.format("- Plurality: %d", result.getPluralitySets())); - Label l = new Label("label", Model.of(label)); - DescriptionTooltipBehavior tooltip = new DescriptionTooltipBehavior(tooltipTitle, - tooltipContent.toString()); + var l = new Label("label", Model.of(label)); + var tooltip = new DescriptionTooltipBehavior(tooltipTitle, tooltipContent.toString()); tooltip.setOption("position", (Object) null); l.add(tooltip); l.add(new AttributeAppender("style", "cursor: help", ";")); return l; } - private Label makeUpperDiagonalCellLabel(String aRater1, String aRater2) + private Label makeUpperDiagonalCellLabel(User aRater1, User aRater2) { - CodingAgreementResult result = getModelObject().getStudy(aRater1, aRater2); + var result = getModelObject().getResult(aRater1.getUsername(), aRater2.getUsername()); + + if (result == null) { + return new Label("label", "no data"); + } - boolean noDataRater0 = isAllNull(result, result.getCasGroupIds().get(0)); - boolean noDataRater1 = isAllNull(result, result.getCasGroupIds().get(1)); - int incPos = result.getIncompleteSetsByPosition().size(); - int incLabel = result.getIncompleteSetsByLabel().size(); + var casGroupId1 = result.getCasGroupIds().get(0); + var casGroupId2 = result.getCasGroupIds().get(1); + var noDataRater1 = result.isAllNull(casGroupId1); + var noDataRater2 = result.isAllNull(casGroupId2); + var incPos = result.getIncompleteSetsByPosition(); + var incLabel = result.getIncompleteSetsByLabel(); String label; - if (result.getStudy().getItemCount() == 0) { + if (result.isEmpty()) { label = "no positions"; } - else if (noDataRater0 && noDataRater1) { + else if (noDataRater1 && noDataRater2) { label = "no labels"; } - else if (noDataRater0) { - label = "no labels from " + result.getCasGroupIds().get(0); - } else if (noDataRater1) { - label = "no labels from " + result.getCasGroupIds().get(1); + label = "no labels from " + aRater1.getUiName(); + } + else if (noDataRater2) { + label = "no labels from " + aRater2.getUiName(); } else if (incPos == result.getRelevantSetCount()) { label = "positions disjunct"; @@ -278,173 +242,30 @@ else if ((incLabel + incPos) == result.getRelevantSetCount()) { label = String.format("%.2f", result.getAgreement()); } - String tooltipTitle = result.getCasGroupIds().get(0) + '/' + result.getCasGroupIds().get(1); - - String tooltipContent = "Positions annotated:\n" - + String.format("- %s: %d/%d%n", result.getCasGroupIds().get(0), - getNonNullCount(result, result.getCasGroupIds().get(0)), - result.getStudy().getItemCount()) - + String.format("- %s: %d/%d%n", result.getCasGroupIds().get(1), - getNonNullCount(result, result.getCasGroupIds().get(1)), - result.getStudy().getItemCount()) - + String.format("Distinct labels: %d%n", result.getStudy().getCategoryCount()); - - Label l = new Label("label", Model.of(label)); - l.add(makeDownloadBehavior(aRater1, aRater2)); - DescriptionTooltipBehavior tooltip = new DescriptionTooltipBehavior(tooltipTitle, - tooltipContent); + var tooltipTitle = aRater1.getUiName() + " ↔ " + aRater2.getUiName(); + + var tooltipContent = "Positions annotated:\n" + + String.format("- %s: %d/%d%n", aRater1.getUiName(), + result.getNonNullCount(casGroupId1), result.getItemCount(casGroupId1)) + + String.format("- %s: %d/%d%n", aRater2.getUiName(), + result.getNonNullCount(casGroupId2), result.getItemCount(casGroupId2)) + + String.format("Distinct labels: %d%n", result.getCategoryCount()); + + var l = new Label("label", Model.of(label)); + var tooltip = new DescriptionTooltipBehavior(tooltipTitle, tooltipContent); tooltip.setOption("position", (Object) null); l.add(tooltip); l.add(new AttributeAppender("style", "cursor: pointer", ";")); - return l; - } + l.add(AjaxEventBehavior.onEvent(CLICK, + _target -> actionScoreClicked(_target, aRater1, aRater2))); - public boolean isAllNull(AgreementResult aResult, String aCasGroupId) - { - for (ICodingAnnotationItem item : aResult.getStudy().getItems()) { - if (item.getUnit(aResult.getCasGroupIds().indexOf(aCasGroupId)).getCategory() != null) { - return false; - } - } - return true; - } - - public int getNonNullCount(AgreementResult aResult, String aCasGroupId) - { - int i = 0; - for (ICodingAnnotationItem item : aResult.getStudy().getItems()) { - if (item.getUnit(aResult.getCasGroupIds().indexOf(aCasGroupId)).getCategory() != null) { - i++; - } - } - return i; - } - - private Behavior makeDownloadBehavior(final String aKey1, final String aKey2) - { - return new AjaxEventBehavior("click") - { - private static final long serialVersionUID = 1L; - - @Override - protected void onEvent(AjaxRequestTarget aTarget) - { - AjaxDownloadBehavior download = new AjaxDownloadBehavior( - LoadableDetachableModel.of(PairwiseCodingAgreementTable.this::getFilename), - LoadableDetachableModel.of(() -> getAgreementTableData(aKey1, aKey2))); - getComponent().add(download); - download.initiate(aTarget); - } - }; - } - - private AbstractResourceStream getAgreementTableData(final String aKey1, final String aKey2) - { - return new AbstractResourceStream() - { - private static final long serialVersionUID = 1L; - - @Override - public InputStream getInputStream() throws ResourceStreamNotFoundException - { - try { - CodingAgreementResult result = PairwiseCodingAgreementTable.this - .getModelObject().getStudy(aKey1, aKey2); - - switch (formatField.getModelObject()) { - case CSV: - return AgreementUtils.generateCsvReport(result); - case DEBUG: - return generateDebugReport(result); - default: - throw new IllegalStateException( - "Unknown export format [" + formatField.getModelObject() + "]"); - } - } - catch (Exception e) { - // FIXME Is there some better error handling here? - LOG.error("Unable to generate agreement report", e); - throw new ResourceStreamNotFoundException(e); - } - } - - @Override - public void close() throws IOException - { - // Nothing to do - } - }; - } - - private String getFilename() - { - return "agreement" + formatField.getModelObject().getExtension(); - } - - private InputStream generateDebugReport(CodingAgreementResult aResult) - { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - AgreementUtils.dumpAgreementStudy(new PrintStream(buf), aResult); - return new ByteArrayInputStream(buf.toByteArray()); - } - - @SuppressWarnings("unchecked") - public PairwiseAnnotationResult getModelObject() - { - return (PairwiseAnnotationResult) getDefaultModelObject(); - } - - public void setModelObject(PairwiseAnnotationResult aAgreements2) - { - setDefaultModelObject(aAgreements2); + return l; } - private IResourceStream exportAllAgreements() + private void actionScoreClicked(AjaxRequestTarget aTarget, User aRater1, User aRater2) { - return new AbstractResourceStream() - { - private static final long serialVersionUID = 1L; - - @Override - public InputStream getInputStream() throws ResourceStreamNotFoundException - { - AnnotationFeature feature = getModelObject().getFeature(); - DefaultAgreementTraits traits = getModelObject().getTraits(); - - Map> casMap = casMapSupplier.get(); - - List adapters = CasDiff.getDiffAdapters(annotationService, - asList(feature.getLayer())); - - CasDiff diff = doDiff(adapters, traits.getLinkCompareBehavior(), casMap); - - Set tagset = annotationService.listTags(feature.getTagset()).stream() - .map(Tag::getName).collect(toCollection(LinkedHashSet::new)); - - // AgreementResult agreementResult = AgreementUtils.makeStudy(diff, - // feature.getLayer().getName(), feature.getName(), - // pref.excludeIncomplete, casMap); - // TODO: for the moment, we always include incomplete annotations during this - // export. - CodingAgreementResult agreementResult = makeCodingStudy(diff, - feature.getLayer().getName(), feature.getName(), tagset, false, casMap); - - try { - return AgreementUtils.generateCsvReport(agreementResult); - } - catch (Exception e) { - // FIXME Is there some better error handling here? - LOG.error("Unable to generate report", e); - throw new ResourceStreamNotFoundException(e); - } - } - - @Override - public void close() throws IOException - { - // Nothing to do - } - }; + send(this, BUBBLE, new PairwiseAgreementScoreClickedEvent(aTarget, aRater1.getUsername(), + aRater2.getUsername())); } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/event/PairwiseAgreementScoreClickedEvent.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/event/PairwiseAgreementScoreClickedEvent.java new file mode 100644 index 00000000000..4c94dd5d009 --- /dev/null +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/coding/event/PairwiseAgreementScoreClickedEvent.java @@ -0,0 +1,50 @@ +/* + * 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.clarin.webanno.agreement.results.coding.event; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.wicketstuff.event.annotation.AbstractAjaxAwareEvent; + +/** + * Fired when a user clicks on a pairwise agreement score. + */ +public class PairwiseAgreementScoreClickedEvent + extends AbstractAjaxAwareEvent +{ + private final String annotator1; + private final String annotator2; + + public PairwiseAgreementScoreClickedEvent(AjaxRequestTarget aTarget, String aAnnotator1, + String aAnnotator2) + { + super(aTarget); + + annotator1 = aAnnotator1; + annotator2 = aAnnotator2; + } + + public String getAnnotator1() + { + return annotator1; + } + + public String getAnnotator2() + { + return annotator2; + } +} diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/AbstractUnitizingAgreementMeasureSupport.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/AbstractUnitizingAgreementMeasureSupport.java index d6db37f5a7f..98713194908 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/AbstractUnitizingAgreementMeasureSupport.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/AbstractUnitizingAgreementMeasureSupport.java @@ -17,14 +17,9 @@ */ package de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing; -import java.util.List; -import java.util.Map; - -import org.apache.uima.cas.CAS; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; -import org.danekja.java.util.function.serializable.SerializableSupplier; -import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; +import org.dkpro.statistics.agreement.unitizing.IUnitizingAnnotationStudy; import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasureSupport_ImplBase; @@ -32,12 +27,10 @@ public abstract class AbstractUnitizingAgreementMeasureSupport extends - AgreementMeasureSupport_ImplBase, ICodingAnnotationStudy> + AgreementMeasureSupport_ImplBase { @Override - public Panel createResultsPanel(String aId, - IModel> aResults, - SerializableSupplier>> aCasMapSupplier) + public Panel createResultsPanel(String aId, IModel aResults) { return new PairwiseUnitizingAgreementTable(aId, aResults); } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/FullUnitizingAgreementResult.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/FullUnitizingAgreementResult.java new file mode 100644 index 00000000000..8b8f26d9f75 --- /dev/null +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/FullUnitizingAgreementResult.java @@ -0,0 +1,66 @@ +/* + * 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.clarin.webanno.agreement.results.unitizing; + +import java.util.List; + +import org.dkpro.statistics.agreement.unitizing.IUnitizingAnnotationStudy; + +import de.tudarmstadt.ukp.clarin.webanno.agreement.FullAgreementResult_ImplBase; + +public class FullUnitizingAgreementResult + extends FullAgreementResult_ImplBase +{ + private static final long serialVersionUID = 2092691057728349705L; + + public FullUnitizingAgreementResult(String aType, String aFeature, IUnitizingAnnotationStudy aStudy, + List aCasGroupIds, boolean aExcludeIncomplete) + { + super(aType, aFeature, aStudy, aCasGroupIds, aExcludeIncomplete); + } + + public boolean isAllNull(String aRater) + { + var raterIdx = getCasGroupIds().indexOf(aRater); + + return study.getUnits().stream() + .noneMatch(u -> u.getRaterIdx() == raterIdx && u.getCategory() != null); + } + + public long getNonNullCount(String aRater) + { + var raterIdx = getCasGroupIds().indexOf(aRater); + + return study.getUnits().stream() // + .filter(u -> u.getRaterIdx() == raterIdx && u.getCategory() != null) // + .count(); + } + + @Override + public long getItemCount(String aRater) + { + var raterIdx = getCasGroupIds().indexOf(aRater); + return study.getUnitCount(raterIdx); + } + + @Override + public boolean isEmpty() + { + return study.getUnitCount() == 0; + } +} diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.html b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.html index d7f04ce7195..0ddbf223631 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.html +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.html @@ -20,9 +20,11 @@
- - - +
+ +
diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.java index e35b3e8ee78..943759fb826 100644 --- a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.java +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/results/unitizing/PairwiseUnitizingAgreementTable.java @@ -19,6 +19,7 @@ import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.visibleWhen; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; @@ -26,7 +27,7 @@ import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.markup.html.panel.GenericPanel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.RefreshingView; import org.apache.wicket.model.IModel; @@ -35,15 +36,14 @@ import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; -import org.dkpro.statistics.agreement.unitizing.IUnitizingAnnotationStudy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.agilecoders.wicket.core.markup.html.bootstrap.components.PopoverConfig; import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig.Placement; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; +import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.bootstrap.PopoverBehavior; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.project.api.ProjectService; @@ -52,58 +52,61 @@ import de.tudarmstadt.ukp.inception.support.wicket.DescriptionTooltipBehavior; public class PairwiseUnitizingAgreementTable - extends Panel + extends GenericPanel { private static final long serialVersionUID = 571396822546125376L; - private final static Logger LOG = LoggerFactory - .getLogger(PairwiseUnitizingAgreementTable.class); + private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @SpringBean AnnotationSchemaService annotationService; private @SpringBean DocumentService documentService; private @SpringBean ProjectService projectService; private @SpringBean UserDao userRepository; - private final RefreshingView rows; + private final RefreshingView rows; - public PairwiseUnitizingAgreementTable(String aId, - IModel> aModel) + public PairwiseUnitizingAgreementTable(String aId, IModel aModel) { super(aId, aModel); setOutputMarkupId(true); - PopoverConfig config = new PopoverConfig().withPlacement(Placement.left).withHtml(true); - WebMarkupContainer legend = new WebMarkupContainer("legend"); + var config = new PopoverConfig().withPlacement(Placement.left).withHtml(true); + var legend = new WebMarkupContainer("legend"); legend.add(new PopoverBehavior(new ResourceModel("legend"), new StringResourceModel("legend.content", legend), config)); add(legend); // This model makes sure we add a "null" dummy rater which accounts for the header columns // of the table. - final IModel> ratersAdapter = LoadableDetachableModel.of(() -> { - List raters = new ArrayList<>(); + final IModel> ratersAdapter = LoadableDetachableModel.of(() -> { + var raters = new ArrayList(); if (getModelObject() != null) { raters.add(null); - raters.addAll(getModelObject().getRaters()); + for (var rater : getModelObject().getRaters()) { + var user = userRepository.get(rater); + if (user != null) { + raters.add(user); + } + } } return raters; }); - rows = new DefaultRefreshingView("rows", ratersAdapter) + rows = new DefaultRefreshingView("rows", ratersAdapter) { private static final long serialVersionUID = 1L; @Override - protected void populateItem(final Item aRowItem) + protected void populateItem(final Item aRowItem) { // Render regular row - aRowItem.add(new DefaultRefreshingView("cells", ratersAdapter) + aRowItem.add(new DefaultRefreshingView("cells", ratersAdapter) { private static final long serialVersionUID = 1L; @Override - protected void populateItem(Item aCellItem) + protected void populateItem(Item aCellItem) { aCellItem.setRenderBodyOnly(true); @@ -128,13 +131,11 @@ else if (aCellItem.getIndex() == 0) { } // Raters header horizontally else if (aRowItem.getIndex() == 0 && aCellItem.getIndex() != 0) { - cell.add(new Label("label", - userRepository.get(aCellItem.getModelObject()).getUiName())); + cell.add(new Label("label", aCellItem.getModelObject().getUiName())); } // Raters header vertically else if (aRowItem.getIndex() != 0 && aCellItem.getIndex() == 0) { - cell.add(new Label("label", - userRepository.get(aRowItem.getModelObject()).getUiName())); + cell.add(new Label("label", aRowItem.getModelObject().getUiName())); } // Upper diagonal else if (aCellItem.getIndex() > aRowItem.getIndex()) { @@ -165,109 +166,56 @@ else if (aCellItem.getIndex() < aRowItem.getIndex()) { add(rows); } - private Label makeLowerDiagonalCellLabel(String aRater1, String aRater2) + private Label makeLowerDiagonalCellLabel(User aRater1, User aRater2) { - // UnitizingAgreementResult result = getModelObject().getStudy(aRater1, - // aRater2); - - // String label = String.format("%d/%d", result.getCompleteSetCount(), - // result.getRelevantSetCount()); - // - // String tooltipTitle = "Details about annotations excluded from agreement calculation"; - - // StringBuilder tooltipContent = new StringBuilder(); - // if (result.isExcludeIncomplete()) { - // tooltipContent.append(String.format("- Incomplete (missing): %d%n", - // result.getIncompleteSetsByPosition().size())); - // tooltipContent.append(String.format( - // "- Incomplete (not labeled): %d%n", result - // .getIncompleteSetsByLabel().size())); - // } - // tooltipContent.append(String.format("- Plurality: %d", result - // .getPluralitySets().size())); - - // Label l = new Label("label", Model.of(label)); - // DescriptionTooltipBehavior tooltip = new DescriptionTooltipBehavior( - // tooltipTitle, tooltipContent.toString()); - // tooltip.setOption("position", (Object) null); - // l.add(tooltip); - // l.add(new AttributeAppender("style", "cursor: help", ";")); - // return l; - return new Label("label"); } - private Label makeUpperDiagonalCellLabel(String aRater1, String aRater2) + private Label makeUpperDiagonalCellLabel(User aRater1, User aRater2) { - UnitizingAgreementResult result = getModelObject().getStudy(aRater1, aRater2); + var result = getModelObject().getResult(aRater1.getUsername(), aRater2.getUsername()); - boolean noDataRater0 = isAllNull(result, 0); - boolean noDataRater1 = isAllNull(result, 1); + if (result == null) { + return new Label("label", "no data"); + } + + var casGroupId1 = result.getCasGroupIds().get(0); + var casGroupId2 = result.getCasGroupIds().get(1); + var noDataRater1 = result.isAllNull(casGroupId1); + var noDataRater2 = result.isAllNull(casGroupId2); String label; - if (result.getStudy().getUnitCount() == 0) { + if (result.isEmpty()) { label = "no positions"; } - else if (noDataRater0 && noDataRater1) { + else if (noDataRater1 && noDataRater2) { label = "no labels"; } - else if (noDataRater0) { - label = "no labels from " + result.getCasGroupIds().get(0); - } else if (noDataRater1) { - label = "no labels from " + result.getCasGroupIds().get(1); + label = "no labels from " + aRater1.getUiName(); + } + else if (noDataRater2) { + label = "no labels from " + aRater2.getUiName(); } - // else if (incPos == result.getRelevantSetCount()) { - // label = "positions disjunct"; - // } - // else if (incLabel == result.getRelevantSetCount()) { - // label = "labels disjunct"; - // } - // else if ((incLabel + incPos) == result.getRelevantSetCount()) { - // label = "labels/positions disjunct"; - // } else { label = String.format("%.2f", result.getAgreement()); } - String tooltipTitle = result.getCasGroupIds().get(0) + '/' + result.getCasGroupIds().get(1); + var tooltipTitle = aRater1.getUiName() + " ↔ " + aRater2.getUiName(); - String tooltipContent = "Positions annotated:\n" - + String.format("- %s: %d/%d%n", result.getCasGroupIds().get(0), - getNonNullCount(result, 0), result.getStudy().getUnitCount(0)) - + String.format("- %s: %d/%d%n", result.getCasGroupIds().get(1), - getNonNullCount(result, 1), result.getStudy().getUnitCount(1)) - + String.format("Distinct labels: %d%n", result.getStudy().getCategoryCount()); + var tooltipContent = "Positions annotated:\n" + + String.format("- %s: %d/%d%n", aRater1.getUiName(), + result.getNonNullCount(casGroupId1), result.getItemCount(casGroupId1)) + + String.format("- %s: %d/%d%n", aRater2.getUiName(), + result.getNonNullCount(casGroupId2), result.getItemCount(casGroupId2)) + + String.format("Distinct labels: %d%n", result.getCategoryCount()); - Label l = new Label("label", Model.of(label)); - DescriptionTooltipBehavior tooltip = new DescriptionTooltipBehavior(tooltipTitle, - tooltipContent); + var l = new Label("label", Model.of(label)); + var tooltip = new DescriptionTooltipBehavior(tooltipTitle, tooltipContent); tooltip.setOption("position", (Object) null); l.add(tooltip); l.add(new AttributeAppender("style", "cursor: help", ";")); return l; } - - public boolean isAllNull(AgreementResult aResult, int aRaterIdx) - { - return !aResult.getStudy().getUnits().stream() - .anyMatch(u -> u.getRaterIdx() == aRaterIdx && u.getCategory() != null); - } - - public long getNonNullCount(AgreementResult aResult, int aRaterIdx) - { - return aResult.getStudy().getUnits().stream() - .filter(u -> u.getRaterIdx() == aRaterIdx && u.getCategory() != null).count(); - } - - public PairwiseAnnotationResult getModelObject() - { - return (PairwiseAnnotationResult) getDefaultModelObject(); - } - - public void setModelObject(PairwiseAnnotationResult aAgreements2) - { - setDefaultModelObject(aAgreements2); - } } diff --git a/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/task/CalculatePairwiseAgreementTask.java b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/task/CalculatePairwiseAgreementTask.java new file mode 100644 index 00000000000..f74b7265326 --- /dev/null +++ b/inception/inception-agreement/src/main/java/de/tudarmstadt/ukp/clarin/webanno/agreement/task/CalculatePairwiseAgreementTask.java @@ -0,0 +1,256 @@ +/* + * 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.clarin.webanno.agreement.task; + +import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasAccessMode.SHARED_READ_ONLY_ACCESS; +import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasUpgradeMode.AUTO_CAS_UPGRADE; +import static java.util.Comparator.comparing; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.uima.cas.CAS; +import org.apache.uima.fit.util.FSUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementSummary; +import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; +import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.clarin.webanno.security.model.User; +import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; +import de.tudarmstadt.ukp.inception.documents.api.DocumentService; +import de.tudarmstadt.ukp.inception.scheduling.Task; +import de.tudarmstadt.ukp.inception.support.logging.LogMessage; +import de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil; + +public class CalculatePairwiseAgreementTask + extends Task +{ + public static final String TYPE = "CalculatePairwiseAgreementTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private @Autowired DocumentService documentService; + + private final List annotators; + private final DefaultAgreementTraits traits; + private final AnnotationFeature feature; + private final AgreementMeasure measure; + private final Map> allAnnDocs; + + private PairwiseAnnotationResult summary; + + public CalculatePairwiseAgreementTask(Builder> aBuilder) + { + super(aBuilder.withType(TYPE)); + + annotators = aBuilder.annotators; + traits = aBuilder.traits; + feature = aBuilder.feature; + measure = aBuilder.measure; + allAnnDocs = aBuilder.allAnnDocs; + } + + @Override + public void execute() + { + summary = new PairwiseAnnotationResult(feature, traits); + + var maxProgress = allAnnDocs.size(); + var progress = 0; + + var docs = allAnnDocs.keySet().stream() // + .sorted(comparing(SourceDocument::getName)) // + .toList(); + + for (var doc : docs) { + var monitor = getMonitor(); + if (monitor.isCancelled()) { + break; + } + + monitor.setProgressWithMessage(progress, maxProgress, + LogMessage.info(this, doc.getName())); + + try (var session = CasStorageSession.openNested()) { + AgreementSummary initialAgreementResult = null; + + for (int m = 0; m < annotators.size(); m++) { + var annotator1 = annotators.get(m).getUsername(); + var maybeCas1 = loadCas(doc, annotator1, allAnnDocs); + + for (int n = 0; n < annotators.size(); n++) { + var annotator2 = annotators.get(n).getUsername(); + var maybeCas2 = loadCas(doc, annotator2, allAnnDocs); + + // Triangle matrix mirrored + if (n < m) { + // So, theoretically, if cas1 and cas2 are both empty, then both are + // the initial CAS - so there must be full agreement. However, we + // would still need to count the units, categories, etc. + if (maybeCas1.isEmpty() && maybeCas2.isEmpty()) { + if (initialAgreementResult == null) { + var casMap = new LinkedHashMap(); + casMap.put("INITIAL1", loadInitialCas(doc)); + casMap.put("INITIAL2", loadInitialCas(doc)); + initialAgreementResult = AgreementSummary + .of(measure.getAgreement(casMap)); + } + var res = initialAgreementResult.remap( + Map.of("INITIAL1", annotator1, "INITIAL2", annotator2)); + summary.mergeResult(annotator1, annotator2, res); + } + else { + var cas1 = maybeCas1.isPresent() ? maybeCas1.get() + : loadInitialCas(doc); + var cas2 = maybeCas2.isPresent() ? maybeCas2.get() + : loadInitialCas(doc); + + var casMap = new LinkedHashMap(); + casMap.put(annotator1, cas1); + casMap.put(annotator2, cas2); + var res = AgreementSummary.of(measure.getAgreement(casMap)); + summary.mergeResult(annotator1, annotator2, res); + } + } + } + } + + progress++; + } + catch (Exception e) { + LOG.error("Unable to load data", e); + } + } + } + + private CAS loadInitialCas(SourceDocument aDocument) throws IOException + { + var cas = documentService.createOrReadInitialCas(aDocument, AUTO_CAS_UPGRADE, + SHARED_READ_ONLY_ACCESS); + + // Set the CAS name in the DocumentMetaData so that we can pick it + // up in the Diff position for the purpose of debugging / transparency. + var dmd = WebAnnoCasUtil.getDocumentMetadata(cas); + FSUtil.setFeature(dmd, "documentId", aDocument.getName()); + FSUtil.setFeature(dmd, "collectionId", aDocument.getProject().getName()); + + return cas; + } + + private Optional loadCas(SourceDocument aDocument, String aDataOwner, + Map> aAllAnnDocs) + throws IOException + { + var annDocs = aAllAnnDocs.get(aDocument); + + if (annDocs.stream().noneMatch(annDoc -> aDataOwner.equals(annDoc.getUser()))) { + return Optional.empty(); + } + + if (!documentService.existsCas(aDocument, aDataOwner)) { + Optional.empty(); + } + + var cas = documentService.readAnnotationCas(aDocument, aDataOwner, AUTO_CAS_UPGRADE, + SHARED_READ_ONLY_ACCESS); + + // Set the CAS name in the DocumentMetaData so that we can pick it + // up in the Diff position for the purpose of debugging / transparency. + var dmd = WebAnnoCasUtil.getDocumentMetadata(cas); + FSUtil.setFeature(dmd, "documentId", aDocument.getName()); + FSUtil.setFeature(dmd, "collectionId", aDocument.getProject().getName()); + + return Optional.of(cas); + } + + public PairwiseAnnotationResult getResult() + { + return summary; + } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends Task.Builder + { + private List annotators; + private DefaultAgreementTraits traits; + private AnnotationFeature feature; + private AgreementMeasure measure; + private Map> allAnnDocs; + + protected Builder() + { + withCancellable(true); + } + + @SuppressWarnings("unchecked") + public T withAnnotators(List aAnnotators) + { + annotators = aAnnotators; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withTraits(DefaultAgreementTraits aTraits) + { + traits = aTraits; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withFeature(AnnotationFeature aFeature) + { + feature = aFeature; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withMeasure(AgreementMeasure aMeasure) + { + measure = aMeasure; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withDocuments(Map> aAllAnnDocs) + { + allAnnDocs = aAllAnnDocs; + return (T) this; + } + + public CalculatePairwiseAgreementTask build() + { + return new CalculatePairwiseAgreementTask(this); + } + } +} diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureTestSuite_ImplBase.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureTestSuite_ImplBase.java index d4ee71e5cbb..41a6b4f9729 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureTestSuite_ImplBase.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementMeasureTestSuite_ImplBase.java @@ -37,7 +37,6 @@ import static org.apache.uima.fit.factory.CasFactory.createText; import static org.apache.uima.fit.factory.JCasFactory.createJCas; -import java.io.Serializable; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -52,6 +51,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import de.tudarmstadt.ukp.clarin.webanno.agreement.FullAgreementResult_ImplBase; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.Project; @@ -104,7 +104,7 @@ public void setup() layerRegistry.init(); } - public // + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // R multiLinkWithRoleLabelDifferenceTest(AgreementMeasureSupport aSupport) throws Exception { @@ -133,16 +133,16 @@ R multiLinkWithRoleLabelDifferenceTest(AgreementMeasureSupport aSupport jcasB.setDocumentText("This is a test."); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot2", 0, 0)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); AgreementMeasure measure = aSupport.createMeasure(feature, traits); return measure.getAgreement(casByUser); } - public // + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // R multiValueStringPartialAgreement(AgreementMeasureSupport aSupport) throws Exception { AnnotationLayer layer = new AnnotationLayer(MULTI_VALUE_SPAN_TYPE, MULTI_VALUE_SPAN_TYPE, @@ -177,11 +177,11 @@ R multiValueStringPartialAgreement(AgreementMeasureSupport aSupport) th AgreementMeasure measure = aSupport.createMeasure(feature, traits); return measure.getAgreement(Map.of( // - "user1", asList(user1), // - "user2", asList(user2))); + "user1", user1, // + "user2", user2)); } - public // + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // R twoEmptyCasTest(AgreementMeasureSupport aSupport) throws Exception { var layer = new AnnotationLayer(Lemma.class.getName(), Lemma.class.getSimpleName(), @@ -202,17 +202,17 @@ R twoEmptyCasTest(AgreementMeasureSupport aSupport) throws Exception CAS user2Cas = JCasFactory.createText(text).getCas(); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1Cas)); - casByUser.put("user2", asList(user2Cas)); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", user1Cas); + casByUser.put("user2", user2Cas); AgreementMeasure measure = aSupport.createMeasure(feature, traits); return measure.getAgreement(casByUser); } - public // - R singleNoDifferencesWithAdditionalCasTest(AgreementMeasureSupport aSupport) + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // + R threeCasesWithAnnotationOnlyInThird(AgreementMeasureSupport aSupport) throws Exception { var layer = new AnnotationLayer(POS.class.getName(), POS.class.getSimpleName(), @@ -220,7 +220,7 @@ R singleNoDifferencesWithAdditionalCasTest(AgreementMeasureSupport aSup layer.setId(1l); layers.add(layer); - AnnotationFeature feature = new AnnotationFeature(project, layer, POS._FeatName_PosValue, + var feature = new AnnotationFeature(project, layer, POS._FeatName_PosValue, POS._FeatName_PosValue, TYPE_NAME_STRING); feature.setId(1l); features.add(feature); @@ -235,12 +235,12 @@ R singleNoDifferencesWithAdditionalCasTest(AgreementMeasureSupport aSup AgreementMeasure measure = aSupport.createMeasure(feature, aSupport.createTraits()); return measure.getAgreement(Map.of( // - "user1", asList(user1), // - "user2", asList(user2), // - "user3", asList(user3))); + "user1", user1, // + "user2", user2, // + "user3", user3)); } - public // + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // R twoWithoutLabelTest(AgreementMeasureSupport aSupport, T aTraits) throws Exception { var layer = new AnnotationLayer(POS.class.getName(), POS.class.getSimpleName(), @@ -269,20 +269,20 @@ R twoWithoutLabelTest(AgreementMeasureSupport aSupport, T aTraits) thro p2.setPosValue("B"); p2.addToIndexes(); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1.getCas())); - casByUser.put("user2", asList(user2.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", user1.getCas()); + casByUser.put("user2", user2.getCas()); AgreementMeasure measure = aSupport.createMeasure(feature, aTraits); return measure.getAgreement(casByUser); } - public // + public , T extends DefaultAgreementTraits, S extends IAnnotationStudy> // R fullSingleCategoryAgreementWithTagset(AgreementMeasureSupport aSupport, T aTraits) throws Exception { - TagSet tagset = new TagSet(project, "tagset"); + var tagset = new TagSet(project, "tagset"); var layer = new AnnotationLayer(POS.class.getName(), POS.class.getSimpleName(), SpanLayerSupport.TYPE, project, false, SINGLE_TOKEN, NO_OVERLAP); @@ -295,55 +295,20 @@ R fullSingleCategoryAgreementWithTagset(AgreementMeasureSupport aSuppor feature.setTagset(tagset); features.add(feature); - CAS user1 = createText("test"); + var user1 = createText("test"); buildAnnotation(user1, POS.class).at(0, 1) // .withFeature(POS._FeatName_PosValue, "+") // .buildAndAddToIndexes(); - CAS user2 = createText("test"); + var user2 = createText("test"); buildAnnotation(user2, POS.class).at(0, 1) // .withFeature(POS._FeatName_PosValue, "+") // .buildAndAddToIndexes(); return aSupport.createMeasure(feature, aTraits).getAgreement(Map.of( // - "user1", asList(user1), // - "user2", asList(user2))); - } - - public // - R twoDocumentsNoOverlap(AgreementMeasureSupport aSupport, T aTraits) throws Exception - { - TagSet tagset = new TagSet(project, "tagset"); - - var layer = new AnnotationLayer(POS.class.getName(), POS.class.getSimpleName(), - SpanLayerSupport.TYPE, project, false, SINGLE_TOKEN, NO_OVERLAP); - layer.setId(1l); - layers.add(layer); - - AnnotationFeature feature = new AnnotationFeature(project, layer, "PosValue", "PosValue", - CAS.TYPE_NAME_STRING); - feature.setId(1l); - feature.setTagset(tagset); - features.add(feature); - - CAS user1a = createText("test"); - CAS user1b = createText("test"); - - buildAnnotation(user1a, POS.class).at(0, 1) // - .withFeature(POS._FeatName_PosValue, "+") // - .buildAndAddToIndexes(); - - CAS user2a = createText("test"); - CAS user2b = createText("test"); - - buildAnnotation(user2b, POS.class).at(0, 1) // - .withFeature(POS._FeatName_PosValue, "+") // - .buildAndAddToIndexes(); - - return aSupport.createMeasure(feature, aTraits).getAgreement(Map.of( // - "user1", asList(user1a, user1b), // - "user2", asList(user2a, user2b))); + "user1", user1, // + "user2", user2)); } } diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementTestUtils.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementTestUtils.java index 3b9aab8cdfc..f3f7c9c1da8 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementTestUtils.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/AgreementTestUtils.java @@ -50,10 +50,9 @@ import org.apache.uima.util.CasCreationUtils; import org.dkpro.core.io.conll.Conll2006Reader; import org.dkpro.core.io.xmi.XmiReader; -import org.dkpro.statistics.agreement.IAgreementMeasure; import org.dkpro.statistics.agreement.InsufficientDataException; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; import de.tudarmstadt.ukp.clarin.webanno.tsv.WebannoTsv2Reader; import de.tudarmstadt.ukp.clarin.webanno.tsv.WebannoTsv3XReader; @@ -345,27 +344,27 @@ public static TypeSystemDescription createMultiLinkWithRoleTestTypeSystem(String } @Deprecated - public static CodingAgreementResult getCohenKappaAgreement(CasDiff aDiff, String aType, - String aFeature, Map> aCasMap) + public static FullCodingAgreementResult getCohenKappaAgreement(CasDiff aDiff, String aType, + String aFeature, Map aCasMap) { return getAgreement(COHEN_KAPPA_AGREEMENT, true, aDiff, aType, aFeature, aCasMap); } @Deprecated - public static CodingAgreementResult getAgreement(ConcreteAgreementMeasure aMeasure, + public static FullCodingAgreementResult getAgreement(ConcreteAgreementMeasure aMeasure, boolean aExcludeIncomplete, CasDiff aDiff, String aType, String aFeature, - Map> aCasMap) + Map aCasMap) { if (aCasMap.size() != 2) { throw new IllegalArgumentException("CAS map must contain exactly two CASes"); } - CodingAgreementResult agreementResult = makeCodingStudy(aDiff, aType, aFeature, emptySet(), + var agreementResult = makeCodingStudy(aDiff, aType, aFeature, emptySet(), aExcludeIncomplete, aCasMap); try { - IAgreementMeasure agreement = aMeasure.make(agreementResult.getStudy()); + var agreement = aMeasure.make(agreementResult.getStudy()); - if (agreementResult.getStudy().getItemCount() > 0) { + if (!agreementResult.isEmpty()) { agreementResult.setAgreement(agreement.calculateAgreement()); } else { diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/CohenKappaAgreementMeasureTest.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/CohenKappaAgreementMeasureTest.java index 946b03cc536..54ccd4db33d 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/CohenKappaAgreementMeasureTest.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/CohenKappaAgreementMeasureTest.java @@ -23,22 +23,19 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import org.dkpro.statistics.agreement.coding.ICodingAnnotationItem; import org.dkpro.statistics.agreement.coding.ICodingAnnotationStudy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.cohenkappa.CohenKappaAgreementMeasureSupport; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.Project; public class CohenKappaAgreementMeasureTest extends AgreementMeasureTestSuite_ImplBase { private AgreementMeasureSupport, ICodingAnnotationStudy> sut; + FullCodingAgreementResult, ICodingAnnotationStudy> sut; private DefaultAgreementTraits traits; @Override @@ -56,11 +53,9 @@ public void multiLinkWithRoleLabelDifference() throws Exception { when(annotationService.listSupportedFeatures(any(Project.class))).thenReturn(features); - var agreement = multiLinkWithRoleLabelDifferenceTest(sut); + var result = multiLinkWithRoleLabelDifferenceTest(sut); - CodingAgreementResult result = agreement.getStudy("user1", "user2"); - - DiffResult diff = result.getDiff(); + var diff = result.getDiff(); diff.print(System.out); @@ -74,11 +69,9 @@ public void multiLinkWithRoleLabelDifference() throws Exception @Test public void twoEmptyCasTest() throws Exception { - PairwiseAnnotationResult agreement = twoEmptyCasTest(sut); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoEmptyCasTest(sut); - DiffResult diff = result.getDiff(); + var diff = result.getDiff(); assertEquals(0, diff.size()); assertEquals(0, diff.getDifferingConfigurationSets().size()); @@ -89,38 +82,25 @@ public void twoEmptyCasTest() throws Exception } @Test - public void singleNoDifferencesWithAdditionalCasTest() throws Exception + public void threeCasesWithAnnotationOnlyInThird() throws Exception { - PairwiseAnnotationResult agreement = singleNoDifferencesWithAdditionalCasTest( - sut); - - CodingAgreementResult result1 = agreement.getStudy("user1", "user2"); - assertEquals(0, result1.getTotalSetCount()); - assertEquals(0, result1.getIrrelevantSets().size()); - assertEquals(0, result1.getRelevantSetCount()); - - CodingAgreementResult result2 = agreement.getStudy("user1", "user3"); - assertEquals(1, result2.getTotalSetCount()); - assertEquals(0, result2.getIrrelevantSets().size()); - assertEquals(1, result2.getRelevantSetCount()); - - assertEquals(NaN, agreement.getStudy("user1", "user2").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user1", "user3").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user2", "user3").getAgreement(), 0.01); + var result = threeCasesWithAnnotationOnlyInThird(sut); + + assertThat(result.getTotalSetCount()).isOne(); + assertThat(result.getIrrelevantSets()).isEmpty(); + assertThat(result.getRelevantSetCount()).isOne(); + assertThat(result.getAgreement()).isNaN(); } @Test public void twoWithoutLabelTest() throws Exception { - PairwiseAnnotationResult agreement = twoWithoutLabelTest(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoWithoutLabelTest(sut, traits); result.getDiff().print(System.out); - ICodingAnnotationItem item1 = result.getStudy().getItem(0); - ICodingAnnotationItem item2 = result.getStudy().getItem(1); + var item1 = result.getStudy().getItem(0); + var item2 = result.getStudy().getItem(1); assertEquals("", item1.getUnit(0).getCategory()); assertEquals("", item1.getUnit(1).getCategory()); assertEquals("A", item2.getUnit(0).getCategory()); @@ -137,12 +117,9 @@ public void twoWithoutLabelTest() throws Exception @Test public void fullSingleCategoryAgreementWithTagsetTest() throws Exception { - PairwiseAnnotationResult agreement = fullSingleCategoryAgreementWithTagset( - sut, traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = fullSingleCategoryAgreementWithTagset(sut, traits); - ICodingAnnotationItem item1 = result.getStudy().getItem(0); + var item1 = result.getStudy().getItem(0); assertEquals("+", item1.getUnit(0).getCategory()); assertEquals(1, result.getTotalSetCount()); @@ -153,15 +130,4 @@ public void fullSingleCategoryAgreementWithTagsetTest() throws Exception assertEquals(1, result.getRelevantSetCount()); assertEquals(1.0, result.getAgreement(), 0.01); } - - @Test - public void twoDocumentsNoOverlapTest() throws Exception - { - PairwiseAnnotationResult agreement = twoDocumentsNoOverlap(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); - - assertThat(result.getAgreement()).isNaN(); - } } diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/FleissKappaAgreementMeasureTest.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/FleissKappaAgreementMeasureTest.java index 501589c9213..c8e73c22f97 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/FleissKappaAgreementMeasureTest.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/FleissKappaAgreementMeasureTest.java @@ -18,7 +18,6 @@ package de.tudarmstadt.ukp.clarin.webanno.agreement.measures; import static java.lang.Double.NaN; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -28,17 +27,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.fleisskappa.FleissKappaAgreementMeasureSupport; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.model.Project; public class FleissKappaAgreementMeasureTest extends AgreementMeasureTestSuite_ImplBase { private AgreementMeasureSupport, ICodingAnnotationStudy> sut; + FullCodingAgreementResult, ICodingAnnotationStudy> sut; private DefaultAgreementTraits traits; @Override @@ -56,11 +53,9 @@ public void multiLinkWithRoleLabelDifference() throws Exception { when(annotationService.listSupportedFeatures(any(Project.class))).thenReturn(features); - var agreement = multiLinkWithRoleLabelDifferenceTest(sut); + var result = multiLinkWithRoleLabelDifferenceTest(sut); - CodingAgreementResult result = agreement.getStudy("user1", "user2"); - - DiffResult diff = result.getDiff(); + var diff = result.getDiff(); diff.print(System.out); @@ -74,11 +69,9 @@ public void multiLinkWithRoleLabelDifference() throws Exception @Test public void twoEmptyCasTest() throws Exception { - PairwiseAnnotationResult agreement = twoEmptyCasTest(sut); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoEmptyCasTest(sut); - DiffResult diff = result.getDiff(); + var diff = result.getDiff(); assertEquals(0, diff.size()); assertEquals(0, diff.getDifferingConfigurationSets().size()); @@ -88,34 +81,32 @@ public void twoEmptyCasTest() throws Exception assertEquals(0, result.getIncompleteSetsByPosition().size()); } - @Test - public void singleNoDifferencesWithAdditionalCasTest() throws Exception - { - PairwiseAnnotationResult agreement = singleNoDifferencesWithAdditionalCasTest( - sut); - - CodingAgreementResult result1 = agreement.getStudy("user1", "user2"); - assertEquals(0, result1.getTotalSetCount()); - assertEquals(0, result1.getIrrelevantSets().size()); - assertEquals(0, result1.getRelevantSetCount()); - - CodingAgreementResult result2 = agreement.getStudy("user1", "user3"); - assertEquals(1, result2.getTotalSetCount()); - assertEquals(0, result2.getIrrelevantSets().size()); - assertEquals(1, result2.getRelevantSetCount()); - - assertEquals(NaN, agreement.getStudy("user1", "user2").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user1", "user3").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user2", "user3").getAgreement(), 0.01); - } + // @Test + // public void singleNoDifferencesWithAdditionalCasTest() throws Exception + // { + // PairwiseAnnotationResult agreement = + // singleNoDifferencesWithAdditionalCasTest( + // sut); + // + // CodingAgreementResult result1 = agreement.getStudy("user1", "user2"); + // assertEquals(0, result1.getTotalSetCount()); + // assertEquals(0, result1.getIrrelevantSets().size()); + // assertEquals(0, result1.getRelevantSetCount()); + // + // CodingAgreementResult result2 = agreement.getStudy("user1", "user3"); + // assertEquals(1, result2.getTotalSetCount()); + // assertEquals(0, result2.getIrrelevantSets().size()); + // assertEquals(1, result2.getRelevantSetCount()); + // + // assertEquals(NaN, agreement.getStudy("user1", "user2").getAgreement(), 0.01); + // assertEquals(NaN, agreement.getStudy("user1", "user3").getAgreement(), 0.01); + // assertEquals(NaN, agreement.getStudy("user2", "user3").getAgreement(), 0.01); + // } @Test public void twoWithoutLabelTest() throws Exception { - PairwiseAnnotationResult agreement = twoWithoutLabelTest(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoWithoutLabelTest(sut, traits); ICodingAnnotationItem item1 = result.getStudy().getItem(0); ICodingAnnotationItem item2 = result.getStudy().getItem(1); @@ -135,10 +126,7 @@ public void twoWithoutLabelTest() throws Exception @Test public void fullSingleCategoryAgreementWithTagsetTest() throws Exception { - PairwiseAnnotationResult agreement = fullSingleCategoryAgreementWithTagset( - sut, traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = fullSingleCategoryAgreementWithTagset(sut, traits); ICodingAnnotationItem item1 = result.getStudy().getItem(0); assertEquals("+", item1.getUnit(0).getCategory()); @@ -151,15 +139,4 @@ public void fullSingleCategoryAgreementWithTagsetTest() throws Exception assertEquals(1, result.getRelevantSetCount()); assertEquals(1.0, result.getAgreement(), 0.01); } - - @Test - public void twoDocumentsNoOverlapTest() throws Exception - { - PairwiseAnnotationResult agreement = twoDocumentsNoOverlap(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); - - assertThat(result.getAgreement()).isNaN(); - } } diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaNominalAgreementMeasureTest.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaNominalAgreementMeasureTest.java index cee7105b144..a2c34f84670 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaNominalAgreementMeasureTest.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaNominalAgreementMeasureTest.java @@ -19,7 +19,6 @@ import static java.lang.Double.NaN; import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -29,10 +28,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalpha.KrippendorffAlphaAgreementMeasureSupport; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalpha.KrippendorffAlphaAgreementTraits; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.Tag; @@ -42,7 +40,7 @@ public class KrippendorffAlphaNominalAgreementMeasureTest extends AgreementMeasureTestSuite_ImplBase { private AgreementMeasureSupport, ICodingAnnotationStudy> sut; + FullCodingAgreementResult, ICodingAnnotationStudy> sut; private KrippendorffAlphaAgreementTraits traits; @Override @@ -60,9 +58,7 @@ public void multiLinkWithRoleLabelDifference() throws Exception { when(annotationService.listSupportedFeatures(any(Project.class))).thenReturn(features); - var agreement = multiLinkWithRoleLabelDifferenceTest(sut); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = multiLinkWithRoleLabelDifferenceTest(sut); DiffResult diff = result.getDiff(); @@ -78,9 +74,7 @@ public void multiLinkWithRoleLabelDifference() throws Exception @Test public void twoEmptyCasTest() throws Exception { - PairwiseAnnotationResult agreement = twoEmptyCasTest(sut); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoEmptyCasTest(sut); DiffResult diff = result.getDiff(); @@ -92,36 +86,32 @@ public void twoEmptyCasTest() throws Exception assertEquals(0, result.getIncompleteSetsByPosition().size()); } - @Test - public void singleNoDifferencesWithAdditionalCasTest() throws Exception - { - PairwiseAnnotationResult agreement = singleNoDifferencesWithAdditionalCasTest( - sut); - - CodingAgreementResult result1 = agreement.getStudy("user1", "user2"); - assertEquals(0, result1.getTotalSetCount()); - assertEquals(0, result1.getIrrelevantSets().size()); - assertEquals(0, result1.getRelevantSetCount()); - - CodingAgreementResult result2 = agreement.getStudy("user1", "user3"); - assertEquals(1, result2.getTotalSetCount()); - assertEquals(0, result2.getIrrelevantSets().size()); - assertEquals(1, result2.getRelevantSetCount()); - - assertEquals(NaN, agreement.getStudy("user1", "user2").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user1", "user3").getAgreement(), 0.01); - assertEquals(NaN, agreement.getStudy("user2", "user3").getAgreement(), 0.01); - } + // @Test + // public void singleNoDifferencesWithAdditionalCasTest() throws Exception + // { + // var result = singleNoDifferencesWithAdditionalCasTest(sut); + // + // CodingAgreementResult result1 = agreement.getStudy("user1", "user2"); + // assertEquals(0, result1.getTotalSetCount()); + // assertEquals(0, result1.getIrrelevantSets().size()); + // assertEquals(0, result1.getRelevantSetCount()); + // + // CodingAgreementResult result2 = agreement.getStudy("user1", "user3"); + // assertEquals(1, result2.getTotalSetCount()); + // assertEquals(0, result2.getIrrelevantSets().size()); + // assertEquals(1, result2.getRelevantSetCount()); + // + // assertEquals(NaN, agreement.getStudy("user1", "user2").getAgreement(), 0.01); + // assertEquals(NaN, agreement.getStudy("user1", "user3").getAgreement(), 0.01); + // assertEquals(NaN, agreement.getStudy("user2", "user3").getAgreement(), 0.01); + // } @Test public void testTwoWithoutLabel_noExcludeIncomplete() throws Exception { traits.setExcludeIncomplete(false); - PairwiseAnnotationResult agreement = twoWithoutLabelTest(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = twoWithoutLabelTest(sut, traits); ICodingAnnotationItem item1 = result.getStudy().getItem(0); ICodingAnnotationItem item2 = result.getStudy().getItem(1); @@ -152,9 +142,7 @@ public void fullSingleCategoryAgreementWithTagsetTest() throws Exception when(annotationService.listTags(tagset)).thenReturn(asList(tag1, tag2)); when(annotationService.listSupportedFeatures(any(Project.class))).thenReturn(features); - var agreement = fullSingleCategoryAgreementWithTagset(sut, traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); + var result = fullSingleCategoryAgreementWithTagset(sut, traits); ICodingAnnotationItem item1 = result.getStudy().getItem(0); assertEquals("+", item1.getUnit(0).getCategory()); @@ -167,15 +155,4 @@ public void fullSingleCategoryAgreementWithTagsetTest() throws Exception assertEquals(1, result.getRelevantSetCount()); assertEquals(1.0, result.getAgreement(), 0.01); } - - @Test - public void twoDocumentsNoOverlapTest() throws Exception - { - PairwiseAnnotationResult agreement = twoDocumentsNoOverlap(sut, - traits); - - CodingAgreementResult result = agreement.getStudy("user1", "user2"); - - assertThat(result.getAgreement()).isNaN(); - } } diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaUnitizingAgreementMeasureTest.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaUnitizingAgreementMeasureTest.java index a578c382215..694c9b09cae 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaUnitizingAgreementMeasureTest.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/KrippendorffAlphaUnitizingAgreementMeasureTest.java @@ -24,16 +24,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.agreement.PairwiseAnnotationResult; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalphaunitizing.KrippendorffAlphaUnitizingAgreementMeasureSupport; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.krippendorffalphaunitizing.KrippendorffAlphaUnitizingAgreementTraits; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.UnitizingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.unitizing.FullUnitizingAgreementResult; public class KrippendorffAlphaUnitizingAgreementMeasureTest extends AgreementMeasureTestSuite_ImplBase { private AgreementMeasureSupport_ImplBase, IUnitizingAnnotationStudy> sut; + FullUnitizingAgreementResult, IUnitizingAnnotationStudy> sut; private KrippendorffAlphaUnitizingAgreementTraits traits; @Override @@ -49,9 +48,7 @@ public void setup() @Test public void multiLinkWithRoleLabelDifference() throws Exception { - var agreement = multiLinkWithRoleLabelDifferenceTest(sut); - - var result = agreement.getStudy("user1", "user2"); + var result = multiLinkWithRoleLabelDifferenceTest(sut); assertEquals(0.0, result.getAgreement(), 0.00001d); } @@ -59,31 +56,27 @@ public void multiLinkWithRoleLabelDifference() throws Exception @Test public void twoEmptyCasTest() throws Exception { - var agreement = twoEmptyCasTest(sut); - - var result = agreement.getStudy("user1", "user2"); + var result = twoEmptyCasTest(sut); assertThat(result.getAgreement()).isNaN(); } - @Test - public void singleNoDifferencesWithAdditionalCasTest() throws Exception - { - var agreement = singleNoDifferencesWithAdditionalCasTest(sut); - - assertThat(agreement.getStudy("user1", "user2").getAgreement()).isNaN(); - assertThat(agreement.getStudy("user1", "user3").getAgreement()).isEqualTo(-4.5d); - assertThat(agreement.getStudy("user2", "user3").getAgreement()).isEqualTo(-4.5d); - } + // @Test + // public void singleNoDifferencesWithAdditionalCasTest() throws Exception + // { + // var agreement = singleNoDifferencesWithAdditionalCasTest(sut); + // + // assertThat(agreement.getStudy("user1", "user2").getAgreement()).isNaN(); + // assertThat(agreement.getStudy("user1", "user3").getAgreement()).isEqualTo(-4.5d); + // assertThat(agreement.getStudy("user2", "user3").getAgreement()).isEqualTo(-4.5d); + // } @Test public void testTwoWithoutLabel_noExcludeIncomplete() throws Exception { traits.setExcludeIncomplete(false); - var agreement = twoWithoutLabelTest(sut, traits); - - var result = agreement.getStudy("user1", "user2"); + var result = twoWithoutLabelTest(sut, traits); assertEquals(0.0, result.getAgreement(), 0.01); } @@ -91,29 +84,15 @@ public void testTwoWithoutLabel_noExcludeIncomplete() throws Exception @Test public void fullSingleCategoryAgreementWithTagsetTest() throws Exception { - var agreement = fullSingleCategoryAgreementWithTagset(sut, traits); - - var result = agreement.getStudy("user1", "user2"); + var result = fullSingleCategoryAgreementWithTagset(sut, traits); assertEquals(1.0, result.getAgreement(), 0.01); } - @Test - public void twoDocumentsNoOverlapTest() throws Exception - { - var agreement = twoDocumentsNoOverlap(sut, traits); - - var result = agreement.getStudy("user1", "user2"); - - assertEquals(-0.0714, result.getAgreement(), 0.001); - } - @Test public void multiValueStringPartialAgreementTest() throws Exception { - var agreement = multiValueStringPartialAgreement(sut); - - var result = agreement.getStudy("user1", "user2"); + var result = multiValueStringPartialAgreement(sut); assertEquals(0.4893, result.getAgreement(), 0.001); } diff --git a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/TwoPairedKappaTest.java b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/TwoPairedKappaTest.java index 60cb1444905..5bc1b985786 100644 --- a/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/TwoPairedKappaTest.java +++ b/inception/inception-agreement/src/test/java/de/tudarmstadt/ukp/clarin/webanno/agreement/measures/TwoPairedKappaTest.java @@ -19,28 +19,27 @@ import static de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementTestUtils.getCohenKappaAgreement; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.AGREE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.DISAGREE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.calculateState; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.LinkCompareBehavior.LINK_TARGET_AS_LABEL; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.relation.RelationDiffAdapter.DEPENDENCY_DIFF_ADAPTER; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.span.SpanDiffAdapter.POS_DIFF_ADAPTER; import static java.util.Arrays.asList; import static org.apache.uima.fit.factory.CollectionReaderFactory.createReader; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.apache.uima.cas.CAS; -import org.apache.uima.collection.CollectionReader; import org.apache.uima.fit.factory.JCasFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementResult; -import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementUtils; -import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; @@ -63,38 +62,36 @@ public class TwoPairedKappaTest @BeforeEach public void init() throws Exception { - user1 = new User(); - user1.setUsername("user1"); - - user2 = new User(); - user2.setUsername("user2"); - - user3 = new User(); - user3.setUsername("user3"); - + user1 = User.builder().withUsername("user1").build(); + user2 = User.builder().withUsername("user2").build(); + user3 = User.builder().withUsername("user3").build(); document = new SourceDocument(); kappatestCas = JCasFactory.createJCas().getCas(); - CollectionReader reader1 = createReader(WebannoTsv2Reader.class, - WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", + var reader1 = createReader( // + WebannoTsv2Reader.class, // + WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", // WebannoTsv2Reader.PARAM_PATTERNS, "kappatest.tsv"); reader1.getNext(kappatestCas); kappaspandiff = JCasFactory.createJCas().getCas(); - CollectionReader reader2 = createReader(WebannoTsv2Reader.class, - WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", + var reader2 = createReader( // + WebannoTsv2Reader.class, // + WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", // WebannoTsv2Reader.PARAM_PATTERNS, "kappaspandiff.tsv"); reader2.getNext(kappaspandiff); kappaarcdiff = JCasFactory.createJCas().getCas(); - CollectionReader reader3 = createReader(WebannoTsv2Reader.class, - WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", + var reader3 = createReader( // + WebannoTsv2Reader.class, // + WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", // WebannoTsv2Reader.PARAM_PATTERNS, "kappaarcdiff.tsv"); reader3.getNext(kappaarcdiff); kappaspanarcdiff = JCasFactory.createJCas().getCas(); - CollectionReader reader4 = createReader(WebannoTsv2Reader.class, - WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", + var reader4 = createReader( // + WebannoTsv2Reader.class, // + WebannoTsv2Reader.PARAM_SOURCE_LOCATION, "src/test/resources/", // WebannoTsv2Reader.PARAM_PATTERNS, "kappaspanarcdiff.tsv"); reader4.getNext(kappaspanarcdiff); } @@ -102,38 +99,39 @@ public void init() throws Exception @Test public void testTwoUserSameAnnotation() throws Exception { - Map> userDocs = new HashMap<>(); + var userDocs = new HashMap>(); userDocs.put(user1, asList(document)); userDocs.put(user2, asList(document)); - Map userCases = new HashMap<>(); + var userCases = new HashMap(); userCases.put(user1, kappatestCas); userCases.put(user2, kappatestCas); - Map> documentJCases = new HashMap<>(); + var documentJCases = new HashMap>(); documentJCases.put(document, userCases); // Check against new impl - CasDiff diff = doDiff(asList(POS_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, convert(userCases)); - DiffResult result = diff.toResult(); - AgreementResult agreement = getCohenKappaAgreement(diff, POS.class.getName(), "PosValue", + var diff = doDiff(asList(POS_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, convert(userCases)); + var result = diff.toResult(); + var agreement = getCohenKappaAgreement(diff, POS.class.getName(), "PosValue", convert(userCases)); // Asserts - System.out.printf("Agreement: %s%n", agreement.toString()); - result.print(System.out); - - assertEquals(1.0d, agreement.getAgreement(), 0.000001); - assertEquals(9, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + // System.out.printf("Agreement: %s%n", agreement.toString()); + // result.print(System.out); + + assertThat(agreement.getAgreement()).isCloseTo(1.0d, within(0.00001d)); + assertThat(result.size()).isEqualTo(9); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); } - private Map> convert(Map aMap) + private Map convert(Map aMap) { - Map> map = new LinkedHashMap<>(); - for (Entry e : aMap.entrySet()) { - map.put(e.getKey().getUsername(), asList(e.getValue())); + var map = new LinkedHashMap(); + for (var e : aMap.entrySet()) { + map.put(e.getKey().getUsername(), e.getValue()); } return map; } @@ -141,94 +139,97 @@ private Map> convert(Map aMap) @Test public void testTwoUserDiffArcAnnotation() throws Exception { - Map> userDocs = new HashMap<>(); + var userDocs = new HashMap>(); userDocs.put(user1, asList(document)); userDocs.put(user2, asList(document)); - Map userCases = new HashMap<>(); + var userCases = new HashMap(); userCases.put(user1, kappatestCas); userCases.put(user2, kappaarcdiff); - Map> documentJCases = new HashMap<>(); + var documentJCases = new HashMap>(); documentJCases.put(document, userCases); // Check against new impl - CasDiff diff = doDiff(asList(DEPENDENCY_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, + var diff = doDiff(asList(DEPENDENCY_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, + convert(userCases)); + var result = diff.toResult(); + var agreement = getCohenKappaAgreement(diff, Dependency.class.getName(), "DependencyType", convert(userCases)); - DiffResult result = diff.toResult(); - AgreementResult agreement = getCohenKappaAgreement(diff, Dependency.class.getName(), - "DependencyType", convert(userCases)); // Asserts - System.out.printf("Agreement: %s%n", agreement.toString()); - result.print(System.out); - - assertEquals(0.86153d, agreement.getAgreement(), 0.00001d); - assertEquals(9, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + // System.out.printf("Agreement: %s%n", agreement.toString()); + // result.print(System.out); + + assertThat(agreement.getAgreement()).isCloseTo(0.86153d, within(0.00001d)); + assertThat(result.size()).isEqualTo(9); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); } @Test public void testTwoUserDiffSpanAnnotation() throws Exception { - Map> userDocs = new HashMap<>(); + var userDocs = new HashMap>(); userDocs.put(user1, asList(document)); userDocs.put(user2, asList(document)); - Map userCases = new HashMap<>(); + var userCases = new HashMap(); userCases.put(user1, kappatestCas); userCases.put(user2, kappaspandiff); - Map> documentJCases = new HashMap<>(); + var documentJCases = new HashMap>(); documentJCases.put(document, userCases); // Check against new impl - CasDiff diff = doDiff(asList(POS_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, convert(userCases)); - DiffResult result = diff.toResult(); - AgreementResult agreement = getCohenKappaAgreement(diff, POS.class.getName(), "PosValue", + var diff = doDiff(asList(POS_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, convert(userCases)); + var result = diff.toResult(); + var agreement = getCohenKappaAgreement(diff, POS.class.getName(), "PosValue", convert(userCases)); // Asserts - System.out.printf("Agreement: %s%n", agreement.toString()); - result.print(System.out); - - assertEquals(0.86153d, agreement.getAgreement(), 0.00001d); - assertEquals(9, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + // System.out.printf("Agreement: %s%n", agreement.toString()); + // result.print(System.out); + + assertThat(agreement.getAgreement()).isCloseTo(0.86153d, within(0.00001d)); + assertThat(result.size()).isEqualTo(9); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); } @Test public void testTwoUserDiffArcAndSpanAnnotation() throws Exception { - Map> userDocs = new HashMap<>(); + var userDocs = new HashMap>(); userDocs.put(user1, asList(document)); userDocs.put(user2, asList(document)); - Map userCases = new HashMap<>(); + var userCases = new HashMap(); userCases.put(user1, kappatestCas); userCases.put(user2, kappaspanarcdiff); - Map> documentJCases = new HashMap<>(); + var documentJCases = new HashMap>(); documentJCases.put(document, userCases); // Check against new impl - CasDiff diff = doDiff(asList(DEPENDENCY_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, + var diff = doDiff(asList(DEPENDENCY_DIFF_ADAPTER), LINK_TARGET_AS_LABEL, + convert(userCases)); + var result = diff.toResult(); + var agreement = getCohenKappaAgreement(diff, Dependency.class.getName(), "DependencyType", convert(userCases)); - DiffResult result = diff.toResult(); - CodingAgreementResult agreement = getCohenKappaAgreement(diff, Dependency.class.getName(), - "DependencyType", convert(userCases)); // Asserts - System.out.printf("Agreement: %s%n", agreement.toString()); - result.print(System.out); - AgreementUtils.dumpAgreementStudy(System.out, agreement); - - assertEquals(0.86153d, agreement.getAgreement(), 0.00001d); - assertEquals(9, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + // System.out.printf("Agreement: %s%n", agreement.toString()); + // result.print(System.out); + // AgreementUtils.dumpAgreementStudy(System.out, agreement); + + assertThat(agreement.getAgreement()).isCloseTo(0.86153d, within(0.00001d)); + assertThat(result.size()).isEqualTo(9); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); } @Test @@ -239,12 +240,12 @@ public void testThreeUserDiffArcAndSpanAnnotation() throws Exception userDocs.put(user2, asList(document)); userDocs.put(user3, asList(document)); - Map userCases = new HashMap<>(); + var userCases = new HashMap(); userCases.put(user1, kappatestCas); userCases.put(user2, kappaspandiff); userCases.put(user3, kappaspanarcdiff); - Map> documentJCases = new HashMap<>(); + var documentJCases = new HashMap>(); documentJCases.put(document, userCases); // Check against new impl @@ -252,26 +253,26 @@ public void testThreeUserDiffArcAndSpanAnnotation() throws Exception LINK_TARGET_AS_LABEL, convert(userCases)); DiffResult result = diff.toResult(); - Map> user1and2 = convert(userCases); + var user1and2 = convert(userCases); user1and2.remove("user3"); - AgreementResult agreement12 = getCohenKappaAgreement(diff, Dependency.class.getName(), - "DependencyType", user1and2); + var agreement12 = getCohenKappaAgreement(diff, Dependency.class.getName(), "DependencyType", + user1and2); - Map> user2and3 = convert(userCases); + var user2and3 = convert(userCases); user2and3.remove("user1"); - AgreementResult agreement23 = getCohenKappaAgreement(diff, Dependency.class.getName(), - "DependencyType", user2and3); + var agreement23 = getCohenKappaAgreement(diff, Dependency.class.getName(), "DependencyType", + user2and3); - Map> user1and3 = convert(userCases); + var user1and3 = convert(userCases); user1and3.remove("user2"); - AgreementResult agreement13 = getCohenKappaAgreement(diff, Dependency.class.getName(), - "DependencyType", user1and3); + var agreement13 = getCohenKappaAgreement(diff, Dependency.class.getName(), "DependencyType", + user1and3); // Asserts - result.print(System.out); + // result.print(System.out); - System.out.printf("New agreement 1/2: %s%n", agreement12.toString()); - System.out.printf("New agreement 2/3: %s%n", agreement23.toString()); - System.out.printf("New agreement 1/3: %s%n", agreement13.toString()); + // System.out.printf("New agreement 1/2: %s%n", agreement12.toString()); + // System.out.printf("New agreement 2/3: %s%n", agreement23.toString()); + // System.out.printf("New agreement 1/3: %s%n", agreement13.toString()); } } diff --git a/inception/inception-annotation-storage-api/pom.xml b/inception/inception-annotation-storage-api/pom.xml index ed55e8c6c12..9a2a6d72826 100644 --- a/inception/inception-annotation-storage-api/pom.xml +++ b/inception/inception-annotation-storage-api/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-annotation-storage-api INCEpTION - Core - Annotation Storage - API diff --git a/inception/inception-annotation-storage/pom.xml b/inception/inception-annotation-storage/pom.xml index 9ccf64659f1..d4d1046d4f1 100644 --- a/inception/inception-annotation-storage/pom.xml +++ b/inception/inception-annotation-storage/pom.xml @@ -22,7 +22,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-annotation-storage INCEpTION - Core - Annotation Storage diff --git a/inception/inception-annotation-storage/src/test/java/de/tudarmstadt/ukp/inception/annotation/storage/CasStorageServiceImplTest.java b/inception/inception-annotation-storage/src/test/java/de/tudarmstadt/ukp/inception/annotation/storage/CasStorageServiceImplTest.java index 9e193252cd3..eebff4d6d7c 100644 --- a/inception/inception-annotation-storage/src/test/java/de/tudarmstadt/ukp/inception/annotation/storage/CasStorageServiceImplTest.java +++ b/inception/inception-annotation-storage/src/test/java/de/tudarmstadt/ukp/inception/annotation/storage/CasStorageServiceImplTest.java @@ -69,6 +69,7 @@ import de.tudarmstadt.ukp.inception.annotation.storage.config.CasStoragePropertiesImpl; import de.tudarmstadt.ukp.inception.annotation.storage.driver.filesystem.FileSystemCasStorageDriver; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.schema.api.event.LayerConfigurationChangedEvent; import de.tudarmstadt.ukp.inception.support.logging.Logging; import de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil; @@ -105,7 +106,7 @@ public void setup() throws Exception deleteCounter.set(0); deleteInitialCounter.set(0); - repositoryProperties = new RepositoryProperties(); + repositoryProperties = new RepositoryPropertiesImpl(); repositoryProperties.setPath(testFolder); MDC.put(Logging.KEY_REPOSITORY_PATH, repositoryProperties.getPath().toString()); diff --git a/inception/inception-annotation-storage/src/test/resources/log4j2-test.xml b/inception/inception-annotation-storage/src/test/resources/log4j2-test.xml index eb8995bfc3f..73d268eedda 100644 --- a/inception/inception-annotation-storage/src/test/resources/log4j2-test.xml +++ b/inception/inception-annotation-storage/src/test/resources/log4j2-test.xml @@ -6,13 +6,11 @@ - - - + + + + + diff --git a/inception/inception-api-annotation/pom.xml b/inception/inception-api-annotation/pom.xml index fe11a68e697..2223a6d52e6 100644 --- a/inception/inception-api-annotation/pom.xml +++ b/inception/inception-api-annotation/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-api-annotation INCEpTION - Core - Annotation API diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/actionbar/export/ExportDocumentDialogContent.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/actionbar/export/ExportDocumentDialogContent.java index 7af97baf169..2ac573a7b72 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/actionbar/export/ExportDocumentDialogContent.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/actionbar/export/ExportDocumentDialogContent.java @@ -17,12 +17,9 @@ */ package de.tudarmstadt.ukp.clarin.webanno.api.annotation.actionbar.export; -import static java.util.stream.Collectors.toList; - import java.io.File; import java.io.FileInputStream; import java.io.Serializable; -import java.util.List; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -73,19 +70,19 @@ public ExportDocumentDialogContent(String aId, IModel aModel) super(aId); state = aModel; - List writeableFormats = importExportService.getWritableFormats().stream() + var writeableFormats = importExportService.getWritableFormats().stream() .map(FormatSupport::getName) // - .sorted() // - .collect(toList()); + .sorted(String.CASE_INSENSITIVE_ORDER) // + .toList(); - Preferences prefs = new Preferences(); + var prefs = new Preferences(); prefs.format = writeableFormats.get(0); preferences = Model.of(prefs); queue(new Form<>("form", CompoundPropertyModel.of(preferences))); - DropDownChoice format = new DropDownChoice<>("format", writeableFormats); + var format = new DropDownChoice("format", writeableFormats); format.add(new LambdaAjaxFormComponentUpdatingBehavior("change")); queue(format); diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/keybindings/KeyBindingsPanel.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/keybindings/KeyBindingsPanel.java index 9b7c0fc2d54..aa69f78ebd1 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/keybindings/KeyBindingsPanel.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/annotation/keybindings/KeyBindingsPanel.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Set; -import org.apache.uima.cas.CAS; import org.apache.wicket.ClassAttributeModifier; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -34,17 +33,14 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.spring.injection.annot.SpringBean; -import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.rendering.editorstate.FeatureState; import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; -import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupport; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink; import de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior; import de.tudarmstadt.ukp.inception.support.wicket.input.InputBehavior; import wicket.contrib.input.events.EventType; -import wicket.contrib.input.events.key.KeyType; /** * This can be added to a feature editor component to enable keyboard shortcuts for it. @@ -59,6 +55,7 @@ public class KeyBindingsPanel private @SpringBean FeatureSupportRegistry featureSupportRegistry; private boolean keyBindingsVisible = false; + private boolean toggleVisibile = true; private IModel> keyBindings; public KeyBindingsPanel(String aId, IModel> aKeyBindings, @@ -71,7 +68,7 @@ public KeyBindingsPanel(String aId, IModel> aKeyBindings, add(visibleWhen(() -> keyBindings.map(List::isEmpty).getObject())); - WebMarkupContainer keyBindingsContainer = new WebMarkupContainer("keyBindingsContainer"); + var keyBindingsContainer = new WebMarkupContainer("keyBindingsContainer"); keyBindingsContainer.setOutputMarkupId(true); keyBindingsContainer.add(new ClassAttributeModifier() { @@ -88,16 +85,15 @@ protected Set update(Set aClasses) }); add(keyBindingsContainer); - Label showHideMessage = new Label("showHideMessage", () -> { - return keyBindingsVisible ? "Hide key bindings..." : "Show key bindings..."; - }); + var showHideMessage = new Label("showHideMessage", + () -> keyBindingsVisible ? "Hide key bindings..." : "Show key bindings..."); showHideMessage.setOutputMarkupId(true); + showHideMessage.add(visibleWhen(() -> toggleVisibile)); - LambdaAjaxLink toggleKeyBindingHints = new LambdaAjaxLink("toggleKeyBindingHints", - _target -> { - keyBindingsVisible = !keyBindingsVisible; - _target.add(keyBindingsContainer, showHideMessage); - }); + var toggleKeyBindingHints = new LambdaAjaxLink("toggleKeyBindingHints", _target -> { + keyBindingsVisible = !keyBindingsVisible; + _target.add(keyBindingsContainer, showHideMessage); + }); toggleKeyBindingHints.add(showHideMessage); toggleKeyBindingHints .add(LambdaBehavior.visibleWhen(() -> !aKeyBindings.getObject().isEmpty())); @@ -110,14 +106,14 @@ protected Set update(Set aClasses) @Override protected void populateItem(ListItem aItem) { - AnnotationFeature feature = aModel.getObject().feature; - String value = aItem.getModelObject().getValue(); - FeatureSupport fs = featureSupportRegistry.findExtension(feature).orElseThrow(); + var feature = aModel.getObject().feature; + var value = aItem.getModelObject().getValue(); + var fs = featureSupportRegistry.findExtension(feature).orElseThrow(); - LambdaAjaxLink link = new LambdaAjaxLink("shortcut", + var link = new LambdaAjaxLink("shortcut", _target -> actionInvokeShortcut(_target, aItem.getModelObject())); - KeyType[] keyCombo = aItem.getModelObject().asKeyTypes(); + var keyCombo = aItem.getModelObject().asKeyTypes(); link.add(new InputBehavior(keyCombo, EventType.click) { private static final long serialVersionUID = -413804179695231212L; @@ -137,6 +133,11 @@ protected Boolean getDisable_in_input() }); } + public void setToggleVisibile(boolean aToggleVisibile) + { + toggleVisibile = aToggleVisibile; + } + public FeatureState getModelObject() { return (FeatureState) getDefaultModelObject(); @@ -145,9 +146,9 @@ public FeatureState getModelObject() private void actionInvokeShortcut(AjaxRequestTarget aTarget, KeyBinding aKeyBinding) throws IOException, AnnotationException { - CAS cas = actionHandler.getEditorCas(); - AnnotationFeature feature = getModelObject().feature; - FeatureSupport fs = featureSupportRegistry.findExtension(feature).orElseThrow(); + var cas = actionHandler.getEditorCas(); + var feature = getModelObject().feature; + var fs = featureSupportRegistry.findExtension(feature).orElseThrow(); getModelObject().value = fs.wrapFeatureValue(feature, cas, aKeyBinding.getValue()); actionHandler.actionCreateOrUpdate(aTarget, actionHandler.getEditorCas()); } diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/bool/BooleanFeatureSupport.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/bool/BooleanFeatureSupport.java index 04188e061d4..19fb8353c30 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/bool/BooleanFeatureSupport.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/bool/BooleanFeatureSupport.java @@ -18,8 +18,8 @@ package de.tudarmstadt.ukp.inception.annotation.feature.bool; import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; -import java.util.Collections; import java.util.List; import org.apache.uima.cas.CAS; @@ -57,7 +57,7 @@ public void afterPropertiesSet() throws Exception @Override public List getSupportedFeatureTypes(AnnotationLayer aAnnotationLayer) { - return Collections.unmodifiableList(primitiveTypes); + return unmodifiableList(primitiveTypes); } @Override @@ -72,7 +72,7 @@ public FeatureEditor createEditor(String aId, MarkupContainer aOwner, AnnotationActionHandler aHandler, final IModel aStateModel, final IModel aFeatureStateModel) { - AnnotationFeature feature = aFeatureStateModel.getObject().feature; + var feature = aFeatureStateModel.getObject().feature; if (!accepts(feature)) { throw unsupportedFeatureTypeException(feature); diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureEditor.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureEditor.java index f2279a53ee0..b2dd0fd0246 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureEditor.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureEditor.java @@ -563,13 +563,12 @@ private void actionAdd(AjaxRequestTarget aTarget) } @SuppressWarnings("unchecked") - List links = (List) LinkFeatureEditor.this - .getModelObject().value; - AnnotatorState state = LinkFeatureEditor.this.stateModel.getObject(); + var links = (List) LinkFeatureEditor.this.getModelObject().value; + var state = LinkFeatureEditor.this.stateModel.getObject(); - LinkWithRoleModel m = new LinkWithRoleModel(); + var m = new LinkWithRoleModel(); m.role = (String) field.getModelObject(); - int insertionPoint = findInsertionPoint(links); + var insertionPoint = findInsertionPoint(links); links.add(insertionPoint, m); state.setArmedSlot(getModelObject(), insertionPoint); @@ -600,12 +599,11 @@ private void actionSet(AjaxRequestTarget aTarget) } @SuppressWarnings("unchecked") - List links = (List) LinkFeatureEditor.this - .getModelObject().value; - AnnotatorState state = LinkFeatureEditor.this.stateModel.getObject(); + var links = (List) LinkFeatureEditor.this.getModelObject().value; + var state = LinkFeatureEditor.this.stateModel.getObject(); // Update the slot - LinkWithRoleModel m = links.get(state.getArmedSlot()); + var m = links.get(state.getArmedSlot()); m.role = (String) field.getModelObject(); links.set(state.getArmedSlot(), m); // avoid reordering @@ -623,11 +621,10 @@ private void actionSet(AjaxRequestTarget aTarget) private void actionDel(AjaxRequestTarget aTarget) { @SuppressWarnings("unchecked") - List links = (List) LinkFeatureEditor.this - .getModelObject().value; - AnnotatorState state = LinkFeatureEditor.this.stateModel.getObject(); + var links = (List) LinkFeatureEditor.this.getModelObject().value; + var state = LinkFeatureEditor.this.stateModel.getObject(); - LinkWithRoleModel linkWithRoleModel = links.get(state.getArmedSlot()); + var linkWithRoleModel = links.get(state.getArmedSlot()); links.remove(state.getArmedSlot()); state.clearArmedSlot(); @@ -638,7 +635,7 @@ private void actionDel(AjaxRequestTarget aTarget) private void actionToggleArmedState(AjaxRequestTarget aTarget, Item aItem) { - AnnotatorState state = LinkFeatureEditor.this.stateModel.getObject(); + var state = LinkFeatureEditor.this.stateModel.getObject(); if (state.isArmedSlot(getModelObject(), aItem.getIndex())) { state.clearArmedSlot(); diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureSupport.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureSupport.java index ce91be4fdae..ce4042b39aa 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureSupport.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/link/LinkFeatureSupport.java @@ -45,6 +45,9 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Tag; 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.chain.ChainLayerSupport; +import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationLayerSupport; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.rendering.editorstate.AnnotatorState; import de.tudarmstadt.ukp.inception.rendering.editorstate.FeatureState; @@ -54,7 +57,6 @@ import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupport; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureType; import de.tudarmstadt.ukp.inception.schema.api.feature.LinkWithRoleModel; -import de.tudarmstadt.ukp.inception.support.WebAnnoConst; import de.tudarmstadt.ukp.inception.support.json.JSONUtil; import de.tudarmstadt.ukp.inception.support.uima.ICasUtil; @@ -92,23 +94,22 @@ public void setBeanName(String aBeanName) } @Override - public List getSupportedFeatureTypes(AnnotationLayer aAnnotationLayer) + public List getSupportedFeatureTypes(AnnotationLayer aLayer) { - List types = new ArrayList<>(); + var types = new ArrayList(); // Slot features are only supported on span layers - if (!WebAnnoConst.CHAIN_TYPE.equals(aAnnotationLayer.getType()) - && !WebAnnoConst.RELATION_TYPE.equals(aAnnotationLayer.getType())) { + if (!ChainLayerSupport.TYPE.equals(aLayer.getType()) + && !RelationLayerSupport.TYPE.equals(aLayer.getType())) { // Add layers of type SPAN available in the project - for (AnnotationLayer spanLayer : annotationService - .listAnnotationLayer(aAnnotationLayer.getProject())) { + for (var spanLayer : annotationService.listAnnotationLayer(aLayer.getProject())) { if (Token.class.getName().equals(spanLayer.getName()) || Sentence.class.getName().equals(spanLayer.getName())) { continue; } - if (WebAnnoConst.SPAN_TYPE.equals(spanLayer.getType())) { + if (SpanLayerSupport.TYPE.equals(spanLayer.getType())) { types.add(new FeatureType(spanLayer.getName(), "Link: " + spanLayer.getUiName(), featureSupportId)); } @@ -189,7 +190,7 @@ public void generateFeature(TypeSystemDescription aTSD, TypeDescription aTD, AnnotationFeature aFeature) { // Link type - TypeDescription linkTD = aTSD.addType(aFeature.getLinkTypeName(), "", CAS.TYPE_NAME_TOP); + var linkTD = aTSD.addType(aFeature.getLinkTypeName(), "", CAS.TYPE_NAME_TOP); linkTD.addFeature(aFeature.getLinkTypeRoleFeatureName(), "", CAS.TYPE_NAME_STRING); linkTD.addFeature(aFeature.getLinkTypeTargetFeatureName(), "", aFeature.getType()); @@ -201,7 +202,7 @@ public void generateFeature(TypeSystemDescription aTSD, TypeDescription aTD, @Override public List getFeatureValue(AnnotationFeature aFeature, FeatureStructure aFS) { - Feature linkFeature = aFS.getType().getFeatureByBaseName(aFeature.getName()); + var linkFeature = aFS.getType().getFeatureByBaseName(aFeature.getName()); if (linkFeature == null) { wrapFeatureValue(aFeature, aFS.getCAS(), null); @@ -215,7 +216,7 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, throws AnnotationException { if (aValue instanceof List && aFeature.getTagset() != null) { - for (LinkWithRoleModel link : (List) aValue) { + for (var link : (List) aValue) { if (!annotationService.existsTag(link.role, aFeature.getTagset())) { if (!aFeature.getTagset().isCreateTag()) { throw new IllegalArgumentException("[" + link.role diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java index bc27801acd7..ee3d69ab622 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiValueStringFeatureSupport.java @@ -22,8 +22,11 @@ import static org.apache.uima.cas.CAS.TYPE_NAME_STRING_ARRAY; import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.stream.Collectors; @@ -32,6 +35,7 @@ import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.StringArrayFS; import org.apache.uima.fit.util.FSUtil; +import org.apache.uima.jcas.cas.StringArray; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; @@ -50,6 +54,7 @@ import de.tudarmstadt.ukp.inception.rendering.vmodel.VLazyDetail; import de.tudarmstadt.ukp.inception.rendering.vmodel.VLazyDetailGroup; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; import de.tudarmstadt.ukp.inception.schema.api.adapter.IllegalFeatureValueException; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureEditor; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureType; @@ -127,6 +132,29 @@ public List unwrapFeatureValue(AnnotationFeature aFeature, CAS aCAS, Obj "Unable to handle value [" + aValue + "] of type [" + aValue.getClass() + "]"); } + @Override + public Serializable wrapFeatureValue(AnnotationFeature aFeature, CAS aCAS, Object aValue) + { + if (aValue == null) { + return null; + } + + if (aValue instanceof StringArray value) { + return new ArrayList<>(asList(value.toArray())); + } + + if (aValue instanceof String value) { + return new ArrayList<>(asList(value)); + } + + if (aValue instanceof Collection) { + return (Serializable) aValue; + } + + throw new IllegalArgumentException( + "Unable to handle value [" + aValue + "] of type [" + aValue.getClass() + "]"); + } + @Override public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, Object aValue) throws IllegalFeatureValueException @@ -135,8 +163,8 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, throw unsupportedFeatureTypeException(aFeature); } - FeatureStructure fs = getFS(aCas, aFeature, aAddress); - List values = unwrapFeatureValue(aFeature, fs.getCAS(), aValue); + var fs = getFS(aCas, aFeature, aAddress); + var values = unwrapFeatureValue(aFeature, fs.getCAS(), aValue); if (values == null || values.isEmpty()) { FSUtil.setFeature(fs, aFeature.getName(), (Collection) null); return; @@ -147,7 +175,7 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, } // Create a new array if size differs otherwise re-use existing one - StringArrayFS array = FSUtil.getFeature(fs, aFeature.getName(), StringArrayFS.class); + var array = FSUtil.getFeature(fs, aFeature.getName(), StringArrayFS.class); if (array == null || (array.size() != values.size())) { array = fs.getCAS().createStringArrayFS(values.size()); } @@ -158,6 +186,46 @@ public void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, fs.setFeatureValue(fs.getType().getFeatureByBaseName(aFeature.getName()), array); } + @Override + public void pushFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, Object aValue) + throws AnnotationException + { + if (!accepts(aFeature)) { + throw unsupportedFeatureTypeException(aFeature); + } + + var fs = getFS(aCas, aFeature, aAddress); + var newValues = unwrapFeatureValue(aFeature, fs.getCAS(), aValue); + if (newValues == null || newValues.isEmpty()) { + return; + } + + for (String value : newValues) { + schemaService.createMissingTag(aFeature, value); + } + + var feature = fs.getType().getFeatureByBaseName(aFeature.getName()); + var oldValues = (Collection) wrapFeatureValue(aFeature, fs.getCAS(), + fs.getFeatureValue(feature)); + + var mergedValues = new LinkedHashSet(); + if (oldValues != null) { + mergedValues.addAll(oldValues); + } + mergedValues.addAll(newValues); + + // Create a new array if size differs otherwise re-use existing one + var array = FSUtil.getFeature(fs, aFeature.getName(), StringArrayFS.class); + if (array == null || (array.size() != mergedValues.size())) { + array = fs.getCAS().createStringArrayFS(mergedValues.size()); + } + + array.copyFromArray(mergedValues.toArray(new String[mergedValues.size()]), 0, 0, + mergedValues.size()); + + fs.setFeatureValue(feature, array); + } + @Override public Panel createTraitsEditor(String aId, IModel aFeatureModel) { diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.html b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.html index 3d5e65b690b..4d47ef4214f 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.html +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.html @@ -32,16 +32,17 @@
-
- - - - - - +
-
diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.java index beea7c59058..db04c4c2923 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/RadioGroupStringFeatureEditor.java @@ -19,9 +19,11 @@ import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.visibleWhen; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.wicket.behavior.Behavior.onTag; import static org.apache.wicket.event.Broadcast.BUBBLE; import java.util.List; +import java.util.Objects; import org.apache.commons.collections4.CollectionUtils; import org.apache.wicket.AttributeModifier; @@ -32,17 +34,18 @@ import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.FormComponent; +import org.apache.wicket.markup.html.form.Radio; import org.apache.wicket.markup.html.form.RadioGroup; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.IModelComparator; +import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.PropertyModel; -import com.googlecode.wicket.kendo.ui.form.Radio; - +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.keybindings.KeyBinding; import de.tudarmstadt.ukp.clarin.webanno.api.annotation.keybindings.KeyBindingsPanel; -import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.api.annotation.keybindings.KeybindingLabel; import de.tudarmstadt.ukp.clarin.webanno.model.ReorderableTag; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; import de.tudarmstadt.ukp.inception.rendering.editorstate.FeatureState; @@ -56,26 +59,34 @@ public class RadioGroupStringFeatureEditor { private static final long serialVersionUID = 9112762779124263198L; + private StringFeatureTraits traits; + private KeyBindingsPanel keyBindingsPanel; + public RadioGroupStringFeatureEditor(String aId, MarkupContainer aItem, IModel aModel, AnnotationActionHandler aHandler) { super(aId, aItem, aModel); - AnnotationFeature feat = getModelObject().feature; - StringFeatureTraits traits = readFeatureTraits(feat); + var feat = getModelObject().feature; + traits = readFeatureTraits(feat); add(new LambdaAjaxLink("clear", this::actionClear)); - WebMarkupContainer emptyTagsetWarning = new WebMarkupContainer("emptyTagsetWarning"); + var emptyTagsetWarning = new WebMarkupContainer("emptyTagsetWarning"); emptyTagsetWarning.setOutputMarkupPlaceholderTag(true); emptyTagsetWarning.add(visibleWhen(() -> CollectionUtils.isEmpty(getModelObject().tagset))); add(emptyTagsetWarning); - add(new KeyBindingsPanel("keyBindings", () -> traits.getKeyBindings(), aModel, aHandler) - // The key bindings are only visible when the label is also enabled, i.e. when the - // editor is used in a "normal" context and not e.g. in the keybindings - // configuration panel - .add(visibleWhen(() -> getLabelComponent().isVisible()))); + keyBindingsPanel = new KeyBindingsPanel("keyBindings", () -> traits.getKeyBindings(), + aModel, aHandler); + // The key bindings are only visible when the label is also enabled, i.e. when the + // editor is used in a "normal" context and not e.g. in the keybindings + // configuration panel + keyBindingsPanel.add(visibleWhen(() -> getLabelComponent().isVisible())); + // We render the key bindings inside the choices already. The panel is only necessary to + // actually react to the bindings + keyBindingsPanel.setToggleVisibile(false); + add(keyBindingsPanel); } private void actionClear(AjaxRequestTarget aTarget) @@ -87,10 +98,9 @@ private void actionClear(AjaxRequestTarget aTarget) } @Override - protected FormComponent createInputField() + protected FormComponent createInputField() { - RadioGroup group = new RadioGroup("value", - new PropertyModel<>(getModel(), "value")) + var group = new RadioGroup("value", new PropertyModel<>(getModel(), "value")) { private static final long serialVersionUID = 1L; @@ -112,9 +122,9 @@ public boolean compare(Component component, Object b) return false; } - String tagA = a instanceof ReorderableTag ? ((ReorderableTag) a).getName() + var tagA = a instanceof ReorderableTag ? ((ReorderableTag) a).getName() : String.valueOf(a); - String tagB = b instanceof ReorderableTag ? ((ReorderableTag) b).getName() + var tagB = b instanceof ReorderableTag ? ((ReorderableTag) b).getName() : String.valueOf(b); return tagA.equals(tagB); @@ -146,41 +156,64 @@ private ListView createFeaturesList(RadioGroup aGroup, @Override protected void populateItem(ListItem item) { - Radio radio = new Radio("radio", (IModel) item.getModel(), aGroup); - Radio.Label label = new Radio.Label("label", - item.getModel().map(ReorderableTag::getName), radio); + var tag = item.getModel(); + + var radio = new Radio<>("radio", (IModel) tag, aGroup); + item.add(radio); + item.add(radio); + + var button = new WebMarkupContainer("button", item.getModel()); + button.add(onTag((c, t) -> t.put("for", radio.getMarkupId()))); + item.add(button); + + var label = new Label("label", item.getModel().map(ReorderableTag::getName)); label.add(AttributeModifier.append("class", item.getModel().map(ReorderableTag::getReordered) .map(_flag -> _flag ? "font-weight-bold" : ""))); + button.add(label); - String descriptionText = item.getModel().getObject().getDescription(); - - Label score = new Label("score", item.getModel().map(ReorderableTag::getScore)); + var score = new Label("score", item.getModel().map(ReorderableTag::getScore)); + button.add(score); - WebMarkupContainer description = new WebMarkupContainer("description"); + var descriptionText = item.getModel().getObject().getDescription(); + var description = new WebMarkupContainer("description"); description.add(visibleWhen(() -> isNotBlank(descriptionText))); - if (isNotBlank(descriptionText)) { - DescriptionTooltipBehavior tooltip = new DescriptionTooltipBehavior( + var tooltip = new DescriptionTooltipBehavior( item.getModel().map(ReorderableTag::getName).getObject(), descriptionText); tooltip.setMode(Mode.MARKDOWN); description.add(tooltip); } + button.add(description); - add(description); - - item.add(radio, label, score, description); + var keyComboModel = LoadableDetachableModel.of(() -> getKeyBinding(tag)); + var keyCombo = new KeybindingLabel("keyCombo", keyComboModel); + keyCombo.add(visibleWhen(() -> keyBindingsPanel.isVisibilityAllowed())); + button.add(keyCombo); } }; } + private KeyBinding getKeyBinding(IModel aTag) + { + if (traits.getKeyBindings() == null) { + return null; + } + + return traits.getKeyBindings().stream() // + .filter(kb -> aTag.map(t -> Objects.equals(t.getName(), kb.getValue())) + .orElse(false).getObject()) + .findFirst() // + .orElse(null); + } + @Override public void addFeatureUpdateBehavior() { // Need to use a AjaxFormChoiceComponentUpdatingBehavior here since we use a RadioGroup // here. - FormComponent focusComponent = getFocusComponent(); + var focusComponent = getFocusComponent(); focusComponent.add(new AjaxFormChoiceComponentUpdatingBehavior() { private static final long serialVersionUID = -5058365578109385064L; diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/StringFeatureSupport.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/StringFeatureSupport.java index e3dae8fc881..f749bb3b4de 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/StringFeatureSupport.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/string/StringFeatureSupport.java @@ -233,9 +233,9 @@ public boolean suppressAutoFocus(AnnotationFeature aFeature) @Override public List lookupLazyDetails(AnnotationFeature aFeature, Object aValue) { - if (aValue instanceof String) { - var value = (String) aValue; + if (aValue instanceof String value) { var tag = schemaService.getTag(value, aFeature.getTagset()); + if (isNotBlank(value) && aFeature.getTagset() != null && tag.isEmpty()) { return asList(new VLazyDetailGroup(new VLazyDetail(value, "Tag not in tagset"))); } diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/TypeAdapter_ImplBase.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/TypeAdapter_ImplBase.java index 126fa76bd04..d00e8f59652 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/TypeAdapter_ImplBase.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/TypeAdapter_ImplBase.java @@ -144,13 +144,13 @@ private void initFeaturesCacheIfNecessary() } @Override - public void setFeatureValue(SourceDocument aDocument, String aUsername, CAS aCas, int aAddress, - AnnotationFeature aFeature, Object aValue) + public final void setFeatureValue(SourceDocument aDocument, String aUsername, CAS aCas, + int aAddress, AnnotationFeature aFeature, Object aValue) throws AnnotationException { var featureSupport = featureSupportRegistry.findExtension(aFeature).orElseThrow(); - FeatureStructure fs = selectFsByAddr(aCas, aAddress); + var fs = selectFsByAddr(aCas, aAddress); var oldValue = featureSupport.getFeatureValue(aFeature, fs); @@ -164,6 +164,27 @@ public void setFeatureValue(SourceDocument aDocument, String aUsername, CAS aCas } } + @Override + public final void pushFeatureValue(SourceDocument aDocument, String aUsername, CAS aCas, + int aAddress, AnnotationFeature aFeature, Object aValue) + throws AnnotationException + { + var featureSupport = featureSupportRegistry.findExtension(aFeature).orElseThrow(); + + var fs = selectFsByAddr(aCas, aAddress); + + var oldValue = featureSupport.getFeatureValue(aFeature, fs); + + featureSupport.pushFeatureValue(aCas, aFeature, aAddress, aValue); + + var newValue = featureSupport.getFeatureValue(aFeature, fs); + + if (!Objects.equals(oldValue, newValue)) { + publishEvent(() -> new FeatureValueUpdatedEvent(this, aDocument, aUsername, getLayer(), + fs, aFeature, newValue, oldValue)); + } + } + @SuppressWarnings("unchecked") @Override public T getFeatureValue(AnnotationFeature aFeature, FeatureStructure aFs) diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationAdapter.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationAdapter.java index 00ba6389e46..f5bb934132c 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationAdapter.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationAdapter.java @@ -203,11 +203,21 @@ public String getSourceFeatureName() return sourceFeatureName; } + public Feature getSourceFeature(CAS aCas) + { + return getAnnotationType(aCas).getFeatureByBaseName(getSourceFeatureName()); + } + public String getTargetFeatureName() { return targetFeatureName; } + public Feature getTargetFeature(CAS aCas) + { + return getAnnotationType(aCas).getFeatureByBaseName(getTargetFeatureName()); + } + @Override public List> validate(CAS aCas) { diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationRenderer.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationRenderer.java index bbb997c1fde..a5c45d5b326 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationRenderer.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/relation/RelationRenderer.java @@ -252,8 +252,7 @@ public List render(VDocument aVDocument, AnnotationFS aFS, } @Override - public List lookupLazyDetails(CAS aCas, VID aVid, int aWindowBeginOffset, - int aWindowEndOffset) + public List lookupLazyDetails(CAS aCas, VID aVid) { if (!checkTypeSystem(aCas)) { return Collections.emptyList(); @@ -278,7 +277,7 @@ public List lookupLazyDetails(CAS aCas, VID aVid, int aWindowB renderYield(fs).ifPresent( yield -> group.addDetail(new VLazyDetail("Yield", abbreviate(yield, "...", 300)))); - var details = super.lookupLazyDetails(aCas, aVid, aWindowBeginOffset, aWindowEndOffset); + var details = super.lookupLazyDetails(aCas, aVid); if (!group.getDetails().isEmpty()) { details.add(0, group); } diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java index 62ce6cf4cd2..47dbb517f4c 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/layer/span/SpanRenderer.java @@ -99,8 +99,7 @@ public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, } @Override - public List lookupLazyDetails(CAS aCas, VID aVid, int aWindowBeginOffset, - int aWindowEndOffset) + public List lookupLazyDetails(CAS aCas, VID aVid) { if (!checkTypeSystem(aCas)) { return Collections.emptyList(); @@ -112,7 +111,7 @@ public List lookupLazyDetails(CAS aCas, VID aVid, int aWindowB group.addDetail( new VLazyDetail("Text", abbreviate(fs.getCoveredText(), MAX_HOVER_TEXT_LENGTH))); - var details = super.lookupLazyDetails(aCas, aVid, aWindowBeginOffset, aWindowEndOffset); + var details = super.lookupLazyDetails(aCas, aVid); if (!group.getDetails().isEmpty()) { details.add(0, group); } diff --git a/inception/inception-api-editor/pom.xml b/inception/inception-api-editor/pom.xml index f9a9f1ace94..503915dff48 100644 --- a/inception/inception-api-editor/pom.xml +++ b/inception/inception-api-editor/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-api-editor INCEpTION - Core - Annotation editor API diff --git a/inception/inception-api-editor/src/main/java/de/tudarmstadt/ukp/inception/editor/AnnotationEditorExtension.java b/inception/inception-api-editor/src/main/java/de/tudarmstadt/ukp/inception/editor/AnnotationEditorExtension.java index c96b308ec4d..128ca153a4a 100644 --- a/inception/inception-api-editor/src/main/java/de/tudarmstadt/ukp/inception/editor/AnnotationEditorExtension.java +++ b/inception/inception-api-editor/src/main/java/de/tudarmstadt/ukp/inception/editor/AnnotationEditorExtension.java @@ -27,6 +27,7 @@ import com.googlecode.wicket.jquery.ui.widget.menu.IMenuItem; 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.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.editor.action.AnnotationActionHandler; @@ -79,8 +80,8 @@ default void generateContextMenuItems(List aItems) // Do nothing by default } - default List lookupLazyDetails(SourceDocument aDocument, User aUser, VID aVid, - AnnotationFeature aFeature) + default List lookupLazyDetails(SourceDocument aDocument, User aUser, CAS aCas, + VID aVid, AnnotationLayer aLayer) { return Collections.emptyList(); } diff --git a/inception/inception-api-formats/pom.xml b/inception/inception-api-formats/pom.xml index 5d5274c82f0..54b53a12d1b 100644 --- a/inception/inception-api-formats/pom.xml +++ b/inception/inception-api-formats/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-api-formats INCEpTION - Core - Formats API diff --git a/inception/inception-api-formats/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/format/FormatSupport.java b/inception/inception-api-formats/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/format/FormatSupport.java index 7629c01b7d3..7fa9f24c549 100644 --- a/inception/inception-api-formats/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/format/FormatSupport.java +++ b/inception/inception-api-formats/src/main/java/de/tudarmstadt/ukp/clarin/webanno/api/format/FormatSupport.java @@ -30,14 +30,12 @@ import java.io.File; import java.io.FileNotFoundException; -import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.zip.ZipFile; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; @@ -57,6 +55,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.support.io.ZipUtils; import de.tudarmstadt.ukp.inception.support.xml.sanitizer.PolicyCollection; public interface FormatSupport @@ -110,50 +109,14 @@ default boolean hasResources() return false; } - default boolean isAccessibelResource(File aDocFile, String aResourcePath) + default boolean isAccessibleResource(File aDocFile, String aResourcePath) { return DEFAULT_PERMITTED_RESOURCE_EXTENSIONS.contains(getExtension(aResourcePath)); } default InputStream openResourceStream(File aDocFile, String aResourcePath) throws IOException { - if (!hasResources() || !isAccessibelResource(aDocFile, aResourcePath)) { - throw new FileNotFoundException("Resource not found [" + aResourcePath + "]"); - } - - if (aResourcePath.contains("..") || aResourcePath.contains("//")) { - throw new FileNotFoundException("Resource not found [" + aResourcePath + "]"); - } - - // var path = prependIfMissing(normalize(aResourcePath, true), "/"); - - ZipFile zipFile = null; - var success = false; - try { - zipFile = new ZipFile(aDocFile); - var entry = zipFile.getEntry(aResourcePath); - if (entry == null) { - throw new FileNotFoundException("Resource not found [" + aResourcePath + "]"); - } - - var finalZipFile = zipFile; - var is = new FilterInputStream(zipFile.getInputStream(entry)) - { - @Override - public void close() throws IOException - { - super.close(); - finalZipFile.close(); - } - }; - success = true; - return is; - } - finally { - if (!success && zipFile != null) { - zipFile.close(); - } - } + return ZipUtils.openResourceStream(aDocFile, aResourcePath); } /** diff --git a/inception/inception-api-render/pom.xml b/inception/inception-api-render/pom.xml index 0f90b8b5b9e..859d8cad6b2 100644 --- a/inception/inception-api-render/pom.xml +++ b/inception/inception-api-render/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-api-render INCEpTION - Core - Annotation rendering API diff --git a/inception/inception-app-webapp/pom.xml b/inception/inception-app-webapp/pom.xml index 8d4be65289f..b9d2d241e43 100644 --- a/inception/inception-app-webapp/pom.xml +++ b/inception/inception-app-webapp/pom.xml @@ -21,7 +21,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT .. @@ -393,6 +393,10 @@ de.tudarmstadt.ukp.inception.app inception-io-bioc + + de.tudarmstadt.ukp.inception.app + inception-io-brat + de.tudarmstadt.ukp.inception.app inception-io-conll @@ -948,6 +952,7 @@ de.tudarmstadt.ukp.inception.app:inception-review-editor de.tudarmstadt.ukp.inception.app:inception-io-bioc + de.tudarmstadt.ukp.inception.app:inception-io-brat de.tudarmstadt.ukp.inception.app:inception-io-nif de.tudarmstadt.ukp.inception.app:inception-io-intertext de.tudarmstadt.ukp.inception.app:inception-io-imscwb diff --git a/inception/inception-bom/pom.xml b/inception/inception-bom/pom.xml index d82cdd91244..e518d9c97f3 100644 --- a/inception/inception-bom/pom.xml +++ b/inception/inception-bom/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception - 31.0-SNAPSHOT + 32.0-SNAPSHOT ../.. @@ -33,222 +33,222 @@ de.tudarmstadt.ukp.inception.app inception-doc - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-agreement - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-agreement - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-annotation - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-project - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-tagsets - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-curation - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-curation - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-search - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-workload - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-workload-dynamic - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-workload-matrix - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-workload-ui - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-core - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-pubannotation - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-pubmed - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-elastic - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-opensearch - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-external-search-solr - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-external-search - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-dashboard - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-dashboard-activity - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-kb - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-kb-fact-linking - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-html-apache-annotator-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-html-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-html-recogito-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-pdf-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-pdf-editor2 - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-recommendation - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-recommendation-api - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-review-editor - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-sharing - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-preferences - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-guidelines - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-schema - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-active-learning - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-core - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-ui-kb - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-concept-linking - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-imls-opennlp - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-imls-elg - 31.0-SNAPSHOT + 32.0-SNAPSHOT de.tudarmstadt.ukp.inception.app inception-imls-hf - 31.0-SNAPSHOT + 32.0-SNAPSHOT + + + diff --git a/inception/inception-constraints/pom.xml b/inception/inception-constraints/pom.xml index bae8f5ab7d2..f75eb402bfc 100644 --- a/inception/inception-constraints/pom.xml +++ b/inception/inception-constraints/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-constraints INCEpTION - Core - Constraints diff --git a/inception/inception-curation-legacy/pom.xml b/inception/inception-curation-legacy/pom.xml index 05c3ba9b329..9f6689dd6ae 100644 --- a/inception/inception-curation-legacy/pom.xml +++ b/inception/inception-curation-legacy/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-curation-legacy INCEpTION - Core - Curation diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiff.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiff.java index 49f641f000a..b8a6e3d99a1 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiff.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiff.java @@ -19,13 +19,13 @@ import static de.tudarmstadt.ukp.clarin.webanno.model.LinkMode.NONE; import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.RELATION_TYPE; +import static de.tudarmstadt.ukp.inception.support.uima.ICasUtil.selectFsByAddr; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; import static org.apache.commons.collections4.CollectionUtils.subtract; -import static org.apache.commons.lang3.StringUtils.abbreviateMiddle; import static org.apache.uima.fit.util.CasUtil.select; import java.io.PrintStream; @@ -35,7 +35,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -67,7 +66,6 @@ import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.span.SpanDiffAdapter; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationAdapter; import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -78,7 +76,7 @@ public class CasDiff { private final static Logger LOG = LoggerFactory.getLogger(CasDiff.class); - private Map> cases = new LinkedHashMap<>(); + private Map cases = new LinkedHashMap<>(); private final Map configSets = new TreeMap<>(); @@ -98,42 +96,14 @@ private CasDiff(int aBegin, int aEnd, Iterable aAdapters, begin = aBegin; end = aEnd; linkCompareBehavior = aLinkCompareBehavior; + if (aAdapters != null) { - for (DiffAdapter adapter : aAdapters) { + for (var adapter : aAdapters) { diffAdapters.put(adapter.getType(), adapter); } } } - /** - * Calculate the differences between CASes. This method scopes the calculation of differences to - * a span instead of calculating them on the whole text. - * - * @param aAdapters - * a set of diff adapters telling how the diff algorithm should handle different - * features - * @param aLinkCompareBehavior - * the link comparison mode - * @param aCasMap - * a set of CASes, each associated with an ID - * @param aBegin - * begin of the span for which differences should be calculated. - * @param aEnd - * end of the span for which differences should be calculated. - * @return a diff result. - */ - public static CasDiff doDiffSingle(Iterable aAdapters, - LinkCompareBehavior aLinkCompareBehavior, Map aCasMap, int aBegin, - int aEnd) - { - Map> casMap = new LinkedHashMap<>(); - for (Entry e : aCasMap.entrySet()) { - casMap.put(e.getKey(), asList(e.getValue())); - } - - return doDiff(aAdapters, aLinkCompareBehavior, casMap, aBegin, aEnd); - } - /** * Calculate the differences between CASes. * @@ -146,7 +116,7 @@ public static CasDiff doDiffSingle(Iterable aAdapters, * @return a diff result. */ public static CasDiff doDiff(Iterable aAdapters, - LinkCompareBehavior aLinkCompareBehavior, Map> aCasMap) + LinkCompareBehavior aLinkCompareBehavior, Map aCasMap) { return doDiff(aAdapters, aLinkCompareBehavior, aCasMap, -1, -1); } @@ -169,32 +139,22 @@ public static CasDiff doDiff(Iterable aAdapters, * @return a diff. */ public static CasDiff doDiff(Iterable aAdapters, - LinkCompareBehavior aLinkCompareBehavior, Map> aCasMap, int aBegin, + LinkCompareBehavior aLinkCompareBehavior, Map aCasMap, int aBegin, int aEnd) { if (aCasMap.isEmpty()) { return new CasDiff(0, 0, aAdapters, aLinkCompareBehavior); } - List casList = aCasMap.values().iterator().next(); - if (casList.isEmpty()) { - return new CasDiff(0, 0, aAdapters, aLinkCompareBehavior); - } - - long startTime = System.currentTimeMillis(); + var startTime = System.currentTimeMillis(); - sanityCheck(aCasMap); + var diff = new CasDiff(aBegin, aEnd, aAdapters, aLinkCompareBehavior); - CasDiff diff = new CasDiff(aBegin, aEnd, aAdapters, aLinkCompareBehavior); - - for (Entry> e : aCasMap.entrySet()) { - int casId = 0; - for (CAS cas : e.getValue()) { - for (DiffAdapter adapter : aAdapters) { - // null elements in the list can occur if a user has never worked on a CAS - diff.addCas(e.getKey(), casId, cas != null ? cas : null, adapter.getType()); - } - casId++; + for (Entry e : aCasMap.entrySet()) { + var cas = e.getValue(); + for (var adapter : aAdapters) { + // null elements in the list can occur if a user has never worked on a CAS + diff.addCas(e.getKey(), cas != null ? cas : null, adapter.getType()); } } @@ -203,52 +163,9 @@ public static CasDiff doDiff(Iterable aAdapters, return diff; } - /** - * Sanity check - all CASes should have the same text. - */ - private static void sanityCheck(Map> aCasMap) - { - if (aCasMap.isEmpty()) { - return; - } - - // little hack to check if asserts are enabled - boolean assertsEnabled = false; - assert assertsEnabled = true; // Intentional side effect! - if (assertsEnabled) { - Iterator>> i = aCasMap.entrySet().iterator(); - - Entry> ref = i.next(); - String refUser = ref.getKey(); - List refCASes = ref.getValue(); - while (i.hasNext()) { - Entry> cur = i.next(); - String curUser = cur.getKey(); - List curCASes = cur.getValue(); - assert refCASes.size() == curCASes.size() : "CAS list sizes differ: " - + refCASes.size() + " vs " + curCASes.size(); - for (int n = 0; n < refCASes.size(); n++) { - CAS refCas = refCASes.get(n); - CAS curCas = curCASes.get(n); - // null elements in the list can occur if a user has never worked on a CAS - assert !(refCas != null && curCas != null) || StringUtils.equals( - refCas.getDocumentText(), - curCas.getDocumentText()) : "Trying to compare CASes with different document texts: [" - + curUser + "] having [" - + abbreviateMiddle(curCas.getDocumentText(), "...", 40) - + "] (length: " + curCas.getDocumentText().length() + ") vs [" - + refUser + "] having [" - + abbreviateMiddle(refCas.getDocumentText(), "...", 40) - + "] (length: " + refCas.getDocumentText().length() + ")"; - } - } - } - // End sanity check - } - private DiffAdapter getAdapter(String aType) { - DiffAdapter adapter = diffAdapters.get(aType); + var adapter = diffAdapters.get(aType); if (adapter == null) { LOG.warn("No diff adapter for type [" + aType + "] -- treating as without features"); adapter = new SpanDiffAdapter(aType, emptySet()); @@ -262,7 +179,7 @@ public Map getTypeAdapters() return diffAdapters; } - public Map> getCasMap() + public Map getCasMap() { return cases; } @@ -279,35 +196,20 @@ public Map> getCasMap() * @param aType * the type on which to calculate the diff. */ - private void addCas(String aCasGroupId, int aCasId, CAS aCas, String aType) + private void addCas(String aCasGroupId, CAS aCas, String aType) { // Remember that we have already seen this CAS. - List casList = cases.get(aCasGroupId); - if (casList == null) { - casList = new ArrayList<>(); - cases.put(aCasGroupId, casList); - } - - // Avoid adding same CAS twice in cases where we add multiple types from a CAS. If the - // current CAS ID is greater than the size of the current CAS list, then we did not add - // it yet. Before, we checked whether the casList already contained the current CAS, but - // that failed when we had multiple "null" CASes. - if ((casList.size() - 1) < aCasId) { - casList.add(aCas); - } - assert (casList.size() - 1) == aCasId : "Expected CAS ID [" + (casList.size() - 1) - + "] but was [" + aCasId + "]"; + cases.put(aCasGroupId, aCas); // null elements in the list can occur if a user has never worked on a CAS // We add these to the internal list above, but then we bail out here. if (aCas == null) { - LOG.debug("CAS group [" + aCasGroupId + "] does not contain a CAS at index [" + aCasId - + "]."); + LOG.debug("CAS group [" + aCasGroupId + "] does not contain a CAS"); return; } if (LOG.isDebugEnabled()) { - LOG.debug("Processing CAS group [" + aCasGroupId + "] CAS [" + aCasId + "]."); + LOG.debug("Processing CAS group [" + aCasGroupId + "]"); String collectionId = null; String documentId = null; @@ -325,8 +227,8 @@ private void addCas(String aCasGroupId, int aCasId, CAS aCas, String aType) Type type = aCas.getTypeSystem().getType(aType); if (type == null) { - LOG.debug("CAS group [" + aCasGroupId + "] CAS [" + aCasId - + "] contains no annotations of type [" + aType + "]"); + LOG.debug("CAS group [" + aCasGroupId + "] contains no annotations of type [" + aType + + "]"); return; } @@ -341,27 +243,27 @@ private void addCas(String aCasGroupId, int aCasId, CAS aCas, String aType) } if (annotations.isEmpty()) { - LOG.debug("CAS group [" + aCasGroupId + "] CAS [" + aCasId - + "] contains no annotations of type [" + aType + "]"); + LOG.debug("CAS group [" + aCasGroupId + "] contains no annotations of type [" + aType + + "]"); return; } - LOG.debug("CAS group [" + aCasGroupId + "] CAS [" + aCasId + "] contains [" - + annotations.size() + "] annotations of type [" + aType + "]"); + LOG.debug("CAS group [" + aCasGroupId + "] contains [" + annotations.size() + + "] annotations of type [" + aType + "]"); int posBefore = configSets.keySet().size(); LOG.debug("Positions before: [{}]", posBefore); - for (AnnotationFS fs : annotations) { - List positions = new ArrayList<>(); + for (var fs : annotations) { + var positions = new ArrayList(); // Get/create configuration set at the current position - positions.add(adapter.getPosition(aCasId, fs)); + positions.add(adapter.getPosition(fs)); // Generate secondary positions for multi-link features - positions.addAll(adapter.generateSubPositions(aCasId, fs, linkCompareBehavior)); + positions.addAll(adapter.generateSubPositions(fs, linkCompareBehavior)); - for (Position pos : positions) { + for (var pos : positions) { ConfigurationSet configSet = configSets.get(pos); if (configSet == null) { configSet = new ConfigurationSet(pos); @@ -418,10 +320,10 @@ private void addConfiguration(ConfigurationSet aSet, String aCasGroupId, Feature // corresponding configuration var links = FSUtil.getFeature(aFS, feat, ArrayFS.class); - for (int i = 0; i < links.size(); i++) { - FeatureStructure link = links.get(i); - DiffAdapter adapter = getAdapter(aFS.getType().getName()); - LinkFeatureDecl decl = adapter.getLinkFeature(aSet.position.getFeature()); + for (var i = 0; i < links.size(); i++) { + var link = links.get(i); + var adapter = getAdapter(aFS.getType().getName()); + var decl = adapter.getLinkFeature(aSet.position.getFeature()); // Check if this configuration is already present Configuration configuration = null; @@ -639,7 +541,7 @@ private boolean equalsFS(FeatureStructure aFS1, FeatureStructure aFS2) return false; } - DiffAdapter adapter = diffAdapters.get(type1.getName()); + var adapter = diffAdapters.get(type1.getName()); if (adapter == null) { LOG.warn("No diff adapter for type [" + type1.getName() + "] -- ignoring!"); @@ -667,9 +569,9 @@ private boolean equalsFS(FeatureStructure aFS1, FeatureStructure aFS2) sortedFeatures.removeIf(f -> adapter.getLinkFeature(f) != null); } - nextFeature: for (String feature : sortedFeatures) { - Feature f1 = type1.getFeatureByBaseName(feature); - Feature f2 = type2.getFeatureByBaseName(feature); + nextFeature: for (var feature : sortedFeatures) { + var f1 = type1.getFeatureByBaseName(feature); + var f2 = type2.getFeatureByBaseName(feature); Type range = (f1 != null) ? f1.getRange() : (f2 != null ? f2.getRange() : null); @@ -820,9 +722,9 @@ private boolean equalsAnnotationFS(AnnotationFS aFS1, AnnotationFS aFS2) } // Position check - DiffAdapter adapter = getAdapter(aFS1.getType().getName()); - Position pos1 = adapter.getPosition(0, aFS1); - Position pos2 = adapter.getPosition(0, aFS2); + var adapter = getAdapter(aFS1.getType().getName()); + var pos1 = adapter.getPosition(aFS1); + var pos2 = adapter.getPosition(aFS2); return pos1.compareTo(pos2) == 0; } @@ -894,15 +796,14 @@ private void add(String aCasGroupId, FeatureStructure aFS, String aFeature, int public AID getRepresentativeAID() { - Entry e = fsAddresses.entrySet().iterator().next(); + var e = fsAddresses.entrySet().iterator().next(); return e.getValue(); } - public FeatureStructure getRepresentative(Map> aCasMap) + public FeatureStructure getRepresentative(Map aCasMap) { - Entry e = fsAddresses.entrySet().iterator().next(); - return ICasUtil.selectFsByAddr(aCasMap.get(e.getKey()).get(position.getCasId()), - e.getValue().addr); + var e = fsAddresses.entrySet().iterator().next(); + return selectFsByAddr(aCasMap.get(e.getKey()), e.getValue().addr); } private Map getAddressByCasId() @@ -925,20 +826,15 @@ public boolean contains(String aCasGroupId, AID aAID) return aAID.equals(fsAddresses.get(aCasGroupId)); } - public FeatureStructure getFs(String aCasGroupId, int aCasId, - Class aClass, Map> aCasMap) + public FeatureStructure getFs(String aCasGroupId, + Class aClass, Map aCasMap) { AID aid = fsAddresses.get(aCasGroupId); if (aid == null) { return null; } - List casses = aCasMap.get(aCasGroupId); - if (casses == null) { - return null; - } - - CAS cas = casses.get(aCasId); + CAS cas = aCasMap.get(aCasGroupId); if (cas == null) { return null; } @@ -946,20 +842,9 @@ public FeatureStructure getFs(String aCasGroupId, i return ICasUtil.selectFsByAddr(cas, aid.addr); } - // FIXME aCasId parameter should not be required as we can get it from the position - public FeatureStructure getFs(String aCasGroupId, int aCasId, - Map> aCasMap) - { - return getFs(aCasGroupId, aCasId, FeatureStructure.class, aCasMap); - } - public FeatureStructure getFs(String aCasGroupId, Map aCasMap) { - Map> casMap = new LinkedHashMap<>(); - for (Entry e : aCasMap.entrySet()) { - casMap.put(e.getKey(), asList(e.getValue())); - } - return getFs(aCasGroupId, 0, FeatureStructure.class, casMap); + return getFs(aCasGroupId, FeatureStructure.class, aCasMap); } @Override @@ -1015,6 +900,11 @@ public boolean hasDifferences() return cachedHasDifferences; } + public boolean hasDifferencesWithExceptions(String... aCasGroupIDsToIgnore) + { + return !getDifferingConfigurationSetsWithExceptions(aCasGroupIDsToIgnore).isEmpty(); + } + public Collection getPositions() { return data.keySet(); @@ -1037,9 +927,21 @@ public ConfigurationSet getConfigurationSet(Position aPosition) public Optional findConfiguration(String aRepresentativeCasGroupId, AID aAid) { - for (ConfigurationSet cfgSet : getConfigurationSets()) { - Optional cfg = cfgSet.findConfiguration(aRepresentativeCasGroupId, - aAid); + for (var cfgSet : getConfigurationSets()) { + var cfg = cfgSet.findConfiguration(aRepresentativeCasGroupId, aAid); + if (cfg.isPresent()) { + return cfg; + } + } + + return Optional.empty(); + } + + public Optional findConfiguration(String aRepresentativeCasGroupId, + FeatureStructure aFS) + { + for (var cfgSet : getConfigurationSets()) { + var cfg = cfgSet.findConfiguration(aRepresentativeCasGroupId, aFS); if (cfg.isPresent()) { return cfg; } @@ -1156,13 +1058,12 @@ public boolean isCompleteWithExceptions(ConfigurationSet aConfigurationSet, } // Short-cut: the common use-case is to ignore a single exception, usually the curator - if (aCasGroupIDsToIgnore.length == 1) { - return unseenGroupCasIDs.size() == 1 - && unseenGroupCasIDs.contains(aCasGroupIDsToIgnore[0]); + if (aCasGroupIDsToIgnore.length == 1 && unseenGroupCasIDs.size() == 1) { + return unseenGroupCasIDs.contains(aCasGroupIDsToIgnore[0]); } // The set is complete if the unseen CAS group IDs match exactly the exceptions. - return unseenGroupCasIDs.containsAll(asList(aCasGroupIDsToIgnore)); + return subtract(unseenGroupCasIDs, asList(aCasGroupIDsToIgnore)).isEmpty(); } public Map getDifferingConfigurationSets() @@ -1173,8 +1074,8 @@ public Map getDifferingConfigurationSets() public Map getDifferingConfigurationSetsWithExceptions( String... aCasGroupIDsToIgnore) { - Map diffs = new LinkedHashMap<>(); - for (Entry e : data.entrySet()) { + var diffs = new LinkedHashMap(); + for (var e : data.entrySet()) { if (!isAgreementWithExceptions(e.getValue(), aCasGroupIDsToIgnore)) { diffs.put(e.getKey(), e.getValue()); } @@ -1196,8 +1097,8 @@ public Map getIncompleteConfigurationSets() public Map getIncompleteConfigurationSetsWithExceptions( String... aCasGroupIDsToIgnore) { - Map diffs = new LinkedHashMap<>(); - for (Entry e : data.entrySet()) { + var diffs = new LinkedHashMap(); + for (var e : data.entrySet()) { if (!isCompleteWithExceptions(e.getValue(), aCasGroupIDsToIgnore)) { diffs.put(e.getKey(), e.getValue()); } @@ -1214,7 +1115,7 @@ public int size() public int size(String aType) { int n = 0; - for (Position pos : data.keySet()) { + for (var pos : data.keySet()) { if (pos.getType().equals(aType)) { n++; } @@ -1225,8 +1126,8 @@ public int size(String aType) public void print(PrintStream aOut) { - for (Position p : getPositions()) { - ConfigurationSet configurationSet = getConfigurationSet(p); + for (var p : getPositions()) { + var configurationSet = getConfigurationSet(p); aOut.printf("=== %s -> %s %s%n", p, isAgreement(configurationSet) ? "AGREE" : "DISAGREE", isComplete(configurationSet) ? "COMPLETE" : "INCOMPLETE"); @@ -1247,18 +1148,18 @@ public static List getDiffAdapters(AnnotationSchemaService schemaSe return emptyList(); } - Project project = aLayers.iterator().next().getProject(); + var project = aLayers.iterator().next().getProject(); var featuresByLayer = schemaService.listSupportedFeatures(project).stream() // .collect(groupingBy(AnnotationFeature::getLayer)); - List adapters = new ArrayList<>(); + var adapters = new ArrayList(); nextLayer: for (AnnotationLayer layer : aLayers) { if (!layer.isEnabled()) { continue nextLayer; } - Set labelFeatures = new LinkedHashSet<>(); + var labelFeatures = new LinkedHashSet(); nextFeature: for (var f : featuresByLayer.getOrDefault(layer, emptyList())) { if (!f.isEnabled() || !f.isCuratable()) { continue nextFeature; diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffSummaryState.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffSummaryState.java new file mode 100644 index 00000000000..becaf535918 --- /dev/null +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffSummaryState.java @@ -0,0 +1,115 @@ +/* + * 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.clarin.webanno.curation.casdiff; + +import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER; + +import java.util.HashSet; + +import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; + +/** + * An enumeration to differentiate sentences in a document with different colors so as to easily + * identify + * + */ +public enum CasDiffSummaryState +{ + /** + * No conflicts of annotation in this sentence, no color - null- white + */ + AGREE("agree"), + + /** + * Stacked annotations + */ + STACKED("stacked"), + + /** + * Incomplete annotations + */ + INCOMPLETE("incomplete"), + + /** + * Conflicts of annotation found in this sentence, mark background in red + */ + DISAGREE("disagree"), + + /** + * Confirmed annotation. + */ + CURATED("curated"); + + private String cssClass; + + CasDiffSummaryState(String aCssClass) + { + cssClass = aCssClass; + } + + public String getCssClass() + { + return cssClass; + } + + public static CasDiffSummaryState calculateState(DiffResult diff) + { + var differingSets = diff.getDifferingConfigurationSetsWithExceptions(CURATION_USER); + + // CURATED: + // The curation user participates in every configuration set + var allCurated = diff.getConfigurationSets().stream() // + .allMatch(set -> set.getCasGroupIds().contains(CURATION_USER)); + if (!diff.getConfigurationSets().isEmpty() && allCurated) { + return CURATED; + } + + // AGREE: + // - there are no differences between the annotators + // - the annotations are complete + if (differingSets.isEmpty() + && diff.getIncompleteConfigurationSetsWithExceptions(CURATION_USER).isEmpty()) { + return AGREE; + } + + // Is this confSet a diff due to stacked annotations (with same configuration)? + var stackedDiff = false; + stackedDiffSet: for (var set : differingSets.values()) { + for (var user : set.getCasGroupIds()) { + if (set.getConfigurations(user).size() > 1) { + stackedDiff = true; + break stackedDiffSet; + } + } + } + + if (stackedDiff) { + return STACKED; + } + + var usersExceptCurator = new HashSet<>(diff.getCasGroupIds()); + usersExceptCurator.remove(CURATION_USER); + for (var set : diff.getIncompleteConfigurationSets().values()) { + if (!set.getCasGroupIds().containsAll(usersExceptCurator)) { + return INCOMPLETE; + } + } + + return DISAGREE; + } +} diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter.java index ac6dcff59e3..fbd13ee5506 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter.java @@ -35,17 +35,17 @@ public interface DiffAdapter */ String getType(); - Collection generateSubPositions(int aCasId, AnnotationFS aFs, + Collection generateSubPositions(AnnotationFS aFs, LinkCompareBehavior aLinkCompareBehavior); LinkFeatureDecl getLinkFeature(String aFeature); Set getLabelFeatures(); - Position getPosition(int aCasId, FeatureStructure aFS); + Position getPosition(FeatureStructure aFS); - Position getPosition(int aCasId, FeatureStructure aFS, String aFeature, String aRole, - int aLinkTargetBegin, int aLinkTargetEnd, LinkCompareBehavior aLinkCompareBehavior); + Position getPosition(FeatureStructure aFS, String aFeature, String aRole, int aLinkTargetBegin, + int aLinkTargetEnd, LinkCompareBehavior aLinkCompareBehavior); List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, int aWindowEnd); } diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter_ImplBase.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter_ImplBase.java index 76726be702f..6493566b256 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter_ImplBase.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/DiffAdapter_ImplBase.java @@ -24,7 +24,6 @@ import java.util.Set; import org.apache.uima.cas.ArrayFS; -import org.apache.uima.cas.Feature; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.FSUtil; @@ -76,32 +75,32 @@ public LinkFeatureDecl getLinkFeature(String aFeature) } @Override - public Position getPosition(int aCasId, FeatureStructure aFS) + public Position getPosition(FeatureStructure aFS) { - return getPosition(aCasId, aFS, null, null, -1, -1, null); + return getPosition(aFS, null, null, -1, -1, null); } @Override - public List generateSubPositions(int aCasId, AnnotationFS aFs, + public List generateSubPositions(AnnotationFS aFs, LinkCompareBehavior aLinkCompareBehavior) { - List subPositions = new ArrayList<>(); + var subPositions = new ArrayList(); - for (LinkFeatureDecl decl : linkFeatures) { - Feature linkFeature = aFs.getType().getFeatureByBaseName(decl.getName()); + for (var decl : linkFeatures) { + var linkFeature = aFs.getType().getFeatureByBaseName(decl.getName()); var array = FSUtil.getFeature(aFs, linkFeature, ArrayFS.class); if (array == null) { continue; } - for (FeatureStructure linkFS : array.toArray()) { - String role = linkFS.getStringValue( + for (var linkFS : array.toArray()) { + var role = linkFS.getStringValue( linkFS.getType().getFeatureByBaseName(decl.getRoleFeature())); - AnnotationFS target = (AnnotationFS) linkFS.getFeatureValue( + var target = (AnnotationFS) linkFS.getFeatureValue( linkFS.getType().getFeatureByBaseName(decl.getTargetFeature())); - Position pos = getPosition(aCasId, aFs, decl.getName(), role, target.getBegin(), - target.getEnd(), aLinkCompareBehavior); + var pos = getPosition(aFs, decl.getName(), role, target.getBegin(), target.getEnd(), + aLinkCompareBehavior); subPositions.add(pos); } } diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position.java index 065fe893d29..7f24f3376d3 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position.java @@ -29,11 +29,6 @@ public interface Position extends Comparable, Serializable { - /** - * @return the CAS id. - */ - int getCasId(); - /** * @return the type. */ diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position_ImplBase.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position_ImplBase.java index 6777079e7a9..e52e8a959dd 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position_ImplBase.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/api/Position_ImplBase.java @@ -28,7 +28,6 @@ public abstract class Position_ImplBase private static final long serialVersionUID = -1237180459049008357L; private final String type; - private final int casId; private final String feature; private final String role; @@ -42,12 +41,11 @@ public abstract class Position_ImplBase private final String collectionId; private final String documentId; - public Position_ImplBase(String aCollectionId, String aDocumentId, int aCasId, String aType, + public Position_ImplBase(String aCollectionId, String aDocumentId, String aType, String aFeature, String aRole, int aLinkTargetBegin, int aLinkTargetEnd, String aLinkTargetText, LinkCompareBehavior aBehavior) { type = aType; - casId = aCasId; feature = aFeature; linkCompareBehavior = aBehavior; @@ -67,12 +65,6 @@ public String getType() return type; } - @Override - public int getCasId() - { - return casId; - } - @Override public String getFeature() { @@ -123,10 +115,6 @@ public LinkCompareBehavior getLinkCompareBehavior() @Override public int compareTo(Position aOther) { - if (casId != aOther.getCasId()) { - return casId - aOther.getCasId(); - } - int typeCmp = type.compareTo(aOther.getType()); if (typeCmp != 0) { return typeCmp; @@ -173,8 +161,6 @@ else if (linkCompareBehavior != null) { protected void toStringFragment(StringBuilder builder) { - builder.append("cas="); - builder.append(getCasId()); if (getCollectionId() != null) { builder.append(", coll="); builder.append(getCollectionId()); diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationDiffAdapter.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationDiffAdapter.java index 0f29717d105..6044410087f 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationDiffAdapter.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationDiffAdapter.java @@ -80,7 +80,7 @@ public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, } @Override - public Position getPosition(int aCasId, FeatureStructure aFS, String aFeature, String aRole, + public Position getPosition(FeatureStructure aFS, String aFeature, String aRole, int aLinkTargetBegin, int aLinkTargetEnd, LinkCompareBehavior aLinkCompareBehavior) { Type type = aFS.getType(); @@ -107,7 +107,7 @@ public Position getPosition(int aCasId, FeatureStructure aFS, String aFeature, S aLinkTargetEnd); } - return new RelationPosition(collectionId, documentId, aCasId, getType(), + return new RelationPosition(collectionId, documentId, getType(), sourceFS != null ? sourceFS.getBegin() : -1, sourceFS != null ? sourceFS.getEnd() : -1, sourceFS != null ? sourceFS.getCoveredText() : null, diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationPosition.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationPosition.java index 5563606e833..38d85b276e5 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationPosition.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/relation/RelationPosition.java @@ -36,13 +36,13 @@ public class RelationPosition private final int targetEnd; private final String targetText; - public RelationPosition(String aCollectionId, String aDocumentId, int aCasId, String aType, + public RelationPosition(String aCollectionId, String aDocumentId, String aType, int aSourceBegin, int aSourceEnd, String aSourceText, int aTargetBegin, int aTargetEnd, String aTargetText, String aFeature, String aRole, int aLinkTargetBegin, int aLinkTargetEnd, String aLinkTargetText, LinkCompareBehavior aLinkCompareBehavior) { - super(aCollectionId, aDocumentId, aCasId, aType, aFeature, aRole, aLinkTargetBegin, - aLinkTargetEnd, aLinkTargetText, aLinkCompareBehavior); + super(aCollectionId, aDocumentId, aType, aFeature, aRole, aLinkTargetBegin, aLinkTargetEnd, + aLinkTargetText, aLinkCompareBehavior); sourceBegin = aSourceBegin; sourceEnd = aSourceEnd; sourceText = aSourceText; diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java index 6e5b2378788..c6b214dcb3b 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanDiffAdapter.java @@ -49,8 +49,8 @@ public class SpanDiffAdapter public static final SpanDiffAdapter SENTENCE_DIFF_ADAPTER = new SpanDiffAdapter( Sentence.class.getName()); - public static final SpanDiffAdapter POS_DIFF_ADAPTER = new SpanDiffAdapter(POS.class.getName(), - "PosValue", "coarseValue"); + public static final SpanDiffAdapter POS_DIFF_ADAPTER = new SpanDiffAdapter( + POS.class.getName(), "PosValue", "coarseValue"); public static final SpanDiffAdapter NER_DIFF_ADAPTER = new SpanDiffAdapter( NamedEntity.class.getName(), "value", "identifier"); @@ -78,7 +78,7 @@ public List selectAnnotationsInWindow(CAS aCas, int aWindowBegin, } @Override - public Position getPosition(int aCasId, FeatureStructure aFS, String aFeature, String aRole, + public Position getPosition(FeatureStructure aFS, String aFeature, String aRole, int aLinkTargetBegin, int aLinkTargetEnd, LinkCompareBehavior aLinkCompareBehavior) { AnnotationFS annoFS = (AnnotationFS) aFS; @@ -101,7 +101,7 @@ public Position getPosition(int aCasId, FeatureStructure aFS, String aFeature, S aLinkTargetEnd); } - return new SpanPosition(collectionId, documentId, aCasId, getType(), annoFS.getBegin(), + return new SpanPosition(collectionId, documentId, getType(), annoFS.getBegin(), annoFS.getEnd(), annoFS.getCoveredText(), aFeature, aRole, aLinkTargetBegin, aLinkTargetEnd, linkTargetText, aLinkCompareBehavior); } diff --git a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanPosition.java b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanPosition.java index d1c15500d30..4b0b6c0929a 100644 --- a/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanPosition.java +++ b/inception/inception-curation-legacy/src/main/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/span/SpanPosition.java @@ -33,12 +33,12 @@ public class SpanPosition private final int end; private final String text; - public SpanPosition(String aCollectionId, String aDocumentId, int aCasId, String aType, - int aBegin, int aEnd, String aText, String aFeature, String aRole, int aLinkTargetBegin, + public SpanPosition(String aCollectionId, String aDocumentId, String aType, int aBegin, + int aEnd, String aText, String aFeature, String aRole, int aLinkTargetBegin, int aLinkTargetEnd, String aLinkTargetText, LinkCompareBehavior aLinkCompareBehavior) { - super(aCollectionId, aDocumentId, aCasId, aType, aFeature, aRole, aLinkTargetBegin, - aLinkTargetEnd, aLinkTargetText, aLinkCompareBehavior); + super(aCollectionId, aDocumentId, aType, aFeature, aRole, aLinkTargetBegin, aLinkTargetEnd, + aLinkTargetText, aLinkCompareBehavior); begin = aBegin; end = aEnd; text = aText; diff --git a/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffTest.java b/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffTest.java index 4c129515a27..ec99fc5563a 100644 --- a/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffTest.java +++ b/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CasDiffTest.java @@ -18,6 +18,11 @@ package de.tudarmstadt.ukp.clarin.webanno.curation.casdiff; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.AGREE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.DISAGREE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.INCOMPLETE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.STACKED; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.calculateState; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CurationTestUtils.HOST_TYPE; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CurationTestUtils.createMultiLinkWithRoleTestTypeSystem; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CurationTestUtils.load; @@ -39,22 +44,17 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.Type; -import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.factory.JCasFactory; import org.apache.uima.fit.factory.TypeSystemDescriptionFactory; import org.apache.uima.fit.testing.factory.TokenBuilder; import org.apache.uima.fit.util.FSUtil; import org.apache.uima.jcas.JCas; -import org.apache.uima.resource.metadata.TypeSystemDescription; import org.apache.uima.util.CasCreationUtils; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.relation.RelationDiffAdapter; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.span.SpanDiffAdapter; @@ -71,17 +71,18 @@ public class CasDiffTest @Test public void noDataTest() throws Exception { - List diffAdapters = new ArrayList<>(); + var diffAdapters = new ArrayList(); - Map> casByUser = new LinkedHashMap<>(); + var casByUser = new LinkedHashMap(); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - assertEquals(0, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(0); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); } @Test @@ -89,20 +90,20 @@ public void singleEmptyCasTest() throws Exception { String text = ""; - CAS user1Cas = JCasFactory.createJCas().getCas(); - user1Cas.setDocumentText(text); + var user1Cas = createText(text); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1Cas)); + var casByUser = Map.of("user1", user1Cas); - List diffAdapters = asList(new SpanDiffAdapter(Token.class.getName())); + var diffAdapters = asList(new SpanDiffAdapter(Token.class.getName())); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - assertEquals(0, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); + assertThat(result.size()).isEqualTo(0); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); } @Test @@ -110,29 +111,21 @@ public void multipleEmptyCasWithMissingOnesTest() throws Exception { String text = ""; - CAS user1Cas1 = null; - CAS user1Cas2 = null; - CAS user1Cas3 = createText(text); - CAS user1Cas4 = createText(text); - CAS user2Cas1 = createText(text); - CAS user2Cas2 = null; - CAS user2Cas3 = null; - CAS user2Cas4 = createText(text); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", null); + casByUser.put("user2", createText(text)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1Cas1, user1Cas2, user1Cas3, user1Cas4)); - casByUser.put("user2", asList(user2Cas1, user2Cas2, user2Cas3, user2Cas4)); + var diffAdapters = asList(new SpanDiffAdapter(Lemma.class.getName())); - List diffAdapters = asList(new SpanDiffAdapter(Lemma.class.getName())); - - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(0, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(0); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, @@ -144,19 +137,21 @@ public void multipleEmptyCasWithMissingOnesTest() throws Exception @Test public void noDifferencesPosTest() throws Exception { - Map> casByUser = load("casdiff/noDifferences/data.conll", + var casByUser = load( // + "casdiff/noDifferences/data.conll", // "casdiff/noDifferences/data.conll"); - List diffAdapters = asList(POS_DIFF_ADAPTER); + var diffAdapters = asList(POS_DIFF_ADAPTER); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(26, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, @@ -168,19 +163,21 @@ public void noDifferencesPosTest() throws Exception @Test public void noDifferencesDependencyTest() throws Exception { - Map> casByUser = load("casdiff/noDifferences/data.conll", + var casByUser = load( // + "casdiff/noDifferences/data.conll", // "casdiff/noDifferences/data.conll"); - List diffAdapters = asList(DEPENDENCY_DIFF_ADAPTER); + var diffAdapters = asList(DEPENDENCY_DIFF_ADAPTER); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(26, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, @@ -192,22 +189,23 @@ public void noDifferencesDependencyTest() throws Exception @Test public void noDifferencesPosDependencyTest() throws Exception { - Map> casByUser = load("casdiff/noDifferences/data.conll", + var casByUser = load( // + "casdiff/noDifferences/data.conll", // "casdiff/noDifferences/data.conll"); - List diffAdapters = asList(POS_DIFF_ADAPTER, - DEPENDENCY_DIFF_ADAPTER); + var diffAdapters = asList(POS_DIFF_ADAPTER, DEPENDENCY_DIFF_ADAPTER); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(52, result.size()); - assertEquals(26, result.size(POS.class.getName())); - assertEquals(26, result.size(Dependency.class.getName())); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(52); + assertThat(result.size(POS.class.getName())).isEqualTo(26); + assertThat(result.size(Dependency.class.getName())).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, @@ -219,13 +217,14 @@ public void noDifferencesPosDependencyTest() throws Exception @Test public void singleDifferencesTest() throws Exception { - Map> casByUser = load("casdiff/singleSpanDifference/user1.conll", + var casByUser = load( // + "casdiff/singleSpanDifference/user1.conll", // "casdiff/singleSpanDifference/user2.conll"); - List diffAdapters = asList(SpanDiffAdapter.POS_DIFF_ADAPTER); + var diffAdapters = asList(POS_DIFF_ADAPTER); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); @@ -233,6 +232,11 @@ public void singleDifferencesTest() throws Exception assertEquals(1, result.getDifferingConfigurationSets().size()); assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); + // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, // entryTypes.get(0), "PosValue", casByUser); @@ -243,19 +247,21 @@ public void singleDifferencesTest() throws Exception @Test public void someDifferencesTest() throws Exception { - Map> casByUser = load("casdiff/someDifferences/user1.conll", + var casByUser = load( // + "casdiff/someDifferences/user1.conll", // "casdiff/someDifferences/user2.conll"); - List diffAdapters = asList(POS_DIFF_ADAPTER); + var diffAdapters = asList(POS_DIFF_ADAPTER); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(26, result.size()); - assertEquals(4, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).hasSize(4); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, entryTypes.get(0), @@ -267,20 +273,21 @@ public void someDifferencesTest() throws Exception @Test public void singleNoDifferencesTest() throws Exception { - Map> casByUser = load("casdiff/singleSpanNoDifference/data.conll", + var casByUser = load( // + "casdiff/singleSpanNoDifference/data.conll", // "casdiff/singleSpanNoDifference/data.conll"); - List diffAdapters = asList( - new SpanDiffAdapter(POS.class.getName(), "PosValue")); + var diffAdapters = asList(new SpanDiffAdapter(POS.class.getName(), "PosValue")); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(1, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, entryTypes.get(0), @@ -292,20 +299,22 @@ public void singleNoDifferencesTest() throws Exception @Test public void relationDistanceTest() throws Exception { - Map> casByUser = load("casdiff/relationDistance/user1.conll", + var casByUser = load( // + "casdiff/relationDistance/user1.conll", // "casdiff/relationDistance/user2.conll"); - List diffAdapters = asList(new RelationDiffAdapter( - Dependency.class.getName(), "Dependent", "Governor", "DependencyType")); + var diffAdapters = asList(new RelationDiffAdapter(Dependency.class.getName(), "Dependent", + "Governor", "DependencyType")); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(27, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(2, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(27); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).hasSize(2); + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, entryTypes.get(0), @@ -317,20 +326,21 @@ public void relationDistanceTest() throws Exception @Test public void spanLabelLabelTest() throws Exception { - Map> casByUser = load("casdiff/spanLabel/user1.conll", + var casByUser = load( // + "casdiff/spanLabel/user1.conll", // "casdiff/spanLabel/user2.conll"); - List diffAdapters = asList( - new SpanDiffAdapter(POS.class.getName(), "PosValue")); + var diffAdapters = asList(new SpanDiffAdapter(POS.class.getName(), "PosValue")); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(26, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, entryTypes.get(0), @@ -342,23 +352,22 @@ public void spanLabelLabelTest() throws Exception @Test public void relationLabelTest() throws Exception { - Map> casByUser = new HashMap<>(); - casByUser.put("user1", - asList(loadWebAnnoTsv3("casdiff/relationLabelTest/user1.tsv").getCas())); - casByUser.put("user2", - asList(loadWebAnnoTsv3("casdiff/relationLabelTest/user2.tsv").getCas())); + var casByUser = new HashMap(); + casByUser.put("user1", loadWebAnnoTsv3("casdiff/relationLabelTest/user1.tsv").getCas()); + casByUser.put("user2", loadWebAnnoTsv3("casdiff/relationLabelTest/user2.tsv").getCas()); - List diffAdapters = asList(new RelationDiffAdapter( - Dependency.class.getName(), "Dependent", "Governor", "DependencyType")); + var diffAdapters = asList(new RelationDiffAdapter(Dependency.class.getName(), "Dependent", + "Governor", "DependencyType")); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(26, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(26); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, entryTypes.get(0), @@ -370,35 +379,33 @@ public void relationLabelTest() throws Exception @Test public void relationStackedSpansTest() throws Exception { - TypeSystemDescription global = TypeSystemDescriptionFactory.createTypeSystemDescription(); - TypeSystemDescription local = TypeSystemDescriptionFactory - .createTypeSystemDescriptionFromPath( - "src/test/resources/desc/type/webannoTestTypes.xml"); + var global = TypeSystemDescriptionFactory.createTypeSystemDescription(); + var local = TypeSystemDescriptionFactory.createTypeSystemDescriptionFromPath( + "src/test/resources/desc/type/webannoTestTypes.xml"); - TypeSystemDescription merged = CasCreationUtils.mergeTypeSystems(asList(global, local)); + var merged = CasCreationUtils.mergeTypeSystems(asList(global, local)); - TokenBuilder tb = new TokenBuilder<>(Token.class, Sentence.class); + var tb = new TokenBuilder<>(Token.class, Sentence.class); - JCas jcasA = JCasFactory.createJCas(merged); + var jcasA = JCasFactory.createJCas(merged); { - CAS casA = jcasA.getCas(); + var casA = jcasA.getCas(); tb.buildTokens(jcasA, "This is a test ."); - List tokensA = new ArrayList<>(select(jcasA, Token.class)); - Token t1A = tokensA.get(0); - Token t2A = tokensA.get(tokensA.size() - 1); + var tokensA = new ArrayList<>(select(jcasA, Token.class)); + var t1A = tokensA.get(0); + var t2A = tokensA.get(tokensA.size() - 1); - NamedEntity govA = new NamedEntity(jcasA, t1A.getBegin(), t1A.getEnd()); + var govA = new NamedEntity(jcasA, t1A.getBegin(), t1A.getEnd()); govA.addToIndexes(); // Here we add a stacked named entity! new NamedEntity(jcasA, t1A.getBegin(), t1A.getEnd()).addToIndexes(); - NamedEntity depA = new NamedEntity(jcasA, t2A.getBegin(), t2A.getEnd()); + var depA = new NamedEntity(jcasA, t2A.getBegin(), t2A.getEnd()); depA.addToIndexes(); - Type relationTypeA = casA.getTypeSystem().getType("webanno.custom.Relation"); - AnnotationFS fs1A = casA.createAnnotation(relationTypeA, depA.getBegin(), - depA.getEnd()); + var relationTypeA = casA.getTypeSystem().getType("webanno.custom.Relation"); + var fs1A = casA.createAnnotation(relationTypeA, depA.getBegin(), depA.getEnd()); FSUtil.setFeature(fs1A, "Governor", govA); FSUtil.setFeature(fs1A, "Dependent", depA); FSUtil.setFeature(fs1A, "value", "REL"); @@ -407,43 +414,42 @@ public void relationStackedSpansTest() throws Exception JCas jcasB = JCasFactory.createJCas(merged); { - CAS casB = jcasB.getCas(); + var casB = jcasB.getCas(); tb.buildTokens(jcasB, "This is a test ."); - List tokensB = new ArrayList<>(select(jcasB, Token.class)); + var tokensB = new ArrayList<>(select(jcasB, Token.class)); Token t1B = tokensB.get(0); Token t2B = tokensB.get(tokensB.size() - 1); - NamedEntity govB = new NamedEntity(jcasB, t1B.getBegin(), t1B.getEnd()); + var govB = new NamedEntity(jcasB, t1B.getBegin(), t1B.getEnd()); govB.addToIndexes(); - NamedEntity depB = new NamedEntity(jcasB, t2B.getBegin(), t2B.getEnd()); + var depB = new NamedEntity(jcasB, t2B.getBegin(), t2B.getEnd()); depB.addToIndexes(); - Type relationTypeB = casB.getTypeSystem().getType("webanno.custom.Relation"); - AnnotationFS fs1B = casB.createAnnotation(relationTypeB, depB.getBegin(), - depB.getEnd()); + var relationTypeB = casB.getTypeSystem().getType("webanno.custom.Relation"); + var fs1B = casB.createAnnotation(relationTypeB, depB.getBegin(), depB.getEnd()); FSUtil.setFeature(fs1B, "Governor", govB); FSUtil.setFeature(fs1B, "Dependent", depB); FSUtil.setFeature(fs1B, "value", "REL"); casB.addFsToIndexes(fs1B); } - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - List diffAdapters = asList( - new RelationDiffAdapter("webanno.custom.Relation", WebAnnoConst.FEAT_REL_TARGET, - WebAnnoConst.FEAT_REL_SOURCE, "value")); + var diffAdapters = asList(new RelationDiffAdapter("webanno.custom.Relation", + WebAnnoConst.FEAT_REL_TARGET, WebAnnoConst.FEAT_REL_SOURCE, "value")); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(1, result.size()); - assertEquals(0, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(1); + assertThat(result.getDifferingConfigurationSets()).isEmpty(); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, @@ -469,19 +475,20 @@ public void multiValueStringFeatureDifferenceTestWithNull() throws Exception .buildAndAddToIndexes(); var casByUser = Map.of( // - "user1", asList(cas1), // - "user2", asList(cas2)); + "user1", cas1, // + "user2", cas2); - SpanDiffAdapter adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); + var adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); - CasDiff diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); assertThat(result.size()).isEqualTo(1); assertThat(result.getDifferingConfigurationSets()).hasSize(1); assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); } @Test @@ -498,19 +505,20 @@ public void multiValueStringFeatureDifferenceTest() throws Exception .buildAndAddToIndexes(); var casByUser = Map.of( // - "user1", asList(cas1), // - "user2", asList(cas2)); + "user1", cas1, // + "user2", cas2); - SpanDiffAdapter adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); + var adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); - CasDiff diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); assertThat(result.size()).isEqualTo(1); assertThat(result.getDifferingConfigurationSets()).hasSize(1); assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); } @Test @@ -527,19 +535,20 @@ public void multiValueStringFeatureNoDifferenceTest() throws Exception .buildAndAddToIndexes(); var casByUser = Map.of( // - "user1", asList(cas1), // - "user2", asList(cas2)); + "user1", cas1, // + "user2", cas2); - SpanDiffAdapter adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); + var adapter = new SpanDiffAdapter("webanno.custom.SpanMultiValue", "values"); - CasDiff diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); assertThat(result.size()).isEqualTo(1); assertThat(result.getDifferingConfigurationSets()).isEmpty(); assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); } @Test @@ -553,20 +562,21 @@ public void multiLinkWithRoleNoDifferenceTest() throws Exception makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 0, 0)); makeLinkHostFS(jcasB, 10, 10, makeLinkFS(jcasB, "slot1", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - CasDiff diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); assertEquals(4, result.size()); assertEquals(0, result.getDifferingConfigurationSets().size()); assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(calculateState(result)).isEqualTo(AGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, HOST_TYPE, "links", @@ -588,22 +598,23 @@ public void multiLinkWithRoleLabelDifferenceTest2() throws Exception JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot2", 0, 0)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - List diffAdapters = asList(adapter); + var diffAdapters = asList(adapter); - CasDiff diff = doDiff(diffAdapters, LINK_ROLE_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_ROLE_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(2, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(2); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, HOST_TYPE, @@ -619,28 +630,29 @@ public void multiLinkWithRoleLabelDifferenceTest2() throws Exception @Test public void multiLinkWithRoleTargetDifferenceTest() throws Exception { - JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); - JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - List diffAdapters = asList(adapter); + var diffAdapters = asList(adapter); - CasDiff diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); - DiffResult result = diff.toResult(); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser); + var result = diff.toResult(); // result.print(System.out); - assertEquals(2, result.size()); - assertEquals(1, result.getDifferingConfigurationSets().size()); - assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(2); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(DISAGREE); // Todo: Agreement has moved to separate project - should create agreement test there // CodingAgreementResult agreement = getCohenKappaAgreement(diff, HOST_TYPE, "links", @@ -656,28 +668,29 @@ public void multiLinkWithRoleTargetDifferenceTest() throws Exception @Test public void multiLinkWithRoleMultiTargetDifferenceTest() throws Exception { - JCas jcasA = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasA = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0), makeLinkFS(jcasA, "slot1", 10, 10)); - JCas jcasB = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasB = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - List diffAdapters = asList(adapter); + var diffAdapters = asList(adapter); - DiffResult diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // diff.print(System.out); - assertEquals(2, diff.size()); - assertEquals(1, diff.getDifferingConfigurationSets().size()); - assertEquals(0, diff.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(2); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).isEmpty(); + assertThat(calculateState(result)).isEqualTo(STACKED); // // Check against new impl // AgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, HOST_TYPE, @@ -694,28 +707,29 @@ public void multiLinkWithRoleMultiTargetDifferenceTest() throws Exception @Test public void multiLinkWithRoleMultiTargetDifferenceTest2() throws Exception { - JCas jcasA = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasA = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0), makeLinkFS(jcasA, "slot1", 10, 10)); - JCas jcasB = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); + var jcasB = JCasFactory.createJCas(createMultiLinkWithRoleTestTypeSystem()); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot2", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - List diffAdapters = asList(adapter); + var diffAdapters = asList(adapter); - DiffResult diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // diff.print(System.out); - assertEquals(3, diff.size()); - assertEquals(1, diff.getDifferingConfigurationSets().size()); - assertEquals(2, diff.getIncompleteConfigurationSets().size()); + assertThat(result.size()).isEqualTo(3); + assertThat(result.getDifferingConfigurationSets()).hasSize(1); + assertThat(result.getIncompleteConfigurationSets()).hasSize(2); + assertThat(calculateState(result)).isEqualTo(STACKED); // // Check against new impl // AgreementResult agreement = AgreementUtils.getCohenKappaAgreement(diff, HOST_TYPE, diff --git a/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CurationTestUtils.java b/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CurationTestUtils.java index c0aaaeadbfb..0a1ed1197d5 100644 --- a/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CurationTestUtils.java +++ b/inception/inception-curation-legacy/src/test/java/de/tudarmstadt/ukp/clarin/webanno/curation/casdiff/CurationTestUtils.java @@ -77,13 +77,13 @@ public static JCas loadWebAnnoTsv3(File aPath) throws UIMAException, IOException return jcas; } - public static Map> load(String... aPaths) throws UIMAException, IOException + public static Map load(String... aPaths) throws UIMAException, IOException { - Map> casByUser = new LinkedHashMap<>(); + var casByUser = new LinkedHashMap(); int n = 1; - for (String path : aPaths) { - CAS cas = readConll2006(path); - casByUser.put("user" + n, asList(cas)); + for (var path : aPaths) { + var cas = readConll2006(path); + casByUser.put("user" + n, cas); n++; } return casByUser; diff --git a/inception/inception-curation-legacy/src/test/resources/log4j2-test.xml b/inception/inception-curation-legacy/src/test/resources/log4j2-test.xml index 9a0b0f7bb4a..9991f90913a 100644 --- a/inception/inception-curation-legacy/src/test/resources/log4j2-test.xml +++ b/inception/inception-curation-legacy/src/test/resources/log4j2-test.xml @@ -6,9 +6,9 @@ - - - + + + diff --git a/inception/inception-curation/pom.xml b/inception/inception-curation/pom.xml index a6c94505a0a..65258329c2e 100644 --- a/inception/inception-curation/pom.xml +++ b/inception/inception-curation/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-curation INCEpTION - Curation diff --git a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/CasMerge.java b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/CasMerge.java index 01bbb125ba5..2d3035cae42 100644 --- a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/CasMerge.java +++ b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/CasMerge.java @@ -30,9 +30,7 @@ import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.isPrimitiveType; import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.selectSentences; import static de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil.selectTokens; -import static java.util.Arrays.asList; import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.apache.uima.fit.util.CasUtil.getType; import static org.apache.uima.fit.util.CasUtil.selectAt; @@ -43,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -55,7 +52,6 @@ import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.cas.FeatureStructure; -import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; import org.apache.uima.fit.util.FSUtil; @@ -70,7 +66,6 @@ import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.ConfigurationSet; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.Position; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.internal.AID; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.relation.RelationPosition; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.span.SpanPosition; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -168,7 +163,7 @@ public MergeStrategy getMergeStrategy() } private List chooseConfigurationToMerge(AnnotationLayer aLayer, DiffResult aDiff, - ConfigurationSet cfgs, Map> aCasMap) + ConfigurationSet cfgs) { return mergeStrategy.chooseConfigurationsToMerge(aDiff, cfgs, aLayer); } @@ -197,42 +192,38 @@ private List chooseConfigurationToMerge(AnnotationLayer aLayer, D * the annotator user owning the target annotation document * @param aTargetCas * the target CAS for the annotation document - * @param aCases + * @param aCasMap * a map of {@code CAS}s for each users and the random merge * @return a list of messages representing the result of the merge operation * @throws UIMAException * if there was an UIMA-level exception */ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocument, - String aTargetUsername, CAS aTargetCas, Map aCases) + String aTargetUsername, CAS aTargetCas, Map aCasMap) throws UIMAException { silenceEvents = true; - int updated = 0; - int created = 0; - Set messages = new LinkedHashSet<>(); + var updated = 0; + var created = 0; + var messages = new LinkedHashSet(); // Remove any annotations from the target CAS - keep type system, sentences and tokens clearAnnotations(aTargetDocument.getProject(), aTargetCas); // If there is nothing to merge, bail out - if (aCases.isEmpty()) { + if (aCasMap.isEmpty()) { return Collections.emptySet(); } - Map> casMap = new LinkedHashMap<>(); - aCases.forEach((k, v) -> casMap.put(k, asList(v))); - // Set up a cache for resolving type to layer to avoid hammering the DB as we process each // position - Map type2layer = aDiff.getPositions().stream() - .map(Position::getType) // + var type2layer = aDiff.getPositions().stream().map(Position::getType) // .distinct() // .map(type -> schemaService.findLayer(aTargetDocument.getProject(), type)) .collect(toMap(AnnotationLayer::getName, identity())); - List layerNames = new ArrayList<>(type2layer.keySet()); + var layerNames = new ArrayList<>(type2layer.keySet()); // Move token layer to front if (layerNames.contains(Token.class.getName())) { @@ -250,14 +241,14 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume // or as relation layers). // We process layer by layer so that we can order the layers (important to process tokens // and sentences before the others) - for (String layerName : layerNames) { - List positions = aDiff.getPositions().stream() + for (var layerName : layerNames) { + var positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof SpanPosition) // .map(pos -> (SpanPosition) pos) // We don't process slot features here (they are span sub-positions) .filter(pos -> pos.getFeature() == null) // - .collect(toList()); + .toList(); if (positions.isEmpty()) { continue; @@ -269,11 +260,10 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume // Slots are also excluded for the moment for (SpanPosition position : positions) { LOG.trace(" | processing {}", position); - AnnotationLayer layer = type2layer.get(position.getType()); - ConfigurationSet cfgs = aDiff.getConfigurationSet(position); + var layer = type2layer.get(position.getType()); + var cfgs = aDiff.getConfigurationSet(position); - List cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs, - casMap); + var cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs); if (cfgsToMerge.isEmpty()) { continue; @@ -281,7 +271,8 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume for (Configuration cfgToMerge : cfgsToMerge) { try { - AnnotationFS sourceFS = (AnnotationFS) cfgToMerge.getRepresentative(casMap); + AnnotationFS sourceFS = (AnnotationFS) cfgToMerge + .getRepresentative(aCasMap); CasMergeOperationResult result = mergeSpanAnnotation(aTargetDocument, aTargetUsername, type2layer.get(position.getType()), aTargetCas, sourceFS, layer.isAllowStacking()); @@ -305,14 +296,14 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume } // After the spans are in place, we can merge the slot features - for (String layerName : layerNames) { - List positions = aDiff.getPositions().stream() + for (var layerName : layerNames) { + var positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof SpanPosition) // .map(pos -> (SpanPosition) pos) // We only process slot features here .filter(pos -> pos.getFeature() != null) // - .collect(Collectors.toList()); + .toList(); if (positions.isEmpty()) { continue; @@ -320,22 +311,21 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume LOG.debug("Processing {} slot positions on layer [{}]", positions.size(), layerName); - for (SpanPosition position : positions) { + for (var position : positions) { LOG.trace(" | processing {}", position); - AnnotationLayer layer = type2layer.get(position.getType()); - ConfigurationSet cfgs = aDiff.getConfigurationSet(position); + var layer = type2layer.get(position.getType()); + var cfgs = aDiff.getConfigurationSet(position); - List cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs, - casMap); + var cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs); if (cfgsToMerge.isEmpty()) { continue; } - for (Configuration cfgToMerge : cfgsToMerge) { + for (var cfgToMerge : cfgsToMerge) { try { - AnnotationFS sourceFS = (AnnotationFS) cfgToMerge.getRepresentative(casMap); - AID sourceFsAid = cfgs.getConfigurations().get(0).getRepresentativeAID(); + var sourceFS = (AnnotationFS) cfgToMerge.getRepresentative(aCasMap); + var sourceFsAid = cfgs.getConfigurations().get(0).getRepresentativeAID(); mergeSlotFeature(aTargetDocument, aTargetUsername, type2layer.get(position.getType()), aTargetCas, sourceFS, sourceFsAid.feature, sourceFsAid.index); @@ -350,8 +340,8 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume } // Finally, we merge the relations - for (String layerName : layerNames) { - List positions = aDiff.getPositions().stream() + for (var layerName : layerNames) { + var positions = aDiff.getPositions().stream() .filter(pos -> layerName.equals(pos.getType())) .filter(pos -> pos instanceof RelationPosition) .map(pos -> (RelationPosition) pos) // @@ -366,22 +356,21 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume for (RelationPosition position : positions) { LOG.trace(" | processing {}", position); - AnnotationLayer layer = type2layer.get(position.getType()); - ConfigurationSet cfgs = aDiff.getConfigurationSet(position); + var layer = type2layer.get(position.getType()); + var cfgs = aDiff.getConfigurationSet(position); - List cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs, - casMap); + var cfgsToMerge = chooseConfigurationToMerge(layer, aDiff, cfgs); if (cfgsToMerge.isEmpty()) { continue; } - for (Configuration cfgToMerge : cfgsToMerge) { + for (var cfgToMerge : cfgsToMerge) { try { - AnnotationFS sourceFS = (AnnotationFS) cfgToMerge.getRepresentative(casMap); - CasMergeOperationResult result = mergeRelationAnnotation(aTargetDocument, - aTargetUsername, type2layer.get(position.getType()), aTargetCas, - sourceFS, layer.isAllowStacking()); + var sourceFS = (AnnotationFS) cfgToMerge.getRepresentative(aCasMap); + var result = mergeRelationAnnotation(aTargetDocument, aTargetUsername, + type2layer.get(position.getType()), aTargetCas, sourceFS, + layer.isAllowStacking()); LOG.trace(" `-> merged annotation with agreement"); switch (result.getState()) { @@ -426,7 +415,7 @@ public Set reMergeCas(DiffResult aDiff, SourceDocument aTargetDocume private void clearAnnotations(Project aProject, CAS aCas) throws UIMAException { // Copy the CAS - basically we do this just to keep the full type system information - CAS backup = WebAnnoCasUtil.createCasCopy(aCas); + var backup = WebAnnoCasUtil.createCasCopy(aCas); // Remove all annotations from the target CAS but we keep the type system! aCas.reset(); @@ -438,6 +427,7 @@ private void clearAnnotations(Project aProject, CAS aCas) throws UIMAException else { createDocumentMetadata(aCas); } + aCas.setDocumentLanguage(backup.getDocumentLanguage()); // DKPro Core Issue 435 aCas.setDocumentText(backup.getDocumentText()); @@ -452,14 +442,14 @@ private void transferSegmentation(Project aProject, CAS aCas, CAS backup) { if (!schemaService.isTokenLayerEditable(aProject)) { // Transfer token boundaries - for (AnnotationFS t : selectTokens(backup)) { + for (var t : selectTokens(backup)) { aCas.addFsToIndexes(createToken(aCas, t.getBegin(), t.getEnd())); } } if (!schemaService.isSentenceLayerEditable(aProject)) { // Transfer sentence boundaries - for (AnnotationFS s : selectSentences(backup)) { + for (var s : selectSentences(backup)) { aCas.addFsToIndexes(createSentence(aCas, s.getBegin(), s.getEnd())); } } @@ -467,7 +457,7 @@ private void transferSegmentation(Project aProject, CAS aCas, CAS backup) private static boolean existsEquivalentAt(CAS aCas, TypeAdapter aAdapter, AnnotationFS aFs) { - Type targetType = CasUtil.getType(aCas, aFs.getType().getName()); + var targetType = CasUtil.getType(aCas, aFs.getType().getName()); return selectAt(aCas, targetType, aFs.getBegin(), aFs.getEnd()).stream() // .filter(cand -> aAdapter.equivalents(aFs, cand, (_fs, _f) -> !shouldIgnoreFeatureOnMerge(_f))) // @@ -478,15 +468,14 @@ private static boolean existsEquivalentAt(CAS aCas, TypeAdapter aAdapter, Annota private static List selectCandidateRelationsAt(CAS aTargetCas, AnnotationFS aSourceFs, AnnotationFS aSourceOriginFs, AnnotationFS aSourceTargetFs) { - Type type = aSourceFs.getType(); - Type targetType = CasUtil.getType(aTargetCas, aSourceFs.getType().getName()); - Feature sourceFeat = type.getFeatureByBaseName(FEAT_REL_SOURCE); - Feature targetFeat = type.getFeatureByBaseName(FEAT_REL_TARGET); + var type = aSourceFs.getType(); + var targetType = CasUtil.getType(aTargetCas, aSourceFs.getType().getName()); + var sourceFeat = type.getFeatureByBaseName(FEAT_REL_SOURCE); + var targetFeat = type.getFeatureByBaseName(FEAT_REL_TARGET); return selectCovered(aTargetCas, targetType, aSourceFs.getBegin(), aSourceFs.getEnd()) - .stream() - .filter(fs -> fs.getFeatureValue(sourceFeat).equals(aSourceOriginFs) + .stream().filter(fs -> fs.getFeatureValue(sourceFeat).equals(aSourceOriginFs) && fs.getFeatureValue(targetFeat).equals(aSourceTargetFs)) - .collect(toList()); + .toList(); } private void copyFeatures(SourceDocument aDocument, String aUsername, TypeAdapter aAdapter, @@ -494,15 +483,15 @@ private void copyFeatures(SourceDocument aDocument, String aUsername, TypeAdapte throws AnnotationException { // Cache the feature list instead of hammering the database - List features = featureCache.computeIfAbsent(aAdapter.getLayer(), + var features = featureCache.computeIfAbsent(aAdapter.getLayer(), key -> schemaService.listSupportedFeatures(key)); - for (AnnotationFeature feature : features) { + for (var feature : features) { if (!feature.isCuratable()) { continue; } - Type sourceFsType = aAdapter.getAnnotationType(aSourceFs.getCAS()); - Feature sourceFeature = sourceFsType.getFeatureByBaseName(feature.getName()); + var sourceFsType = aAdapter.getAnnotationType(aSourceFs.getCAS()); + var sourceFeature = sourceFsType.getFeatureByBaseName(feature.getName()); if (sourceFeature == null) { throw new IllegalStateException("Target CAS type [" + sourceFsType.getName() @@ -513,7 +502,7 @@ private void copyFeatures(SourceDocument aDocument, String aUsername, TypeAdapte continue; } - Object value = aAdapter.getFeatureValue(feature, aSourceFs); + var value = aAdapter.getFeatureValue(feature, aSourceFs); try { aAdapter.setFeatureValue(aDocument, aUsername, aTargetFS.getCAS(), @@ -531,11 +520,11 @@ private void copyFeatures(SourceDocument aDocument, String aUsername, TypeAdapte private static List getCandidateAnnotations(CAS aTargetCas, TypeAdapter aAdapter, AnnotationFS aSource) { - Type targetType = CasUtil.getType(aTargetCas, aSource.getType().getName()); + var targetType = CasUtil.getType(aTargetCas, aSource.getType().getName()); return selectCovered(aTargetCas, targetType, aSource.getBegin(), aSource.getEnd()).stream() .filter(fs -> aAdapter.equivalents(fs, aSource, (_fs, _f) -> !shouldIgnoreFeatureOnMerge(_f))) - .collect(toList()); + .toList(); } public CasMergeOperationResult mergeSpanAnnotation(SourceDocument aDocument, String aUsername, @@ -554,7 +543,7 @@ public CasMergeOperationResult mergeSpanAnnotation(SourceDocument aDocument, Str } // a) if stacking allowed add this new annotation to the mergeview - Type targetType = CasUtil.getType(aTargetCas, adapter.getAnnotationTypeName()); + var targetType = CasUtil.getType(aTargetCas, adapter.getAnnotationTypeName()); var existingAnnos = selectAt(aTargetCas, targetType, aSourceFs.getBegin(), aSourceFs.getEnd()); if (existingAnnos.isEmpty() || aAllowStacking) { @@ -563,7 +552,7 @@ public CasMergeOperationResult mergeSpanAnnotation(SourceDocument aDocument, Str var mergedSpan = adapter.add(aDocument, aUsername, aTargetCas, aSourceFs.getBegin(), aSourceFs.getEnd()); - int mergedSpanAddr = -1; + var mergedSpanAddr = -1; try { copyFeatures(aDocument, aUsername, adapter, mergedSpan, aSourceFs); mergedSpanAddr = getAddr(mergedSpan); @@ -579,9 +568,9 @@ public CasMergeOperationResult mergeSpanAnnotation(SourceDocument aDocument, Str } // b) if stacking is not allowed, modify the existing annotation with this one else { - AnnotationFS annoToUpdate = existingAnnos.get(0); + var annoToUpdate = existingAnnos.get(0); copyFeatures(aDocument, aUsername, adapter, annoToUpdate, aSourceFs); - int mergedSpanAddr = getAddr(annoToUpdate); + var mergedSpanAddr = getAddr(annoToUpdate); return new CasMergeOperationResult(CasMergeOperationResult.ResultState.UPDATED, mergedSpanAddr); } @@ -592,7 +581,7 @@ public CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument, AnnotationFS aSourceFs, boolean aAllowStacking) throws AnnotationException { - RelationAdapter relationAdapter = (RelationAdapter) adapterCache.get(aAnnotationLayer); + var relationAdapter = (RelationAdapter) adapterCache.get(aAnnotationLayer); if (silenceEvents) { relationAdapter.silenceEvents(); } @@ -602,12 +591,12 @@ public CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument, "The annotation already exists in the target document."); } - AnnotationFS originFsClicked = getFeature(aSourceFs, relationAdapter.getSourceFeatureName(), + var originFsClicked = getFeature(aSourceFs, relationAdapter.getSourceFeatureName(), AnnotationFS.class); - AnnotationFS targetFsClicked = getFeature(aSourceFs, relationAdapter.getTargetFeatureName(), + var targetFsClicked = getFeature(aSourceFs, relationAdapter.getTargetFeatureName(), AnnotationFS.class); - SpanAdapter spanAdapter = (SpanAdapter) adapterCache.get(aAnnotationLayer.getAttachType()); + var spanAdapter = (SpanAdapter) adapterCache.get(aAnnotationLayer.getAttachType()); var candidateOrigins = getCandidateAnnotations(aTargetCas, spanAdapter, originFsClicked); var candidateTargets = getCandidateAnnotations(aTargetCas, spanAdapter, targetFsClicked); @@ -628,13 +617,13 @@ public CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument, "Stacked targets exist in the target document. Cannot merge this relation."); } - AnnotationFS originFs = candidateOrigins.get(0); - AnnotationFS targetFs = candidateTargets.get(0); + var originFs = candidateOrigins.get(0); + var targetFs = candidateTargets.get(0); if (relationAdapter.getAttachFeatureName() != null) { - AnnotationFS originAttachAnnotation = FSUtil.getFeature(originFs, + var originAttachAnnotation = FSUtil.getFeature(originFs, relationAdapter.getAttachFeatureName(), AnnotationFS.class); - AnnotationFS targetAttachAnnotation = FSUtil.getFeature(targetFs, + var targetAttachAnnotation = FSUtil.getFeature(targetFs, relationAdapter.getAttachFeatureName(), AnnotationFS.class); if (originAttachAnnotation == null || targetAttachAnnotation == null) { @@ -643,11 +632,10 @@ public CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument, } } - List existingAnnos = selectCandidateRelationsAt(aTargetCas, aSourceFs, - originFs, targetFs); + var existingAnnos = selectCandidateRelationsAt(aTargetCas, aSourceFs, originFs, targetFs); if (existingAnnos.isEmpty() || aAllowStacking) { - AnnotationFS mergedRelation = relationAdapter.add(aDocument, aUsername, originFs, - targetFs, aTargetCas); + var mergedRelation = relationAdapter.add(aDocument, aUsername, originFs, targetFs, + aTargetCas); try { copyFeatures(aDocument, aUsername, relationAdapter, mergedRelation, aSourceFs); } @@ -660,7 +648,7 @@ public CasMergeOperationResult mergeRelationAnnotation(SourceDocument aDocument, getAddr(mergedRelation)); } else { - AnnotationFS mergeTargetFS = existingAnnos.get(0); + var mergeTargetFS = existingAnnos.get(0); copyFeatures(aDocument, aUsername, relationAdapter, mergeTargetFS, aSourceFs); return new CasMergeOperationResult(CasMergeOperationResult.ResultState.UPDATED, getAddr(mergeTargetFS)); @@ -672,12 +660,12 @@ public CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String String aSourceFeature, int aSourceSlotIndex) throws AnnotationException { - TypeAdapter adapter = adapterCache.get(aAnnotationLayer); + var adapter = adapterCache.get(aAnnotationLayer); if (silenceEvents) { adapter.silenceEvents(); } - List candidateHosts = getCandidateAnnotations(aTargetCas, adapter, aSourceFs); + var candidateHosts = getCandidateAnnotations(aTargetCas, adapter, aSourceFs); if (candidateHosts.size() == 0) { throw new UnfulfilledPrerequisitesException( @@ -685,7 +673,7 @@ public CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String + aSourceFs.getBegin() + "," + aSourceFs.getEnd() + "] into which the link could be merged. Please add one first."); } - AnnotationFS mergeFs = candidateHosts.get(0); + var mergeFs = candidateHosts.get(0); int liIndex = aSourceSlotIndex; var slotFeature = adapter.listFeatures().stream() // @@ -699,14 +687,14 @@ public CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String } List sourceLinks = adapter.getFeatureValue(slotFeature, aSourceFs); - List targets = checkAndGetTargets(aTargetCas, + var targets = checkAndGetTargets(aTargetCas, selectAnnotationByAddr(aSourceFs.getCAS(), sourceLinks.get(liIndex).targetAddr)); if (targets.isEmpty()) { throw new AnnotationException("No suitable merge target found"); } - LinkWithRoleModel newLink = new LinkWithRoleModel(sourceLinks.get(liIndex)); + var newLink = new LinkWithRoleModel(sourceLinks.get(liIndex)); newLink.targetAddr = getAddr(targets.get(0)); List links = adapter.getFeatureValue(slotFeature, mergeFs); @@ -726,7 +714,7 @@ public CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String } } else { - LinkWithRoleModel existing = existingLinkWithTarget(newLink, links); + var existing = existingLinkWithTarget(newLink, links); if (existing != null && existing.equals(newLink)) { throw new AlreadyMergedException( "The slot has already been filled with this annotation in the target document."); @@ -749,7 +737,7 @@ public CasMergeOperationResult mergeSlotFeature(SourceDocument aDocument, String private LinkWithRoleModel existingLinkWithTarget(LinkWithRoleModel aLink, List aLinks) { - for (LinkWithRoleModel lr : aLinks) { + for (var lr : aLinks) { if (lr.targetAddr == aLink.targetAddr) { return lr; } @@ -760,14 +748,12 @@ private LinkWithRoleModel existingLinkWithTarget(LinkWithRoleModel aLink, private static List checkAndGetTargets(CAS aCas, AnnotationFS aOldTarget) throws UnfulfilledPrerequisitesException { - Type casType = CasUtil.getType(aCas, aOldTarget.getType().getName()); - List targets = selectCovered(aCas, casType, aOldTarget.getBegin(), - aOldTarget.getEnd()) - .stream() - .filter(fs -> AnnotationComparisonUtils.isEquivalentSpanAnnotation(fs, - aOldTarget, - (FeatureFilter) (_fs, _f) -> !shouldIgnoreFeatureOnMerge(_f))) - .collect(Collectors.toList()); + var casType = CasUtil.getType(aCas, aOldTarget.getType().getName()); + var targets = selectCovered(aCas, casType, aOldTarget.getBegin(), aOldTarget.getEnd()) + .stream() + .filter(fs -> AnnotationComparisonUtils.isEquivalentSpanAnnotation(fs, aOldTarget, + (FeatureFilter) (_fs, _f) -> !shouldIgnoreFeatureOnMerge(_f))) + .collect(Collectors.toList()); if (targets.size() == 0) { throw new UnfulfilledPrerequisitesException( diff --git a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/DefaultMergeStrategy.java b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/DefaultMergeStrategy.java index e56ea8af5cb..c7c1c35e30d 100644 --- a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/DefaultMergeStrategy.java +++ b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/DefaultMergeStrategy.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.curation.merge.strategy; +import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE; @@ -51,12 +52,12 @@ public List chooseConfigurationsToMerge(DiffResult aDiff, Configu return emptyList(); } - if (!aDiff.isComplete(aCfgs)) { + if (!aDiff.isCompleteWithExceptions(aCfgs, CURATION_USER)) { LOG.trace(" `-> Not merging incomplete annotation"); return emptyList(); } - if (!aDiff.isAgreement(aCfgs)) { + if (!aDiff.isAgreementWithExceptions(aCfgs, CURATION_USER)) { LOG.trace(" `-> Not merging annotation with disagreement"); return emptyList(); } diff --git a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/MergeIncompleteStrategy.java b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/MergeIncompleteStrategy.java index 39d47e83454..08c225398bc 100644 --- a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/MergeIncompleteStrategy.java +++ b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/merge/strategy/MergeIncompleteStrategy.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.curation.merge.strategy; +import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE; @@ -53,7 +54,7 @@ public List chooseConfigurationsToMerge(DiffResult aDiff, Configu return emptyList(); } - if (!aDiff.isAgreement(aCfgs)) { + if (!aDiff.isAgreementWithExceptions(aCfgs, CURATION_USER)) { LOG.trace(" `-> Not merging annotation with disagreement"); return emptyList(); } diff --git a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/service/CurationMergeServiceImpl.java b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/service/CurationMergeServiceImpl.java index 8316339322c..291fbb65e88 100644 --- a/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/service/CurationMergeServiceImpl.java +++ b/inception/inception-curation/src/main/java/de/tudarmstadt/ukp/inception/curation/service/CurationMergeServiceImpl.java @@ -17,7 +17,7 @@ */ package de.tudarmstadt.ukp.inception.curation.service; -import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiffSingle; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.getDiffAdapters; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.LinkCompareBehavior.LINK_ROLE_AS_LABEL; import static java.lang.Integer.MAX_VALUE; @@ -103,8 +103,7 @@ public Set mergeCasses(SourceDocument aDocument, String aTargetCasUs DiffResult diff; try (StopWatch watch = new StopWatch(LOG, "CasDiff")) { - diff = doDiffSingle(adapters, LINK_ROLE_AS_LABEL, aCassesToMerge, 0, MAX_VALUE) - .toResult(); + diff = doDiff(adapters, LINK_ROLE_AS_LABEL, aCassesToMerge, 0, MAX_VALUE).toResult(); } try (StopWatch watch = new StopWatch(LOG, "CasMerge")) { diff --git a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/export/CuratedDocumentsExporterTest.java b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/export/CuratedDocumentsExporterTest.java index 92df85bfb9b..c484e1fd7ef 100644 --- a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/export/CuratedDocumentsExporterTest.java +++ b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/export/CuratedDocumentsExporterTest.java @@ -56,6 +56,7 @@ import de.tudarmstadt.ukp.inception.annotation.storage.driver.filesystem.FileSystemCasStorageDriver; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.export.DocumentImportExportServiceImpl; import de.tudarmstadt.ukp.inception.export.config.DocumentImportExportServiceProperties; import de.tudarmstadt.ukp.inception.export.config.DocumentImportExportServicePropertiesImpl; @@ -95,7 +96,7 @@ public void setUp() throws Exception DocumentImportExportServiceProperties properties = new DocumentImportExportServicePropertiesImpl(); - repositoryProperties = new RepositoryProperties(); + repositoryProperties = new RepositoryPropertiesImpl(); repositoryProperties.setPath(workFolder); CasStorageDriver driver = new FileSystemCasStorageDriver(repositoryProperties, @@ -105,9 +106,9 @@ public void setUp() throws Exception new CasStorageCachePropertiesImpl(), null, schemaService)); var xmiFormatSupport = new XmiFormatSupport(new XmiFormatProperties()); - importExportSerivce = new DocumentImportExportServiceImpl(repositoryProperties, - asList(xmiFormatSupport), casStorageService, schemaService, properties, - checksRegistry, repairsRegistry, xmiFormatSupport); + importExportSerivce = new DocumentImportExportServiceImpl(asList(xmiFormatSupport), + casStorageService, schemaService, properties, checksRegistry, repairsRegistry, + xmiFormatSupport); // Dynamically generate a SourceDocument with an incrementing ID when asked for one when(documentService.getSourceDocument(any(), any())).then(invocation -> { diff --git a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasDiffLinkFeaturesTest.java b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasDiffLinkFeaturesTest.java index a9bdf8f390f..391821a6fed 100644 --- a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasDiffLinkFeaturesTest.java +++ b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasDiffLinkFeaturesTest.java @@ -29,19 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.Feature; -import org.apache.uima.cas.FeatureStructure; -import org.apache.uima.cas.Type; -import org.apache.uima.cas.text.AnnotationFS; -import org.apache.uima.jcas.JCas; import org.junit.jupiter.api.Test; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; - public class CasDiffLinkFeaturesTest extends CasMergeTestBase { @@ -49,22 +40,22 @@ public class CasDiffLinkFeaturesTest public void copyLinkToEmptyTest() throws Exception { // Set up target CAS - JCas targetCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - Type type = targetCas.getTypeSystem().getType(HOST_TYPE); - Feature feature = type.getFeatureByBaseName("f1"); - AnnotationFS mergeFs = makeLinkHostFS(targetCas, 0, 0, feature, "A"); - FeatureStructure linkFs = makeLinkFS(targetCas, "slot1", 0, 0); + var targetCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var type = targetCas.getTypeSystem().getType(HOST_TYPE); + var feature = type.getFeatureByBaseName("f1"); + var mergeFs = makeLinkHostFS(targetCas, 0, 0, feature, "A"); + var linkFs = makeLinkFS(targetCas, "slot1", 0, 0); setLinkFeatureValue(mergeFs, type.getFeatureByBaseName("links"), asList(linkFs)); // Set up source CAS - JCas sourceCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var sourceCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(sourceCas, 0, 0, feature, "A", makeLinkFS(sourceCas, "slot1", 0, 0)); // Perform diff - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(targetCas.getCas())); - casByUser.put("user2", asList(sourceCas.getCas())); - DiffResult diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", targetCas.getCas()); + casByUser.put("user2", sourceCas.getCas()); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); assertThat(diff.getDifferingConfigurationSets()).isEmpty(); assertThat(diff.getIncompleteConfigurationSets()).isEmpty(); @@ -74,23 +65,23 @@ public void copyLinkToEmptyTest() throws Exception public void copyLinkToExistingButDiffLinkTest() throws Exception { // Set up target CAS - JCas targetCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - Type type = targetCas.getTypeSystem().getType(HOST_TYPE); - Feature feature = type.getFeatureByBaseName("f1"); - AnnotationFS mergeFs = makeLinkHostFS(targetCas, 0, 0, feature, "A", + var targetCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var type = targetCas.getTypeSystem().getType(HOST_TYPE); + var feature = type.getFeatureByBaseName("f1"); + var mergeFs = makeLinkHostFS(targetCas, 0, 0, feature, "A", makeLinkFS(targetCas, "slot1", 0, 0)); - FeatureStructure linkFs = makeLinkFS(targetCas, "slot2", 0, 0); + var linkFs = makeLinkFS(targetCas, "slot2", 0, 0); setLinkFeatureValue(mergeFs, type.getFeatureByBaseName("links"), asList(linkFs)); // Set up source CAS - JCas sourceCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var sourceCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(sourceCas, 0, 0, feature, "A", makeLinkFS(sourceCas, "slot1", 0, 0)); // Perform diff - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(targetCas.getCas())); - casByUser.put("user2", asList(sourceCas.getCas())); - DiffResult diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", targetCas.getCas()); + casByUser.put("user2", sourceCas.getCas()); + var diff = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); assertThat(diff.getDifferingConfigurationSets()).isEmpty(); assertThat(diff.getIncompleteConfigurationSets()).hasSize(2); diff --git a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeRemergeTest.java b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeRemergeTest.java index d0301bf5495..93cb5b2fcd6 100644 --- a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeRemergeTest.java +++ b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeRemergeTest.java @@ -18,6 +18,9 @@ package de.tudarmstadt.ukp.inception.curation.merge; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.AGREE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.INCOMPLETE; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState.calculateState; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.LinkCompareBehavior.LINK_TARGET_AS_LABEL; import static de.tudarmstadt.ukp.dkpro.core.api.lexmorph.type.pos.POS._FeatName_PosValue; import static de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token._FeatName_pos; @@ -42,7 +45,6 @@ import java.util.Map; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.Feature; import org.apache.uima.cas.Type; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.factory.CasFactory; @@ -76,29 +78,28 @@ public void thatIncompleteAnnotationIsNotMerged() throws Exception CAS user2 = CasFactory.createText("word"); createTokenAndOptionalPos(user2, 0, 4, null); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1)); - casByUser.put("user2", asList(user2)); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", user1); + casByUser.put("user2", user2); JCas curatorCas = createText(casByUser.values().stream() // - .flatMap(Collection::stream) // .findFirst().get() // .getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); assertThat(result.getDifferingConfigurationSets()).isEmpty(); assertThat(result.getIncompleteConfigurationSets().values()) .extracting(set -> set.getPosition()) // .usingRecursiveFieldByFieldElementComparator() // .containsExactly( // - new SpanPosition(null, null, 0, POS.class.getName(), 0, 4, "word", null, - null, -1, -1, null, null)); + new SpanPosition(null, null, POS.class.getName(), 0, 4, "word", null, null, + -1, -1, null, null)); assertThat(select(curatorCas, POS.class)).isEmpty(); + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); } /** @@ -115,64 +116,63 @@ public void thatIncompleteAnnotationIsMerged() throws Exception CAS user2 = CasFactory.createText("word"); createTokenAndOptionalPos(user2, 0, 4, null); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(user1)); - casByUser.put("user2", asList(user2)); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", user1); + casByUser.put("user2", user2); - JCas curatorCas = createText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + JCas curatorCas = createText( + casByUser.values().stream().findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); sut.setMergeStrategy(new MergeIncompleteStrategy()); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); assertThat(result.getDifferingConfigurationSets()).isEmpty(); assertThat(result.getIncompleteConfigurationSets().values()) .extracting(set -> set.getPosition()) // .usingRecursiveFieldByFieldElementComparator()// .containsExactly(// - new SpanPosition(null, null, 0, POS.class.getName(), 0, 4, "word", null, - null, -1, -1, null, null)); + new SpanPosition(null, null, POS.class.getName(), 0, 4, "word", null, null, + -1, -1, null, null)); assertThat(select(curatorCas, POS.class)).hasSize(1); + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); } @Test public void multiLinkWithRoleNoDifferenceTest() throws Exception { - JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); makeLinkHostFS(jcasA, 10, 10, makeLinkFS(jcasA, "slot1", 10, 10)); - JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 0, 0)); makeLinkHostFS(jcasB, 10, 10, makeLinkFS(jcasB, "slot1", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + Map casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + var curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + curatorCas.setDocumentText(casByUser.values().stream().findFirst().get().getDocumentText()); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); - casByUser = new HashMap<>(); - casByUser.put("actual", asList(jcasA.getCas())); - casByUser.put("merge", asList(curatorCas.getCas())); + casByUser = new HashMap(); + casByUser.put("actual", jcasA.getCas()); + casByUser.put("merge", curatorCas.getCas()); result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); assertEquals(0, result.getDifferingConfigurationSets().size()); assertEquals(0, result.getIncompleteConfigurationSets().size()); + assertThat(calculateState(result)).isEqualTo(AGREE); } @Test @@ -184,20 +184,18 @@ public void multiLinkWithRoleLabelDifferenceTest() throws Exception JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot2", 0, 0)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + curatorCas.setDocumentText(casByUser.values().stream().findFirst().get().getDocumentText()); DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); Type hostType = curatorCas.getCas().getTypeSystem().getType(HOST_TYPE); FeatureSupport slotSupport = featureSupportRegistry.findExtension(slotFeature) @@ -208,33 +206,32 @@ public void multiLinkWithRoleLabelDifferenceTest() throws Exception assertThat(select(curatorCas.getCas(), hostType).stream() .map(host -> (List) slotSupport.getFeatureValue(slotFeature, host))) .allMatch(Collection::isEmpty); + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); } @Test public void multiLinkWithRoleTargetDifferenceTest() throws Exception { - JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); - JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 10, 10)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + var curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + curatorCas.setDocumentText(casByUser.values().stream().findFirst().get().getDocumentText()); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); - Type hostType = curatorCas.getCas().getTypeSystem().getType(HOST_TYPE); + var hostType = curatorCas.getCas().getTypeSystem().getType(HOST_TYPE); FeatureSupport slotSupport = featureSupportRegistry.findExtension(slotFeature) .orElseThrow(); @@ -243,79 +240,69 @@ public void multiLinkWithRoleTargetDifferenceTest() throws Exception assertThat(select(curatorCas.getCas(), hostType).stream() .map(host -> (List) slotSupport.getFeatureValue(slotFeature, host))) .allMatch(Collection::isEmpty); + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); } @Test public void multiLinkMultiHostTest() throws Exception { // Creating two span stacked annotations. This should cause the data not to be merged. - JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); makeLinkHostFS(jcasA, 0, 0, makeLinkFS(jcasA, "slot1", 0, 0)); - JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, makeLinkFS(jcasB, "slot1", 0, 0)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - CAS curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")).getCas(); - curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + var curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")).getCas(); + curatorCas.setDocumentText(casByUser.values().stream().findFirst().get().getDocumentText()); - SpanDiffAdapter adapter = new SpanDiffAdapter(HOST_TYPE); + var adapter = new SpanDiffAdapter(HOST_TYPE); adapter.addLinkFeature("links", "role", "target"); - DiffResult result = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(asList(adapter), LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas, getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas, casByUser); assertThat(select(curatorCas, getType(curatorCas, HOST_TYPE))).isEmpty(); + assertThat(calculateState(result)).isEqualTo(AGREE); } @Test public void multiLinkMultiSpanRoleDiffTest() throws Exception { - JCas jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - Type type = jcasA.getTypeSystem().getType(HOST_TYPE); - Feature feature = type.getFeatureByBaseName("f1"); + var jcasA = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var type = jcasA.getTypeSystem().getType(HOST_TYPE); + var feature = type.getFeatureByBaseName("f1"); makeLinkHostFS(jcasA, 0, 0, feature, "A", makeLinkFS(jcasA, "slot1", 0, 0)); - JCas jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + var jcasB = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); makeLinkHostFS(jcasB, 0, 0, feature, "A", makeLinkFS(jcasB, "slot2", 0, 0)); - Map> casByUser = new LinkedHashMap<>(); - casByUser.put("user1", asList(jcasA.getCas())); - casByUser.put("user2", asList(jcasB.getCas())); + var casByUser = new LinkedHashMap(); + casByUser.put("user1", jcasA.getCas()); + casByUser.put("user2", jcasB.getCas()); - JCas curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); - curatorCas.setDocumentText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + var curatorCas = createJCas(createMultiLinkWithRoleTestTypeSystem("f1")); + curatorCas.setDocumentText(casByUser.values().stream().findFirst().get().getDocumentText()); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); // result.print(System.out); - sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, DUMMY_USER, curatorCas.getCas(), casByUser); Type hostType = curatorCas.getTypeSystem().getType(HOST_TYPE); assertThat(select(curatorCas.getCas(), hostType)).hasSize(1); - } - - private Map getSingleCasByUser(Map> aCasByUserSingle) - { - Map casByUserSingle = new HashMap<>(); - for (String user : aCasByUserSingle.keySet()) { - casByUserSingle.put(user, aCasByUserSingle.get(user).get(0)); - } - - return casByUserSingle; + assertThat(calculateState(result)).isEqualTo(INCOMPLETE); } private AnnotationFS createTokenAndOptionalPos(CAS aCas, int aBegin, int aEnd, String aPos) diff --git a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSpanTest.java b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSpanTest.java index 58ebe3b3142..b1538253269 100644 --- a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSpanTest.java +++ b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSpanTest.java @@ -52,10 +52,10 @@ public class CasMergeSpanTest @Test public void simpleCopyToEmptyTest() throws Exception { - CAS sourceCas = createCas(); - AnnotationFS clickedFs = createNEAnno(sourceCas, "NN", 0, 0); + var sourceCas = createCas(); + var clickedFs = createNEAnno(sourceCas, "NN", 0, 0); - CAS targetCas = createCas(); + var targetCas = createCas(); createToken(targetCas, 0, 0); sut.mergeSpanAnnotation(document, DUMMY_USER, neLayer, targetCas, clickedFs, false); diff --git a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSuiteTest.java b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSuiteTest.java index 1cc58bcc838..6de0d0a9810 100644 --- a/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSuiteTest.java +++ b/inception/inception-curation/src/test/java/de/tudarmstadt/ukp/inception/curation/merge/CasMergeSuiteTest.java @@ -29,10 +29,7 @@ import java.io.File; import java.io.FilenameFilter; -import java.util.Collection; import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.RegexFileFilter; @@ -41,7 +38,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; import de.tudarmstadt.ukp.clarin.webanno.tsv.WebannoTsv3XWriter; import de.tudarmstadt.ukp.dkpro.core.api.metadata.type.DocumentMetaData; @@ -59,55 +55,44 @@ public static Iterable tsvFiles() @MethodSource("tsvFiles") public void runTest(File aReferenceFolder) throws Exception { - Map> casByUser = new HashMap<>(); + var casByUser = new HashMap(); - File[] inputFiles = aReferenceFolder + var inputFiles = aReferenceFolder .listFiles((FilenameFilter) new RegexFileFilter("user.*\\.tsv")); - for (File inputFile : inputFiles) { - casByUser.put(inputFile.getName(), List.of(loadWebAnnoTsv3(inputFile).getCas())); + for (var inputFile : inputFiles) { + casByUser.put(inputFile.getName(), loadWebAnnoTsv3(inputFile).getCas()); } - CAS curatorCas = createText(casByUser.values().stream().flatMap(Collection::stream) - .findFirst().get().getDocumentText()); + var curatorCas = createText( + casByUser.values().stream().findFirst().get().getDocumentText()); - DiffResult result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); + var result = doDiff(diffAdapters, LINK_TARGET_AS_LABEL, casByUser).toResult(); - sut.reMergeCas(result, document, "dummyTargetUser", curatorCas, - getSingleCasByUser(casByUser)); + sut.reMergeCas(result, document, "dummyTargetUser", curatorCas, casByUser); writeAndAssertEquals(curatorCas, aReferenceFolder); } - private Map getSingleCasByUser(Map> aCasByUserSingle) - { - Map casByUserSingle = new HashMap<>(); - for (String user : aCasByUserSingle.keySet()) { - casByUserSingle.put(user, aCasByUserSingle.get(user).get(0)); - } - - return casByUserSingle; - } - private void writeAndAssertEquals(CAS curatorCas, File aReferenceFolder) throws Exception { - String targetFolder = "target/test-output/" + getClass().getSimpleName() + "/" + var targetFolder = "target/test-output/" + getClass().getSimpleName() + "/" + aReferenceFolder.getName(); - DocumentMetaData dmd = DocumentMetaData.get(curatorCas); + var dmd = DocumentMetaData.get(curatorCas); dmd.setDocumentId("curator"); runPipeline(curatorCas, createEngineDescription(WebannoTsv3XWriter.class, WebannoTsv3XWriter.PARAM_TARGET_LOCATION, targetFolder, WebannoTsv3XWriter.PARAM_OVERWRITE, true)); - File referenceFile = new File(aReferenceFolder, "curator.tsv"); + var referenceFile = new File(aReferenceFolder, "curator.tsv"); assumeTrue(referenceFile.exists(), "No reference data available for this test."); - File actualFile = new File(targetFolder, "curator.tsv"); + var actualFile = new File(targetFolder, "curator.tsv"); - String reference = FileUtils.readFileToString(referenceFile, "UTF-8"); - String actual = FileUtils.readFileToString(actualFile, "UTF-8"); + var reference = FileUtils.readFileToString(referenceFile, "UTF-8"); + var actual = FileUtils.readFileToString(actualFile, "UTF-8"); assertThat(actual).isEqualToNormalizingNewlines(reference); } diff --git a/inception/inception-dependencies/pom.xml b/inception/inception-dependencies/pom.xml index 20c2858a7e3..bb8a3bde2c4 100644 --- a/inception/inception-dependencies/pom.xml +++ b/inception/inception-dependencies/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception - 31.0-SNAPSHOT + 32.0-SNAPSHOT ../.. @@ -155,6 +155,13 @@ pom import + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + org.assertj assertj-core @@ -442,7 +449,7 @@ com.hubspot.jinjava jinjava - 2.7.1 + 2.7.2 @@ -645,7 +652,7 @@ it.unimi.dsi fastutil - 8.5.12 + 8.5.13 @@ -697,12 +704,12 @@ net.bytebuddy byte-buddy - 1.14.11 + 1.14.12 net.bytebuddy byte-buddy-agent - 1.14.11 + 1.14.12 org.wicketstuff @@ -892,6 +899,19 @@ 1.16.1 + + + + org.apache.james + apache-mime4j-core + 0.8.9 + + + org.apache.james + apache-mime4j-dom + 0.8.9 + + @@ -1007,12 +1027,12 @@ org.apache.commons commons-compress - 1.25.0 + 1.26.0 commons-codec commons-codec - 1.16.0 + 1.16.1 commons-validator @@ -1087,6 +1107,48 @@ + + org.cyberborean + rdfbeans + 2.2 + + + + net.sourceforge.owlapi + owlapi-api + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-apibinding + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-impl + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-oboformat + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-parsers + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-rio + ${owlapi-version} + + + net.sourceforge.owlapi + owlapi-tools + ${owlapi-version} + + com.github.jsonld-java jsonld-java diff --git a/inception/inception-diag/pom.xml b/inception/inception-diag/pom.xml index a446b4ae7d9..4d984e130e8 100644 --- a/inception/inception-diag/pom.xml +++ b/inception/inception-diag/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-diag INCEpTION - Core - Diagnostics diff --git a/inception/inception-diam-api/pom.xml b/inception/inception-diam-api/pom.xml index a9d703334b1..ea780d0a915 100644 --- a/inception/inception-diam-api/pom.xml +++ b/inception/inception-diam-api/pom.xml @@ -20,7 +20,7 @@ inception-app de.tudarmstadt.ukp.inception.app - 31.0-SNAPSHOT + 32.0-SNAPSHOT 4.0.0 inception-api-diam diff --git a/inception/inception-diam-compactv2/pom.xml b/inception/inception-diam-compactv2/pom.xml index 2636b965093..199a1a24ce0 100644 --- a/inception/inception-diam-compactv2/pom.xml +++ b/inception/inception-diam-compactv2/pom.xml @@ -20,7 +20,7 @@ inception-app de.tudarmstadt.ukp.inception.app - 31.0-SNAPSHOT + 32.0-SNAPSHOT 4.0.0 inception-diam-compactv2 diff --git a/inception/inception-diam-editor/pom.xml b/inception/inception-diam-editor/pom.xml index 918c26a1fd5..16b37c60e37 100644 --- a/inception/inception-diam-editor/pom.xml +++ b/inception/inception-diam-editor/pom.xml @@ -21,7 +21,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-diam-editor INCEpTION - DIAM - Editor diff --git a/inception/inception-diam-editor/src/main/java/de/tudarmstadt/ukp/inception/diam/sidebar/DiamSidebarFactoryUserPreferences.schema.json b/inception/inception-diam-editor/src/main/java/de/tudarmstadt/ukp/inception/diam/sidebar/DiamSidebarFactoryUserPreferences.schema.json index 712bbd83d9b..0dde7a48c2e 100644 --- a/inception/inception-diam-editor/src/main/java/de/tudarmstadt/ukp/inception/diam/sidebar/DiamSidebarFactoryUserPreferences.schema.json +++ b/inception/inception-diam-editor/src/main/java/de/tudarmstadt/ukp/inception/diam/sidebar/DiamSidebarFactoryUserPreferences.schema.json @@ -4,7 +4,7 @@ "properties": { "mode": { "type": "string", - "enum": ["by-label", "by-position"] + "enum": ["by-label", "by-position", "by-layer"] }, "sortByScore": { "type": "boolean" diff --git a/inception/inception-diam-editor/src/main/ts/src/AnnotationsByLayerList.svelte b/inception/inception-diam-editor/src/main/ts/src/AnnotationsByLayerList.svelte new file mode 100644 index 00000000000..d908231e2f9 --- /dev/null +++ b/inception/inception-diam-editor/src/main/ts/src/AnnotationsByLayerList.svelte @@ -0,0 +1,221 @@ + + +{#if !data} +
+
+
+ Loading... +
+
+
+{:else} +
+
+ + +
+
+ + +
+
+
+ {#if sortedLayers || sortedLayers?.length} +
    + {#each sortedLayers as layer} +
  • +
    + {layer.name} +
    +
      + {#if groupedAnnotations[layer.name]} + {#each groupedAnnotations[layer.name] as ann} + +
    • mouseOverAnnotation(ev, ann)} + on:mouseout={ev => mouseOutAnnotation(ev, ann)} + > +
      + {#if ann instanceof Span} +
      + {:else if ann instanceof Relation} +
      + {/if} +
      + +
      scrollTo(ann)} + > +
      + +
      + + {#if ann instanceof Span} + + {:else if ann instanceof Relation} + + {/if} +
      +
    • + {/each} + {:else} +
    • + No occurrences +
    • + {/if} +
    +
  • + {/each} +
+ {/if} +
+{/if} + + diff --git a/inception/inception-diam-editor/src/main/ts/src/DiamAnnotationBrowser.svelte b/inception/inception-diam-editor/src/main/ts/src/DiamAnnotationBrowser.svelte index 81dc231898c..679af6a063d 100644 --- a/inception/inception-diam-editor/src/main/ts/src/DiamAnnotationBrowser.svelte +++ b/inception/inception-diam-editor/src/main/ts/src/DiamAnnotationBrowser.svelte @@ -31,6 +31,7 @@ } from "./AnnotationBrowserState"; import AnnotationsByPositionList from "./AnnotationsByPositionList.svelte"; import AnnotationsByLabelList from "./AnnotationsByLabelList.svelte"; + import AnnotationsByLayerList from "./AnnotationsByLayerList.svelte"; import AnnotationDetailPopOver from "@inception-project/inception-js-api/src/widget/AnnotationDetailPopOver.svelte" export let wsEndpointUrl: string; @@ -52,6 +53,7 @@ let modes = { "by-position": "Group by position", "by-label": "Group by label", + "by-layer": "Group by layer", }; let tooManyAnnotations = false; @@ -154,6 +156,8 @@ {:else if $groupingMode == "by-position"} + {:else if $groupingMode == "by-layer"} + {:else} {/if} diff --git a/inception/inception-diam-editor/src/main/ts/src/LabelBadge.svelte b/inception/inception-diam-editor/src/main/ts/src/LabelBadge.svelte index 12d8158f929..d2f66dfe239 100644 --- a/inception/inception-diam-editor/src/main/ts/src/LabelBadge.svelte +++ b/inception/inception-diam-editor/src/main/ts/src/LabelBadge.svelte @@ -26,6 +26,8 @@ $: backgroundColor = annotation.color || "var(--bs-secondary)"; $: textColor = bgToFgColor(backgroundColor); + $: hasError = annotation.comments?.find(comment => comment.type === 'error') + $: hasInfo = annotation.comments?.find(comment => comment.type === 'info') function handleSelect(ev: MouseEvent) { ajaxClient.selectAnnotation(annotation.vid, { scrollTo: true }); @@ -97,7 +99,13 @@ {/if} {:else} -
+
+ {#if hasError || hasInfo} + + {#if hasError}{/if} + {#if hasInfo}{/if} + + {/if}
{#if annotation} -
+
{#if annotation.comments} - {#each annotation.comments as comment} -
{comment.comment}
- {/each} +
+ {#each annotation.comments as comment} +
{comment.comment}
+ {/each} +
{/if} + {#if loading} -
+
Loading... @@ -202,17 +205,21 @@
{:else if detailGroups} - {#each detailGroups as detailGroup} - {#if detailGroup.title} -
{detailGroup.title}
- {/if} - {#each detailGroup.details as detail} -
- {detail.label}: - {detail.value} -
+
    + {#each detailGroups as detailGroup} +
  • + {#if detailGroup.title} +
    {detailGroup.title}
    + {/if} + {#each detailGroup.details as detail} +
    + {detail.label}: + {detail.value} +
    + {/each} +
  • {/each} - {/each} +
{/if}
{/if} diff --git a/inception/inception-js-api/src/main/ts_template/package-lock.json b/inception/inception-js-api/src/main/ts_template/package-lock.json index 8e31baa1119..c354c168583 100644 --- a/inception/inception-js-api/src/main/ts_template/package-lock.json +++ b/inception/inception-js-api/src/main/ts_template/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@stomp/stompjs": "^6.1.2", "@types/stompjs": "^2.3.5", - "bootstrap": "5.3.2" + "bootstrap": "5.3.3" }, "devDependencies": { "@types/chai": "^4.3.1", @@ -50,10 +50,26 @@ "node": ">=0.10.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", - "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -67,9 +83,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", - "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -83,9 +99,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", - "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -99,9 +115,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", - "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -115,9 +131,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", - "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -131,9 +147,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", - "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -147,9 +163,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", - "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -163,9 +179,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", - "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -179,9 +195,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", - "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -195,9 +211,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", - "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -211,9 +227,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", - "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -227,9 +243,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", - "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -243,9 +259,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", - "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -259,9 +275,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", - "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -275,9 +291,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", - "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -291,9 +307,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", - "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -307,9 +323,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", - "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -323,9 +339,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", - "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -339,9 +355,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", - "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -355,9 +371,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", - "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -371,9 +387,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", - "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -387,9 +403,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", - "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -449,29 +465,73 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -486,15 +546,15 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -507,9 +567,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -591,9 +651,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.2.tgz", - "integrity": "sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==", + "version": "20.11.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", + "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", "dependencies": { "undici-types": "~5.26.4" } @@ -605,9 +665,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/stompjs": { @@ -619,16 +679,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -654,15 +714,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -682,13 +742,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -699,13 +759,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -726,9 +786,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -739,16 +799,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -766,17 +827,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -791,12 +852,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -814,9 +875,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -903,13 +964,16 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -943,17 +1007,36 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -999,17 +1082,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1038,10 +1122,13 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -1065,9 +1152,9 @@ } }, "node_modules/bootstrap": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "funding": [ { "type": "github", @@ -1083,13 +1170,12 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1135,14 +1221,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1170,9 +1261,9 @@ } }, "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -1372,17 +1463,20 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -1451,50 +1545,52 @@ "dev": true }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1503,15 +1599,42 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1550,9 +1673,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", - "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1562,28 +1685,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-runner-plugins": { @@ -1639,9 +1763,9 @@ } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz", - "integrity": "sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.1.tgz", + "integrity": "sha512-mBB2aEF0xk7yo+Q9pSUh8xYED/1O2wbAM6IauGkDrqy6pl9SbJNakLeLGXiNpNujWIudu8TJTZCv2L5AQYRXtA==", "dev": true, "dependencies": { "resolve": "^1.22.6", @@ -1668,9 +1792,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -1689,15 +1813,15 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1819,9 +1943,9 @@ } }, "node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.2.tgz", - "integrity": "sha512-LOIfGx5sZZ5FwM1shr2GlYAWV9Omdi+1/3byuVagvQNoGUuU0iHhp7AfjA1uR+4dJ4Isfb4+FwBJgQajIw9iAg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.4.tgz", + "integrity": "sha512-PGPjJ8diYgX1mjLxGJqRop2rrGwZRKImoEOwUOgoIhg0p80MkTaqvmFLe5TF7/iagZHggasvIfQlUyHIhK/PYg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -1874,9 +1998,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -1895,7 +2019,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -1904,6 +2028,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -1925,6 +2059,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1935,9 +2081,9 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", - "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz", + "integrity": "sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", @@ -1975,6 +2121,28 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -2042,6 +2210,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2148,9 +2338,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2220,9 +2410,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -2323,28 +2513,33 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -2354,20 +2549,19 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2385,10 +2579,22 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2478,21 +2684,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -2514,12 +2720,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -2529,9 +2735,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -2550,18 +2756,18 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -2606,12 +2812,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -2620,14 +2826,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2749,9 +2957,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -2819,12 +3027,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2873,12 +3084,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3058,12 +3269,12 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -3142,15 +3353,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -3178,9 +3392,9 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -3190,13 +3404,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -3211,10 +3424,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha-junit-reporter": { @@ -3233,15 +3442,6 @@ "mocha": ">=2.2.5" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -3310,18 +3510,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3391,15 +3579,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -3550,6 +3739,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3616,14 +3814,15 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -3704,6 +3903,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3728,13 +3969,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -3766,15 +4007,18 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3791,6 +4035,48 @@ "rimraf": "^2.5.2" } }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/sander/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -3816,9 +4102,9 @@ } }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", + "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -3833,9 +4119,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3857,29 +4143,32 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3907,14 +4196,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4110,27 +4403,28 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.1.tgz", - "integrity": "sha512-p/Dp4hmrBW5mrCCq29lEMFpIJT2FZsRlouxEc5qpbOmXRbaFs7clLs8oKPwD3xCFyZfv1bIhvOzpQkhMEVQdMw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz", + "integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -4217,12 +4511,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -4293,9 +4587,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -4344,29 +4638,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -4376,16 +4671,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -4395,23 +4691,29 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4500,16 +4802,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" diff --git a/inception/inception-kb-fact-linking/pom.xml b/inception/inception-kb-fact-linking/pom.xml index 66ffc5a7b74..ee73f94b9a9 100644 --- a/inception/inception-kb-fact-linking/pom.xml +++ b/inception/inception-kb-fact-linking/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-kb-fact-linking INCEpTION - Knowledge Base - Fact Linking (deprecated) diff --git a/inception/inception-kb/pom.xml b/inception/inception-kb/pom.xml index 1759ebed6d7..cc7e561481b 100644 --- a/inception/inception-kb/pom.xml +++ b/inception/inception-kb/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-kb INCEpTION - Knowledge Base - Core @@ -163,7 +163,34 @@ org.cyberborean rdfbeans - 2.2 + + + net.sourceforge.owlapi + owlapi-api + + + net.sourceforge.owlapi + owlapi-apibinding + + + net.sourceforge.owlapi + owlapi-impl + + + net.sourceforge.owlapi + owlapi-parsers + + + net.sourceforge.owlapi + owlapi-rio + + + net.sourceforge.owlapi + owlapi-tools + + + net.sourceforge.owlapi + owlapi-oboformat @@ -314,6 +341,16 @@ spring-core test + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + @@ -323,6 +360,11 @@ maven-dependency-plugin + + net.sourceforge.owlapi:owlapi-impl + net.sourceforge.owlapi:owlapi-parsers + net.sourceforge.owlapi:owlapi-tools + net.sourceforge.owlapi:owlapi-oboformat org.eclipse.rdf4j:rdf4j-rio-rdfxml org.eclipse.rdf4j:rdf4j-rio-ntriples diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/IriConstants.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/IriConstants.java index 6d3822aa1c3..18848d6f48f 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/IriConstants.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/IriConstants.java @@ -47,6 +47,7 @@ public class IriConstants public static final String PREFIX_LUCENE_SEARCH = "http://www.openrdf.org/contrib/lucenesail#"; public static final String PREFIX_MWAPI = "https://www.mediawiki.org/ontology#API/"; public static final String PREFIX_STARDOG = "tag:stardog:api:search:"; + public static final String PREFIX_BLAZEGRAPH = "http://www.bigdata.com/rdf/search#"; public static final String UKP_WIKIDATA_SPARQL_ENDPOINT = "http://knowledgebase.ukp.informatik.tu-darmstadt.de:8890/sparql"; public static final Set IMPLICIT_NAMESPACES = Set.of(RDF.NAMESPACE, RDFS.NAMESPACE, @@ -87,6 +88,7 @@ public class IriConstants public static final IRI FTS_VIRTUOSO; public static final IRI FTS_WIKIDATA; public static final IRI FTS_STARDOG; + public static final IRI FTS_BLAZEGRAPH; public static final IRI FTS_NONE; public static final List CLASS_IRIS; @@ -98,6 +100,7 @@ public class IriConstants public static final List PROPERTY_TYPE_IRIS; public static final List PROPERTY_LABEL_IRIS; public static final List PROPERTY_DESCRIPTION_IRIS; + public static final List DEPRECATION_PROPERTY_IRIS; public static final List FTS_IRIS; static { @@ -115,6 +118,7 @@ public class IriConstants FTS_LUCENE = vf.createIRI(PREFIX_LUCENE_SEARCH, "matches"); FTS_WIKIDATA = vf.createIRI(PREFIX_MWAPI, "search"); FTS_STARDOG = vf.createIRI(PREFIX_STARDOG, "textMatch"); + FTS_BLAZEGRAPH = vf.createIRI(PREFIX_BLAZEGRAPH, "search"); FTS_NONE = vf.createIRI("FTS:NONE"); CLASS_IRIS = asList(RDFS.CLASS, OWL.CLASS, WIKIDATA_CLASS, SKOS.CONCEPT); @@ -126,7 +130,9 @@ public class IriConstants PROPERTY_TYPE_IRIS = asList(RDF.PROPERTY, WIKIDATA_PROPERTY_TYPE); PROPERTY_LABEL_IRIS = asList(RDFS.LABEL, SKOS.PREF_LABEL); PROPERTY_DESCRIPTION_IRIS = asList(RDFS.COMMENT, SCHEMA_DESCRIPTION); - FTS_IRIS = asList(FTS_FUSEKI, FTS_VIRTUOSO, FTS_WIKIDATA, FTS_LUCENE, FTS_STARDOG); + DEPRECATION_PROPERTY_IRIS = asList(OWL.DEPRECATED); + FTS_IRIS = asList(FTS_FUSEKI, FTS_BLAZEGRAPH, FTS_VIRTUOSO, FTS_WIKIDATA, FTS_LUCENE, + FTS_STARDOG); } public static boolean hasImplicitNamespace(KnowledgeBase kb, String s) diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseService.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseService.java index ffa8099618d..fd6452c2682 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseService.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseService.java @@ -149,6 +149,10 @@ RepositoryImplConfig getKnowledgeBaseConfig(KnowledgeBase kb) /** * Creates a new concept in the given knowledge base. Does nothing if the knowledge base is read * only. + *

+ * Note: This method binds the concept to the knowledge base by generating an new + * {@link KBConcept#setIdentifier(String) identifier} and setting the + * {@link KBConcept#setKB(KnowledgeBase) knowledge base} property. * * @param kb * The knowledge base to which the new concept will be added @@ -195,6 +199,10 @@ Optional readConcept(Project aProject, String aIdentifier) /** * Creates a new property in the given knowledge base. Does nothing if the knowledge base is * read only. + *

+ * Note: This method binds the concept to the knowledge base by generating an new + * {@link KBConcept#setIdentifier(String) identifier} and setting the + * {@link KBConcept#setKB(KnowledgeBase) knowledge base} property. * * @param kb * The knowledge base to which the new property will be added @@ -255,6 +263,10 @@ Optional readProperty(KnowledgeBase kb, String aIdentifier) /** * Creates a new instance in the given knowledge base. Does nothing if the knowledge base is * read only. + *

+ * Note: This method binds the concept to the knowledge base by generating an new + * {@link KBConcept#setIdentifier(String) identifier} and setting the + * {@link KBConcept#setKB(KnowledgeBase) knowledge base} property. * * @param kb * The knowledge base to which the new instance will be added diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java index f6e598c5fd4..606c28486d7 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImpl.java @@ -46,6 +46,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -62,7 +63,6 @@ import org.apache.lucene.index.IndexFormatTooNewException; import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.Statement; -import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.model.vocabulary.XSD; import org.eclipse.rdf4j.query.QueryEvaluationException; @@ -95,6 +95,11 @@ import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries; import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns; +import org.semanticweb.owlapi.apibinding.OWLManager; +import org.semanticweb.owlapi.formats.FunctionalSyntaxDocumentFormat; +import org.semanticweb.owlapi.model.OWLOntologyCreationException; +import org.semanticweb.owlapi.model.OWLOntologyStorageException; +import org.semanticweb.owlapi.rio.OWLAPIRDFFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; @@ -137,6 +142,7 @@ import de.tudarmstadt.ukp.inception.support.SettingsUtil; import de.tudarmstadt.ukp.inception.support.StopWatch; import de.tudarmstadt.ukp.inception.support.json.JSONUtil; +import de.tudarmstadt.ukp.inception.support.wicket.PipedStreamResource; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; @@ -338,7 +344,7 @@ public void defineBaseProperties(KnowledgeBase aKB) var readOnly = aKB.isReadOnly(); aKB.setReadOnly(false); try { - ValueFactory vf = SimpleValueFactory.getInstance(); + var vf = SimpleValueFactory.getInstance(); createBaseProperty(aKB, new KBProperty(aKB.getSubclassIri(), vf.createIRI(aKB.getSubclassIri()).getLocalName())); @@ -630,20 +636,66 @@ public void importData(KnowledgeBase kb, String aFilename, InputStream aIS) LOG.debug("Stream is not compressed, continue as is."); } - // Detect the file format - var format = Rio.getParserFormatForFileName(aFilename).orElse(RDFXML); + PipedStreamResource resource = null; + try { + // Detect the file format + var format = Rio.getParserFormatForFileName(aFilename).orElse(RDFXML); - // Load files into the repository - try (var conn = getConnection(kb)) { - conn.setIsolationLevel(IsolationLevels.NONE); - // If the RDF file contains relative URLs, then they probably start with a hash. - // To avoid having two hashes here, we drop the hash from the base prefix configured - // by the user. - String prefix = StringUtils.removeEnd(kb.getBasePrefix(), "#"); - conn.add(is, prefix, format); + String lowerCaseFilename = aFilename.toLowerCase(Locale.ROOT); + if (lowerCaseFilename.endsWith(".obo") || lowerCaseFilename.endsWith(".obo.gz")) { + try { + resource = transduceOboToOwlFunctionalSyntax(is); + is = resource.getInputStream(); + format = OWLAPIRDFFormat.OWL_FUNCTIONAL; + } + catch (Exception e) { + throw new IOException(e); + } + } + + // Load files into the repository + try (var conn = getConnection(kb)) { + conn.setIsolationLevel(IsolationLevels.NONE); + // If the RDF file contains relative URLs, then they probably start with a hash. + // To avoid having two hashes here, we drop the hash from the base prefix configured + // by the user. + String prefix = StringUtils.removeEnd(kb.getBasePrefix(), "#"); + conn.add(is, prefix, format); + } + } + finally { + if (resource != null) { + resource.close(); + } } } + private PipedStreamResource transduceOboToOwlFunctionalSyntax(InputStream aIs) + throws OWLOntologyCreationException + { + var manager = OWLManager.createOWLOntologyManager(); + + // // Does not seem to work for imports in OBO files.... + // var iriMappers = manager.getIRIMappers(); + // iriMappers.add( + // new AutoIRIMapper(new File(kbRepositoriesRoot, "materializedOntologies"), true)); + // manager.getOntologyLoaderConfiguration() + // .setMissingImportHandlingStrategy(MissingImportHandlingStrategy.SILENT); + // manager.getOntologyConfigurator() + // .setMissingImportHandlingStrategy(MissingImportHandlingStrategy.SILENT); + + var ontology = manager.loadOntologyFromOntologyDocument(aIs); + + return new PipedStreamResource(os -> { + try { + manager.saveOntology(ontology, new FunctionalSyntaxDocumentFormat(), os); + } + catch (OWLOntologyStorageException e) { + LOG.error("Unable to stream OBO file to OWL Functional Syntax", e); + } + }); + } + @Override public void exportData(KnowledgeBase kb, RDFFormat format, OutputStream os) { @@ -666,24 +718,25 @@ public void clear(KnowledgeBase kb) } @Override - public boolean isEmpty(KnowledgeBase kb) + public boolean isEmpty(KnowledgeBase aKB) { - try (var conn = getConnection(kb)) { + try (var conn = getConnection(aKB)) { return conn.isEmpty(); } } @Override - public void createConcept(KnowledgeBase kb, KBConcept aConcept) + public void createConcept(KnowledgeBase aKB, KBConcept aConcept) { if (isNotEmpty(aConcept.getIdentifier())) { throw new IllegalArgumentException("Identifier must be empty on create"); } - update(kb, (conn) -> { - String identifier = getReificationStrategy(kb).generateConceptIdentifier(conn, kb); + update(aKB, (conn) -> { + var identifier = getReificationStrategy(aKB).generateConceptIdentifier(conn, aKB); aConcept.setIdentifier(identifier); - aConcept.write(conn, kb); + aConcept.setKB(aKB); + aConcept.write(conn, aKB); }); } @@ -696,7 +749,8 @@ public Optional readConcept(KnowledgeBase aKB, String aIdentifier, bo .withIdentifier(aIdentifier) // .excludeInferred() // .retrieveLabel() // - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); Optional result; if (aKB.isReadOnly()) { @@ -750,6 +804,7 @@ public List listAllConcepts(KnowledgeBase aKB, boolean aAll) var query = SPARQLQueryBuilder.forClasses(aKB) // .retrieveLabel() // .retrieveDescription() // + .retrieveDeprecation() // .excludeInferred(); List result; @@ -765,16 +820,17 @@ public List listAllConcepts(KnowledgeBase aKB, boolean aAll) } @Override - public void createProperty(KnowledgeBase kb, KBProperty aProperty) + public void createProperty(KnowledgeBase aKB, KBProperty aProperty) { if (isNotEmpty(aProperty.getIdentifier())) { throw new IllegalArgumentException("Identifier must be empty on create"); } - update(kb, (conn) -> { - String identifier = getReificationStrategy(kb).generatePropertyIdentifier(conn, kb); + update(aKB, (conn) -> { + String identifier = getReificationStrategy(aKB).generatePropertyIdentifier(conn, aKB); aProperty.setIdentifier(identifier); - aProperty.write(conn, kb); + aProperty.setKB(aKB); + aProperty.write(conn, aKB); }); } @@ -786,7 +842,7 @@ public Optional readProperty(KnowledgeBase aKB, String aIdentifier) .withIdentifier(aIdentifier) // .retrieveDescription() // .retrieveLabel() // - .retrieveDomainAndRange() // + .retrieveDeprecation().retrieveDomainAndRange() // .excludeInferred(); Optional result; @@ -835,6 +891,7 @@ public List listProperties(KnowledgeBase aKB, boolean aIncludeInferred var query = SPARQLQueryBuilder.forProperties(aKB) // .retrieveLabel() // .retrieveDescription() // + .retrieveDeprecation() // .retrieveDomainAndRange() // .includeInferred(aIncludeInferred); @@ -851,16 +908,17 @@ public List listProperties(KnowledgeBase aKB, boolean aIncludeInferred } @Override - public void createInstance(KnowledgeBase kb, KBInstance aInstance) + public void createInstance(KnowledgeBase aKB, KBInstance aInstance) { if (isNotEmpty(aInstance.getIdentifier())) { throw new IllegalArgumentException("Identifier must be empty on create"); } - update(kb, (conn) -> { - String identifier = getReificationStrategy(kb).generateInstanceIdentifier(conn, kb); + update(aKB, (conn) -> { + String identifier = getReificationStrategy(aKB).generateInstanceIdentifier(conn, aKB); aInstance.setIdentifier(identifier); - aInstance.write(conn, kb); + aInstance.setKB(aKB); + aInstance.write(conn, aKB); }); } @@ -873,7 +931,7 @@ public Optional readInstance(KnowledgeBase aKB, String aIdentifier) .withIdentifier(aIdentifier) // .retrieveDescription() // .retrieveLabel() // - .excludeInferred(); + .retrieveDeprecation().excludeInferred(); Optional result; if (aKB.isReadOnly()) { @@ -926,7 +984,8 @@ public List listInstances(KnowledgeBase aKB, String aConceptIri, boole var query = SPARQLQueryBuilder.forInstances(aKB) // .childrenOf(aConceptIri) // .retrieveLabel() // - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); List result; if (aKB.isReadOnly()) { @@ -1034,6 +1093,7 @@ public List listDomainProperties(KnowledgeBase aKB, String aDomain, .matchingDomain(aDomain) // .retrieveLabel() // .retrieveDescription() // + .retrieveDeprecation() // .retrieveDomainAndRange() // .includeInferred(aIncludeInferred); @@ -1056,7 +1116,8 @@ public List listRootConcepts(KnowledgeBase aKB, boolean aAll) { try (var watch = new StopWatch(LOG, "listRootConcepts()")) { var query = SPARQLQueryBuilder.forClasses(aKB).roots().retrieveLabel() - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); List result; if (aKB.isReadOnly()) { @@ -1095,7 +1156,8 @@ public List getConceptForInstance(KnowledgeBase aKB, String aIdentifie var query = SPARQLQueryBuilder.forClasses(aKB) // .parentsOf(aIdentifier) // .retrieveLabel() // - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); List result; if (aKB.isReadOnly()) { @@ -1117,7 +1179,8 @@ public List getParentConceptList(KnowledgeBase aKB, String aIdentifier var query = SPARQLQueryBuilder.forClasses(aKB) // .ancestorsOf(aIdentifier) // .retrieveLabel() // - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); List result; if (aKB.isReadOnly()) { @@ -1141,6 +1204,7 @@ public List listChildConcepts(KnowledgeBase aKB, String aParentIdentif .childrenOf(aParentIdentifier) // .retrieveLabel() // .retrieveDescription() // + .retrieveDeprecation() // .limit(aLimit); List result; @@ -1264,7 +1328,8 @@ public Optional readHandle(KnowledgeBase aKB, String aIdentifier) var query = SPARQLQueryBuilder.forItems(aKB) // .withIdentifier(aIdentifier) // .retrieveLabel() // - .retrieveDescription(); + .retrieveDescription() // + .retrieveDeprecation(); Optional result; if (aKB.isReadOnly()) { diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/SchemaProfile.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/SchemaProfile.java index 5d2ee9b1269..a05581e95bf 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/SchemaProfile.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/SchemaProfile.java @@ -26,26 +26,25 @@ import org.eclipse.rdf4j.model.vocabulary.SKOS; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; -import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseMapping; import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseProfile; public enum SchemaProfile { RDFSCHEMA("RDF", RDFS.CLASS, RDFS.SUBCLASSOF, RDF.TYPE, RDFS.SUBPROPERTYOF, RDFS.COMMENT, - RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT), + RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT, OWL.DEPRECATED), WIKIDATASCHEMA("WIKIDATA", IriConstants.WIKIDATA_CLASS, IriConstants.WIKIDATA_SUBCLASS, IriConstants.WIKIDATA_TYPE, RDFS.SUBPROPERTYOF, RDFS.COMMENT, RDFS.LABEL, - IriConstants.WIKIDATA_PROPERTY_TYPE, RDFS.LABEL, RDFS.COMMENT), + IriConstants.WIKIDATA_PROPERTY_TYPE, RDFS.LABEL, RDFS.COMMENT, OWL.DEPRECATED), OWLSCHEMA("OWL", OWL.CLASS, RDFS.SUBCLASSOF, RDF.TYPE, RDFS.SUBPROPERTYOF, RDFS.COMMENT, - RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT), + RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT, OWL.DEPRECATED), SKOSSCHEMA("SKOS", SKOS.CONCEPT, SKOS.BROADER, RDF.TYPE, RDFS.SUBPROPERTYOF, RDFS.COMMENT, - SKOS.PREF_LABEL, RDF.PROPERTY, SKOS.PREF_LABEL, RDFS.COMMENT), + SKOS.PREF_LABEL, RDF.PROPERTY, SKOS.PREF_LABEL, RDFS.COMMENT, OWL.DEPRECATED), CUSTOMSCHEMA("CUSTOM", RDFS.CLASS, RDFS.SUBCLASSOF, RDF.TYPE, RDFS.SUBPROPERTYOF, RDFS.COMMENT, - RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT); + RDFS.LABEL, RDF.PROPERTY, RDFS.LABEL, RDFS.COMMENT, OWL.DEPRECATED); private final String uiLabel; private final String classIri; @@ -57,10 +56,11 @@ public enum SchemaProfile private final String propertyTypeIri; private final String propertyLabelIri; private final String propertyDescriptionIri; + private final String deprecationPropertyIri; private SchemaProfile(String aUiLabel, IRI aClassIri, IRI aSubclassIri, IRI aTypeIri, IRI aSubPropertyIri, IRI aDescriptionIri, IRI aLabelIri, IRI aPropertyTypeIri, - IRI aPropertyLabelIri, IRI aPropertyDescriptionIri) + IRI aPropertyLabelIri, IRI aPropertyDescriptionIri, IRI aDeprecationPropertyIri) { uiLabel = aUiLabel; classIri = aClassIri.stringValue(); @@ -72,11 +72,13 @@ private SchemaProfile(String aUiLabel, IRI aClassIri, IRI aSubclassIri, IRI aTyp propertyTypeIri = aPropertyTypeIri.stringValue(); propertyLabelIri = aPropertyLabelIri.stringValue(); propertyDescriptionIri = aPropertyDescriptionIri.stringValue(); + deprecationPropertyIri = aDeprecationPropertyIri.stringValue(); } private SchemaProfile(String aUiLabel, String aClassIri, String aSubclassIri, String aTypeIri, String aSubPropertyIri, String aDescriptionIri, String aLabelIri, - String aPropertyTypeIri, String aPropertyLabelIri, String aPropertyDescriptionIri) + String aPropertyTypeIri, String aPropertyLabelIri, String aPropertyDescriptionIri, + String aDeprecationPropertyIri) { uiLabel = aUiLabel; classIri = aClassIri; @@ -88,6 +90,7 @@ private SchemaProfile(String aUiLabel, String aClassIri, String aSubclassIri, St propertyTypeIri = aPropertyTypeIri; propertyLabelIri = aPropertyLabelIri; propertyDescriptionIri = aPropertyDescriptionIri; + deprecationPropertyIri = aDeprecationPropertyIri; } public String getUiLabel() @@ -140,6 +143,11 @@ public String getPropertyDescriptionIri() return propertyDescriptionIri; } + public String getDeprecationPropertyIri() + { + return deprecationPropertyIri; + } + /** * Check if the given profile equals one of the schema profiles defined in {@link SchemaProfile} * @@ -149,14 +157,15 @@ public String getPropertyDescriptionIri() @SuppressWarnings("javadoc") public static SchemaProfile checkSchemaProfile(KnowledgeBaseProfile aProfile) { - SchemaProfile[] profiles = SchemaProfile.values(); - KnowledgeBaseMapping mapping = aProfile.getMapping(); - for (int i = 0; i < profiles.length; i++) { + var profiles = SchemaProfile.values(); + var mapping = aProfile.getMapping(); + for (var i = 0; i < profiles.length; i++) { // Check if kb profile corresponds to a known schema profile if (equalsSchemaProfile(profiles[i], mapping.getClassIri(), mapping.getSubclassIri(), mapping.getTypeIri(), mapping.getSubPropertyIri(), mapping.getDescriptionIri(), mapping.getLabelIri(), mapping.getPropertyTypeIri(), - mapping.getPropertyLabelIri(), mapping.getPropertyDescriptionIri())) { + mapping.getPropertyLabelIri(), mapping.getPropertyDescriptionIri(), + mapping.getDeprecationPropertyIri())) { return profiles[i]; } } @@ -174,13 +183,13 @@ public static SchemaProfile checkSchemaProfile(KnowledgeBaseProfile aProfile) @SuppressWarnings("javadoc") public static SchemaProfile checkSchemaProfile(KnowledgeBase aKb) { - SchemaProfile[] profiles = SchemaProfile.values(); - for (int i = 0; i < profiles.length; i++) { + var profiles = SchemaProfile.values(); + for (var i = 0; i < profiles.length; i++) { // Check if kb has a known schema profile if (equalsSchemaProfile(profiles[i], aKb.getClassIri(), aKb.getSubclassIri(), aKb.getTypeIri(), aKb.getSubPropertyIri(), aKb.getDescriptionIri(), aKb.getLabelIri(), aKb.getPropertyTypeIri(), aKb.getPropertyLabelIri(), - aKb.getPropertyDescriptionIri())) { + aKb.getPropertyDescriptionIri(), aKb.getDeprecationPropertyIri())) { return profiles[i]; } } @@ -195,7 +204,7 @@ public static SchemaProfile checkSchemaProfile(KnowledgeBase aKb) private static boolean equalsSchemaProfile(SchemaProfile profile, String classIri, String subclassIri, String typeIri, String subPropertyIri, String descriptionIri, String labelIri, String propertyTypeIri, String propertyLabelIri, - String propertyDescriptionIri) + String propertyDescriptionIri, String deprecationPropertyIri) { return Objects.equals(profile.getClassIri(), classIri) && Objects.equals(profile.getSubclassIri(), subclassIri) @@ -205,6 +214,7 @@ private static boolean equalsSchemaProfile(SchemaProfile profile, String classIr && Objects.equals(profile.getLabelIri(), labelIri) && Objects.equals(profile.getPropertyTypeIri(), propertyTypeIri) && Objects.equals(profile.getPropertyLabelIri(), propertyLabelIri) - && Objects.equals(profile.getPropertyDescriptionIri(), propertyDescriptionIri); + && Objects.equals(profile.getPropertyDescriptionIri(), propertyDescriptionIri) + && Objects.equals(profile.getDeprecationPropertyIri(), deprecationPropertyIri); } } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/ExportedKnowledgeBase.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/ExportedKnowledgeBase.java index 2ee5f6472b2..6535674f2e6 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/ExportedKnowledgeBase.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/ExportedKnowledgeBase.java @@ -60,6 +60,9 @@ public class ExportedKnowledgeBase @JsonProperty("property_description_iri") private String propertyDescriptionIri; + @JsonProperty("deprecation_property_iri") + private String deprecationPropertyIri; + @JsonProperty("full_text_search_iri") private String fullTextSearchIri; @@ -229,6 +232,16 @@ public void setFullTextSearchIri(String aFullTextSearchIri) fullTextSearchIri = aFullTextSearchIri; } + public String getDeprecationPropertyIri() + { + return deprecationPropertyIri; + } + + public void setDeprecationPropertyIri(String aDeprecationPropertyIri) + { + deprecationPropertyIri = aDeprecationPropertyIri; + } + public boolean isReadOnly() { return readOnly; diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/KnowledgeBaseExporter.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/KnowledgeBaseExporter.java index 1acbfeed063..55d2bac5cfd 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/KnowledgeBaseExporter.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/exporter/KnowledgeBaseExporter.java @@ -125,6 +125,7 @@ public void exportData(FullProjectExportRequest aRequest, ProjectExportTaskMonit exportedKB.setPropertyTypeIri(kb.getPropertyTypeIri()); exportedKB.setPropertyLabelIri(kb.getPropertyLabelIri()); exportedKB.setPropertyDescriptionIri(kb.getPropertyDescriptionIri()); + exportedKB.setDeprecationPropertyIri(kb.getDeprecationPropertyIri()); exportedKB.setFullTextSearchIri(kb.getFullTextSearchIri()); exportedKB.setReadOnly(kb.isReadOnly()); exportedKB.setUseFuzzy(kb.isUseFuzzy()); @@ -181,11 +182,10 @@ public void importData(ProjectImportRequest aRequest, Project aProject, ExportedProject aExProject, ZipFile aZip) throws Exception { - ExportedKnowledgeBase[] knowledgeBases = aExProject.getArrayProperty(KEY, - ExportedKnowledgeBase.class); + var knowledgeBases = aExProject.getArrayProperty(KEY, ExportedKnowledgeBase.class); - for (ExportedKnowledgeBase exportedKB : knowledgeBases) { - KnowledgeBase kb = new KnowledgeBase(); + for (var exportedKB : knowledgeBases) { + var kb = new KnowledgeBase(); kb.setName(exportedKB.getName()); kb.setType(RepositoryType.valueOf(exportedKB.getType())); // set default value for IRIs if no value is present in @@ -217,15 +217,18 @@ public void importData(ProjectImportRequest aRequest, Project aProject, kb.setSubPropertyIri(exportedKB.getSubPropertyIri() != null // ? exportedKB.getSubPropertyIri() // : DEFAULTPROFILE.getSubPropertyIri()); + kb.setDeprecationPropertyIri(exportedKB.getDeprecationPropertyIri() != null // + ? exportedKB.getDeprecationPropertyIri() // + : DEFAULTPROFILE.getDeprecationPropertyIri()); + // The imported project may date from a time where we did not yet have the FTS IRI. // In that case we use concept linking support as an indicator that we dealt with a // remote Virtuoso. if (exportedKB.isSupportConceptLinking() && exportedKB.getFullTextSearchIri() == null) { kb.setFullTextSearchIri(IriConstants.FTS_VIRTUOSO.stringValue()); } - kb.setFullTextSearchIri( - exportedKB.getFullTextSearchIri() != null ? exportedKB.getFullTextSearchIri() // - : null); + kb.setFullTextSearchIri(exportedKB.getFullTextSearchIri() != null ? // + exportedKB.getFullTextSearchIri() : null); kb.setEnabled(exportedKB.isEnabled()); kb.setUseFuzzy(exportedKB.isUseFuzzy()); diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBConcept.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBConcept.java index 4f2091e77bf..af46dae8923 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBConcept.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBConcept.java @@ -39,6 +39,7 @@ public class KBConcept private String identifier; private String name; private String description; + private boolean deprecated; private KnowledgeBase kb; private String language; @@ -166,6 +167,17 @@ public void setDescription(String aDescription) description = aDescription; } + public void setDeprecated(boolean aDeprecated) + { + deprecated = aDeprecated; + } + + @Override + public boolean isDeprecated() + { + return deprecated; + } + @Override public String getLanguage() { diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBHandle.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBHandle.java index b5db6e23494..a8e08c98d99 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBHandle.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBHandle.java @@ -21,6 +21,7 @@ import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -44,6 +45,7 @@ public class KBHandle private String description; private KnowledgeBase kb; private String language; + private boolean deprecated; private int rank; private double score; @@ -56,22 +58,59 @@ public class KBHandle @Deprecated private String range; + private KBHandle(Builder builder) + { + identifier = builder.identifier; + name = builder.name; + queryBestMatchTerm = builder.queryBestMatchTerm; + matchTerms = builder.matchTerms; + description = builder.description; + kb = builder.kb; + language = builder.language; + deprecated = builder.deprecated; + rank = builder.rank; + score = builder.score; + debugInfo = builder.debugInfo; + domain = builder.domain; + range = builder.range; + } + + public KBHandle(KBHandle aOther) + { + identifier = aOther.identifier; + name = aOther.name; + queryBestMatchTerm = aOther.queryBestMatchTerm; + matchTerms = aOther.matchTerms; + description = aOther.description; + kb = aOther.kb; + language = aOther.language; + deprecated = aOther.deprecated; + rank = aOther.rank; + score = aOther.score; + debugInfo = aOther.debugInfo; + domain = aOther.domain; + range = aOther.range; + } + + @Deprecated public KBHandle() { this(null, null); } + @Deprecated public KBHandle(String aIdentifier) { this(aIdentifier, null); } + @Deprecated public KBHandle(String aIdentifier, String aLabel) { - this(aIdentifier, aLabel, null); + this(aIdentifier, aLabel, null, null); } - public KBHandle(String aIdentifier, String aLabel, String aDescription) + protected KBHandle(String aIdentifier, String aLabel, String aDescription) { identifier = aIdentifier; name = aLabel; @@ -98,6 +137,17 @@ public KBHandle(String aIdentifier, String aLabel, String aDescription, String a range = aRange; } + public void setDeprecated(boolean aDeprecated) + { + deprecated = aDeprecated; + } + + @Override + public boolean isDeprecated() + { + return deprecated; + } + @Deprecated public String getDomain() { @@ -242,7 +292,7 @@ public void setScore(double aScore) public static KBHandle of(KBObject aObject) { - return new KBHandle(aObject.getIdentifier(), aObject.getUiLabel()); + return aObject.toKBHandle(); } @SuppressWarnings("unchecked") @@ -254,6 +304,7 @@ public static T convertTo(Class aClass, KBHandle aHandle concept.setKB(aHandle.getKB()); concept.setLanguage(aHandle.getLanguage()); concept.setDescription(aHandle.getDescription()); + concept.setDeprecated(aHandle.isDeprecated()); concept.setName(aHandle.getName()); return (T) concept; } @@ -263,6 +314,7 @@ else if (aClass == KBInstance.class) { instance.setKB(aHandle.getKB()); instance.setLanguage(aHandle.getLanguage()); instance.setDescription(aHandle.getDescription()); + instance.setDeprecated(aHandle.isDeprecated()); instance.setName(aHandle.getName()); return (T) instance; } @@ -272,6 +324,7 @@ else if (aClass == KBProperty.class) { property.setKB(aHandle.getKB()); property.setLanguage(aHandle.getLanguage()); property.setDescription(aHandle.getDescription()); + property.setDeprecated(aHandle.isDeprecated()); property.setName(aHandle.getName()); property.setRange(aHandle.getRange()); property.setDomain(aHandle.getDomain()); @@ -335,6 +388,123 @@ public String toString() if (range != null) { builder.append("range", range); } + if (score != 0.0) { + builder.append("score", score); + } + if (deprecated) { + builder.append("deprecated", deprecated); + } return builder.toString(); } + + public static Builder builder() + { + return new Builder(); + } + + public static final class Builder + { + private String identifier; + private String name; + private String queryBestMatchTerm; + private Set> matchTerms = Collections.emptySet(); + private String description; + private KnowledgeBase kb; + private String language; + private boolean deprecated; + private int rank; + private double score; + private String debugInfo; + private String domain; + private String range; + + private Builder() + { + } + + public Builder withIdentifier(String aIdentifier) + { + identifier = aIdentifier; + return this; + } + + public Builder withName(String aName) + { + name = aName; + return this; + } + + public Builder withQueryBestMatchTerm(String aQueryBestMatchTerm) + { + queryBestMatchTerm = aQueryBestMatchTerm; + return this; + } + + public Builder withMatchTerms(Set> aMatchTerms) + { + matchTerms = aMatchTerms; + return this; + } + + public Builder withDescription(String aDescription) + { + description = aDescription; + return this; + } + + public Builder withKb(KnowledgeBase aKb) + { + this.kb = aKb; + return this; + } + + public Builder withLanguage(String aLanguage) + { + language = aLanguage; + return this; + } + + public Builder withDeprecated(boolean aDeprecated) + { + deprecated = aDeprecated; + return this; + } + + public Builder withRank(int aRank) + { + rank = aRank; + return this; + } + + public Builder withScore(double aScore) + { + score = aScore; + return this; + } + + public Builder withDebugInfo(String aDebugInfo) + { + debugInfo = aDebugInfo; + return this; + } + + @Deprecated + public Builder withDomain(String aDomain) + { + domain = aDomain; + return this; + } + + @Deprecated + public Builder withRange(String aRange) + { + range = aRange; + return this; + } + + public KBHandle build() + { + return new KBHandle(this); + } + } } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBInstance.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBInstance.java index 5de7ecf22b4..cda72dc665a 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBInstance.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBInstance.java @@ -41,6 +41,7 @@ public class KBInstance private String identifier; private String name; private String description; + private boolean deprecated; private URI type; private List originalStatements = new ArrayList<>(); private String language; @@ -115,6 +116,17 @@ public void setDescription(String aDescription) description = aDescription; } + public void setDeprecated(boolean aDeprecated) + { + deprecated = aDeprecated; + } + + @Override + public boolean isDeprecated() + { + return deprecated; + } + public List getOriginalStatements() { return originalStatements; diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBObject.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBObject.java index 697136045d9..55a2a98c1ad 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBObject.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBObject.java @@ -26,9 +26,7 @@ import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; /** - * A {@code KnowledgeGraphItem} represents any element (entity, type, relation, etc.) in the - * knowledge graph. {@code KnowledgeGraphItem}s can be identified by a {@code String} identifier. - * {@code KnowledgeGraphItem}s feature a label, a human-readable {@code String}. + * Represents any element (class, instance, property, etc.) in the knowledge base. */ public interface KBObject extends Serializable @@ -69,6 +67,8 @@ public interface KBObject void setDescription(String label); + boolean isDeprecated(); + /** * @return the language (e.g. of label and description) of this element. */ @@ -122,6 +122,8 @@ default KBHandle toKBHandle() handle.setName(getName()); handle.setLanguage(getLanguage()); handle.setDescription(getDescription()); + handle.setDeprecated(isDeprecated()); + handle.setKB(getKB()); return handle; } } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBProperty.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBProperty.java index 5fab81ceda7..cb28ab15b07 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBProperty.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/graph/KBProperty.java @@ -44,6 +44,7 @@ public class KBProperty private String identifier; private String name; private String description; + private boolean deprecated; private String domain; private KnowledgeBase kb; private String range; @@ -121,6 +122,17 @@ public void setDescription(String aDescription) description = aDescription; } + public void setDeprecated(boolean aDeprecated) + { + deprecated = aDeprecated; + } + + @Override + public boolean isDeprecated() + { + return deprecated; + } + public String getDomain() { return domain; diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/model/KnowledgeBase.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/model/KnowledgeBase.java index 000995c965a..515c9429727 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/model/KnowledgeBase.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/model/KnowledgeBase.java @@ -151,6 +151,12 @@ public class KnowledgeBase @Column(nullable = false) private String propertyDescriptionIri; + /** + * The IRI for a property marking a resources as deprecated + */ + @Column(nullable = false) + private String deprecationPropertyIri; + /** * The IRI of the default dataset */ @@ -364,6 +370,16 @@ public String getPropertyDescriptionIri() return propertyDescriptionIri; } + public void setDeprecationPropertyIri(String aDeprecationPropertyIri) + { + deprecationPropertyIri = aDeprecationPropertyIri; + } + + public String getDeprecationPropertyIri() + { + return deprecationPropertyIri; + } + public boolean isReadOnly() { return readOnly; @@ -449,6 +465,7 @@ public void applyMapping(KnowledgeBaseMapping aMapping) setPropertyTypeIri(aMapping.getPropertyTypeIri()); setPropertyLabelIri(aMapping.getPropertyLabelIri()); setPropertyDescriptionIri(aMapping.getPropertyDescriptionIri()); + setDeprecationPropertyIri(aMapping.getDeprecationPropertyIri()); } public void applyRootConcepts(KnowledgeBaseProfile aProfile) diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphFtsQuery.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphFtsQuery.java new file mode 100644 index 00000000000..4eb181a9b20 --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphFtsQuery.java @@ -0,0 +1,90 @@ +/* + * 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.kb.querybuilder; + +import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.prefix; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOf; + +import org.eclipse.rdf4j.sparqlbuilder.core.Prefix; +import org.eclipse.rdf4j.sparqlbuilder.core.Variable; +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; +import org.eclipse.rdf4j.sparqlbuilder.rdf.Iri; + +import de.tudarmstadt.ukp.inception.kb.IriConstants; + +public class BlazegraphFtsQuery + implements GraphPattern +{ + public static final Prefix PREFIX_BLAZEGRAPH_SEARCH = prefix("bds", + iri(IriConstants.FTS_BLAZEGRAPH)); + public static final Iri BLAZEGRAPH_SEARCH = PREFIX_BLAZEGRAPH_SEARCH.iri("search"); + public static final Iri BLAZEGRAPH_RELEVANCE = PREFIX_BLAZEGRAPH_SEARCH.iri("relevance"); + public static final Iri BLAZEGRAPH_MAX_RANK = PREFIX_BLAZEGRAPH_SEARCH.iri("maxRank"); + + private final Variable subject; + private final Variable score; + private final Variable matchTerm; + private final Variable matchTermProperty; + private final String query; + private int limit = 0; + + public BlazegraphFtsQuery(Variable aSubject, Variable aScore, Variable aMatchTerm, + Variable aMatchTermProperty, String aQuery) + { + subject = aSubject; + score = aScore; + matchTerm = aMatchTerm; + matchTermProperty = aMatchTermProperty; + query = aQuery; + } + + public BlazegraphFtsQuery withLimit(int aLimit) + { + limit = aLimit; + return this; + } + + @Override + public String getQueryString() + { + var sb = new StringBuilder(); + sb.append("SERVICE "); + sb.append(BLAZEGRAPH_SEARCH.getQueryString()); + sb.append(" { \n"); + + var pattern = matchTerm // + .has(BLAZEGRAPH_SEARCH, literalOf(query)) // + .andHas(BLAZEGRAPH_RELEVANCE, score); + + if (limit > 0) { + pattern = pattern.andHas(BLAZEGRAPH_MAX_RANK, literalOf(2 * limit)); + } + + sb.append(pattern.getQueryString()); + sb.append(" } "); + sb.append(subject.has(matchTermProperty, matchTerm).getQueryString()); + return sb.toString(); + } + + @Override + public boolean isEmpty() + { + return false; + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapter.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapter.java new file mode 100644 index 00000000000..2467bca506f --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapter.java @@ -0,0 +1,30 @@ +/* + * 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.kb.querybuilder; + +public interface FtsAdapter + extends SPARQLVariables +{ + void withLabelMatchingExactlyAnyOf(String... values); + + void withLabelContainingAnyOf(String... values); + + void withLabelMatchingAnyOf(String... values); + + void withLabelStartingWith(String prefix); +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterBlazegraph.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterBlazegraph.java new file mode 100644 index 00000000000..a557324bb7b --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterBlazegraph.java @@ -0,0 +1,187 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterBlazegraph + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterBlazegraph(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + builder.addPrefix(SPARQLQueryBuilder.PREFIX_BLAZEGRAPH_SEARCH); + } + + @Override + public void withLabelMatchingExactlyAnyOf(String[] aValues) + { + var kb = builder.getKnowledgeBase(); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(kb, sanitizedValue); + } + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(SPARQLQueryBuilder.VAR_SCORE); + + valuePatterns.add(new BlazegraphFtsQuery(SPARQLQueryBuilder.VAR_SUBJECT, + SPARQLQueryBuilder.VAR_SCORE, SPARQLQueryBuilder.VAR_MATCH_TERM, + SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY, sanitizedValue) // + .withLimit(builder.getLimit()) // + .filter(builder.equalsPattern(SPARQLQueryBuilder.VAR_MATCH_TERM, value, + kb))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + var kb = builder.getKnowledgeBase(); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(kb, sanitizedValue); + } + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(SPARQLQueryBuilder.VAR_SCORE); + + valuePatterns.add(new BlazegraphFtsQuery(SPARQLQueryBuilder.VAR_SUBJECT, + SPARQLQueryBuilder.VAR_SCORE, SPARQLQueryBuilder.VAR_MATCH_TERM, + SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY, sanitizedValue) // + .withLimit(builder.getLimit()) // + .filter(builder.containsPattern(SPARQLQueryBuilder.VAR_MATCH_TERM, + value))); + } + + builder.addPattern(PRIMARY, + and(builder.bindMatchTermProperties(SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + var kb = builder.getKnowledgeBase(); + + var queryString = aPrefixQuery.trim(); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + queryString = SPARQLQueryBuilder.toLowerCase(kb, queryString); + } + + if (queryString.isEmpty()) { + builder.setReturnEmptyResult(true); + } + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!aPrefixQuery.endsWith(" ")) { + queryString += "*"; + } + + builder.addProjection(SPARQLQueryBuilder.VAR_SCORE); + + // Locate all entries where the label contains the prefix (using the FTS) and then + // filter them by those which actually start with the prefix. + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY), // + new BlazegraphFtsQuery(SPARQLQueryBuilder.VAR_SUBJECT, SPARQLQueryBuilder.VAR_SCORE, + SPARQLQueryBuilder.VAR_MATCH_TERM, + SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY, queryString) // + .withLimit(builder.getLimit()) // + .filter(builder.startsWithPattern(SPARQLQueryBuilder.VAR_MATCH_TERM, + aPrefixQuery)))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + var kb = builder.getKnowledgeBase(); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(kb, sanitizedValue); + } + + var fuzzyQuery = SPARQLQueryBuilder.convertToFuzzyMatchingQuery(sanitizedValue, "*"); + + if (isBlank(fuzzyQuery)) { + continue; + } + + builder.addProjection(SPARQLQueryBuilder.VAR_SCORE); + + valuePatterns.add(new BlazegraphFtsQuery(SPARQLQueryBuilder.VAR_SUBJECT, + SPARQLQueryBuilder.VAR_SCORE, SPARQLQueryBuilder.VAR_MATCH_TERM, + SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY, fuzzyQuery) + .withLimit(builder.getLimit())); + } + + builder.addPattern(PRIMARY, + and(builder.bindMatchTermProperties(SPARQLQueryBuilder.VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterFuseki.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterFuseki.java new file mode 100644 index 00000000000..4f88f32a41d --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterFuseki.java @@ -0,0 +1,180 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterFuseki + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterFuseki(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_FUSEKI_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(builder.getKnowledgeBase(), + sanitizedValue); + } + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(VAR_SCORE); + + valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, + VAR_MATCH_TERM_PROPERTY, sanitizedValue) // + .withLimit(builder.getLimit()) // + .filter(builder.equalsPattern(VAR_MATCH_TERM, value, + builder.getKnowledgeBase()))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_FUSEKI_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(builder.getKnowledgeBase(), + sanitizedValue); + } + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(VAR_SCORE); + + valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, + VAR_MATCH_TERM_PROPERTY, sanitizedValue) // + .withLimit(builder.getLimit()) // + .filter(builder.containsPattern(VAR_MATCH_TERM, value))); + } + + builder.addPattern(PRIMARY, and(builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + builder.addPrefix(PREFIX_FUSEKI_SEARCH); + + var queryString = aPrefixQuery.trim(); + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + queryString = SPARQLQueryBuilder.toLowerCase(builder.getKnowledgeBase(), queryString); + } + + if (queryString.isEmpty()) { + builder.setReturnEmptyResult(true); + } + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!aPrefixQuery.endsWith(" ")) { + queryString += "*"; + } + + builder.addProjection(VAR_SCORE); + + // Locate all entries where the label contains the prefix (using the FTS) and then + // filter them by those which actually start with the prefix. + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, VAR_MATCH_TERM_PROPERTY, + queryString) // + .withLimit(builder.getLimit()) // + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery)))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_FUSEKI_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + // We assume that the FTS is case insensitive and found that some FTSes (i.e. + // Fuseki) can have trouble matching if they get upper-case query when they + // internally lower-case# + if (builder.isCaseInsensitive()) { + sanitizedValue = SPARQLQueryBuilder.toLowerCase(builder.getKnowledgeBase(), + sanitizedValue); + } + + String fuzzyQuery = SPARQLQueryBuilder.convertToFuzzyMatchingQuery(sanitizedValue, "~"); + + if (isBlank(fuzzyQuery)) { + continue; + } + + builder.addProjection(VAR_SCORE); + + valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, + VAR_MATCH_TERM_PROPERTY, fuzzyQuery).withLimit(builder.getLimit())); + } + + builder.addPattern(PRIMARY, and(builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterNoFts.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterNoFts.java new file mode 100644 index 00000000000..7e6581db4a1 --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterNoFts.java @@ -0,0 +1,123 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.SECONDARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOf; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOfLanguage; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; +import org.eclipse.rdf4j.sparqlbuilder.rdf.RdfValue; + +public class FtsAdapterNoFts + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterNoFts(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + var values = new ArrayList(); + var language = builder.getKnowledgeBase().getDefaultLanguage(); + + for (var value : aValues) { + var sanitizedValue = sanitizeQueryString_noFTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + if (language != null) { + values.add(literalOfLanguage(sanitizedValue, language)); + } + + values.add(literalOf(sanitizedValue)); + } + + builder.addPattern(PRIMARY, + and(builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + new ValuesPattern(VAR_MATCH_TERM, values), + VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + if (isBlank(value)) { + continue; + } + + valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.containsPattern(VAR_MATCH_TERM, value))); + } + + // The WikiData search service does not support properties. So we disable the use of the + // WikiData search service when looking for properties. But then, searching first by + // the label becomes very slow because withLabelMatchingAnyOf falls back to "containing" + // when no FTS is used. To avoid forcing the SPARQL server to perform a full scan + // of its database, we demote the label matching to a secondary condition, allowing the + // the matching by type (e.g. PRIMARY_RESTRICTIONS is-a property) to take precedence. + builder.addPattern(SECONDARY, and(builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + if (aPrefixQuery.isEmpty()) { + builder.setReturnEmptyResult(true); + } + + // Label matching without FTS is slow, so we add this with low prio and hope that some + // other higher-prio condition exists which limites the number of candidates to a + // manageable level + builder.addPattern(SECONDARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery)))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + // Falling back to "contains" semantics if there is no FTS + withLabelContainingAnyOf(aValues); + } + + private String sanitizeQueryString_noFTS(String aQuery) + { + return aQuery + // character classes to replace with a simple space + .replaceAll("[\\p{Space}\\p{Cntrl}]+", " ") // + .trim(); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterRdf4J.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterRdf4J.java new file mode 100644 index 00000000000..f9e5f055f93 --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterRdf4J.java @@ -0,0 +1,187 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_LUCENE; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.and; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.bind; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.function; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.str; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.REPLACE; +import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.var; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.bNode; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOf; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.constraint.Expression; +import org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions; +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterRdf4J + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterRdf4J(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_LUCENE_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + // Strip single quotes and asterisks because they have special semantics + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(VAR_SCORE); + + valuePatterns.add(VAR_SUBJECT + .has(FTS_LUCENE, bNode(LUCENE_QUERY, literalOf(sanitizedValue)) // + .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY) // + .andHas(LUCENE_SCORE, VAR_SCORE)) + .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM).filter(builder + .equalsPattern(VAR_MATCH_TERM, value, builder.getKnowledgeBase()))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_LUCENE_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + builder.addProjection(VAR_SCORE); + + valuePatterns.add(VAR_SUBJECT + .has(FTS_LUCENE, bNode(LUCENE_QUERY, literalOf(sanitizedValue + "*")) // + .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY) // + .andHas(LUCENE_SCORE, VAR_SCORE)) + .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.containsPattern(VAR_MATCH_TERM, value))); + } + + builder.addPattern(PRIMARY, and(builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + builder.addPrefix(PREFIX_LUCENE_SEARCH); + + // Strip single quotes and asterisks because they have special semantics + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(aPrefixQuery); + + if (isBlank(sanitizedValue)) { + builder.setReturnEmptyResult(true); + } + + var queryString = sanitizedValue.trim(); + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!aPrefixQuery.endsWith(" ")) { + queryString += "*"; + } + + builder.addProjection(VAR_SCORE); + + // Locate all entries where the label contains the prefix (using the FTS) and then + // filter them by those which actually start with the prefix. + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + VAR_SUBJECT.has(FTS_LUCENE, bNode(LUCENE_QUERY, literalOf(queryString)) // + .andHas(LUCENE_SCORE, VAR_SCORE) + .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY)) + .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery)))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_LUCENE_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + // Strip single quotes and asterisks because they have special semantics + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + var fuzzyQuery = SPARQLQueryBuilder.convertToFuzzyMatchingQuery(sanitizedValue, "~"); + + if (isBlank(sanitizedValue) || isBlank(fuzzyQuery)) { + continue; + } + + var labelFilterExpressions = new ArrayList>(); + labelFilterExpressions.add(Expressions.equals(str(var("label")), str(VAR_MATCH_TERM))); + labelFilterExpressions.add(builder.matchKbLanguage(VAR_MATCH_TERM)); + + builder.addProjection(VAR_SCORE); + + // If a KB item has multiple labels, we want to return only the ones which actually + // match the query term such that the user is not confused that the results contain + // items that don't match the query (even though they do through a label that is not + // returned). RDF4J only provides access to the matched term in a "highlighed" form + // where "" and "" match the search term. So we have to strip these markers + // out as part of the query. + valuePatterns.add(VAR_SUBJECT // + .has(FTS_LUCENE, bNode(LUCENE_QUERY, literalOf(fuzzyQuery)) // + .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY) // + .andHas(LUCENE_SCORE, VAR_SCORE) // + .andHas(LUCENE_SNIPPET, var("snippet"))) + .and(bind( + function(REPLACE, + function(REPLACE, var("snippet"), literalOf(""), + literalOf("")), + literalOf(""), literalOf("")), + var("label"))) + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM)) + .filter(and(labelFilterExpressions.toArray(Expression[]::new)))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterStardog.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterStardog.java new file mode 100644 index 00000000000..f806e0f109a --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterStardog.java @@ -0,0 +1,139 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterStardog + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterStardog(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_STARDOG_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, sanitizedValue) // + .withLimit(builder.getLimit()) // + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM).filter(builder + .equalsPattern(VAR_MATCH_TERM, value, builder.getKnowledgeBase())))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_STARDOG_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, sanitizedValue) + .withLimit(builder.getLimit()) // + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.containsPattern(VAR_MATCH_TERM, value)))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + builder.addPrefix(PREFIX_STARDOG_SEARCH); + + // Strip single quotes and asterisks because they have special semantics + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(aPrefixQuery); + + if (isBlank(sanitizedValue)) { + builder.setReturnEmptyResult(true); + } + + var queryString = sanitizedValue.trim(); + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!aPrefixQuery.endsWith(" ")) { + queryString += "*"; + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + new StardogEntitySearchService(VAR_MATCH_TERM, queryString) // + .withLimit(builder.getLimit()) // + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + builder.addPrefix(PREFIX_STARDOG_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + var fuzzyQuery = SPARQLQueryBuilder.convertToFuzzyMatchingQuery(sanitizedValue, "~"); + + if (isBlank(sanitizedValue) || isBlank(fuzzyQuery)) { + continue; + } + + valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, fuzzyQuery) // + .withLimit(builder.getLimit()) // + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterVirtuoso.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterVirtuoso.java new file mode 100644 index 00000000000..55a088ddb57 --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterVirtuoso.java @@ -0,0 +1,177 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOf; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterVirtuoso + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterVirtuoso(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + // addPrefix(PREFIX_VIRTUOSO_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, + literalOf("\"" + sanitizedValue + "\""))) + .filter(builder.equalsPattern(VAR_MATCH_TERM, value, + builder.getKnowledgeBase()))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + // addPrefix(PREFIX_VIRTUOSO_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, + literalOf("\"" + sanitizedValue + "\""))) + .filter(builder.containsPattern(VAR_MATCH_TERM, value))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + // addPrefix(PREFIX_VIRTUOSO_SEARCH); + + // Strip single quotes and asterisks because they have special semantics + String sanitizedQuery = SPARQLQueryBuilder.sanitizeQueryString_FTS(aPrefixQuery); + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!aPrefixQuery.endsWith(" ")) { + sanitizedQuery = virtuosoStartsWithQuery(sanitizedQuery); + } + + // If the query string was reduced to nothing, then the query should always return an + // empty + // result. + if (sanitizedQuery.length() == 2) { + builder.setReturnEmptyResult(true); + } + + // Locate all entries where the label contains the prefix (using the FTS) and then + // filter them by those which actually start with the prefix. + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, + literalOf("\"" + sanitizedQuery + "\""))) + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery)))); + } + + private static String virtuosoStartsWithQuery(String sanitizedQuery) + { + var ftsQueryString = new StringBuilder(); + var queryTokens = sanitizedQuery.split(" "); + + for (var i = 0; i < queryTokens.length; i++) { + if (i > 0) { + ftsQueryString.append(" "); + } + + // Virtuoso requires that a token has at least 4 characters before it can be + // used with a wildcard. If the last token has less than 4 characters, we simply + // drop it to avoid the user hitting a point where the auto-suggesions suddenly + // are empty. If the token 4 or more, we add the wildcard. + if (i == (queryTokens.length - 1)) { + if (queryTokens[i].length() >= 4) { + ftsQueryString.append(queryTokens[i]); + ftsQueryString.append("*"); + } + } + else { + ftsQueryString.append(queryTokens[i]); + } + } + + return ftsQueryString.toString(); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + // addPrefix(PREFIX_VIRTUOSO_SEARCH); + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + // If the query string entered by the user does not end with a space character, then + // we assume that the user may not yet have finished writing the word and add a + // wildcard + if (!value.endsWith(" ")) { + sanitizedValue = FtsAdapterVirtuoso.virtuosoStartsWithQuery(sanitizedValue); + } + + valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM).and( + VAR_MATCH_TERM.has(VIRTUOSO_QUERY, literalOf("\"" + sanitizedValue + "\"")))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterWikidata.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterWikidata.java new file mode 100644 index 00000000000..0536663967b --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FtsAdapterWikidata.java @@ -0,0 +1,140 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; +import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.union; + +import java.util.ArrayList; + +import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; + +public class FtsAdapterWikidata + implements FtsAdapter +{ + private final SPARQLQueryBuilder builder; + + public FtsAdapterWikidata(SPARQLQueryBuilder aBuilder) + { + builder = aBuilder; + } + + @Override + public void withLabelMatchingExactlyAnyOf(String... aValues) + { + var kb = builder.getKnowledgeBase(); + + // In our KB settings, the language can be unset, but the Wikidata entity search + // requires a preferred language. So we use English as the default. + var language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.equalsPattern(VAR_MATCH_TERM, value, kb)))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelContainingAnyOf(String... aValues) + { + var kb = builder.getKnowledgeBase(); + + // In our KB settings, the language can be unset, but the Wikidata entity search + // requires a preferred language. So we use English as the default. + var language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.containsPattern(VAR_MATCH_TERM, value)))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // + union(valuePatterns.toArray(GraphPattern[]::new)))); + } + + @Override + public void withLabelStartingWith(String aPrefixQuery) + { + var kb = builder.getKnowledgeBase(); + + // In our KB settings, the language can be unset, but the Wikidata entity search + // requires a preferred language. So we use English as the default. + var language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; + + if (aPrefixQuery.isEmpty()) { + builder.setReturnEmptyResult(true); + } + + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(aPrefixQuery); + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) + .filter(builder.startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))))); + } + + @Override + public void withLabelMatchingAnyOf(String... aValues) + { + var kb = builder.getKnowledgeBase(); + + // In our KB settings, the language can be unset, but the Wikidata entity search + // requires a preferred language. So we use English as the default. + var language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; + + var valuePatterns = new ArrayList(); + for (var value : aValues) { + var sanitizedValue = SPARQLQueryBuilder.sanitizeQueryString_FTS(value); + + if (isBlank(sanitizedValue)) { + continue; + } + + valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) + .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM))); + } + + builder.addPattern(PRIMARY, and( // + builder.bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), + union(valuePatterns.toArray(GraphPattern[]::new)))); + } +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Queries.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Queries.java index 49b299e8040..4193145f6fd 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Queries.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Queries.java @@ -50,6 +50,7 @@ public static Map fetchProperties(KnowledgeBase aKB, .withIdentifier(propertyIris) // .retrieveLabel() // .retrieveDescription() // + .retrieveDeprecation() // .retrieveDomainAndRange() // .asHandles(aConn, true) // .stream() // diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilder.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilder.java index 4fc4be6197f..07882a9192c 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilder.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilder.java @@ -17,14 +17,13 @@ */ package de.tudarmstadt.ukp.inception.kb.querybuilder; +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_BLAZEGRAPH; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_FUSEKI; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_LUCENE; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_NONE; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_STARDOG; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_VIRTUOSO; import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_WIKIDATA; -import static de.tudarmstadt.ukp.inception.kb.IriConstants.PREFIX_STARDOG; -import static de.tudarmstadt.ukp.inception.kb.IriConstants.PREFIX_VIRTUOSO; import static de.tudarmstadt.ukp.inception.kb.IriConstants.hasImplicitNamespace; import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY; import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.Priority.PRIMARY_RESTRICTIONS; @@ -37,22 +36,18 @@ import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; import static java.util.stream.Stream.concat; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.and; -import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.bind; import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.function; import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.notEquals; import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.or; -import static org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions.str; import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.CONTAINS; import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.LANG; import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.LANGMATCHES; import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.REGEX; -import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.REPLACE; +import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.STR; import static org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction.STRSTARTS; import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.dataset; import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.from; -import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.prefix; import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.var; import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.and; import static org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPatterns.filterExists; @@ -62,7 +57,6 @@ import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.bNode; import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri; import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOf; -import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.literalOfLanguage; import java.util.ArrayList; import java.util.Arrays; @@ -82,19 +76,16 @@ import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.model.vocabulary.OWL; -import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.query.Binding; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.QueryEvaluationException; import org.eclipse.rdf4j.query.TupleQuery; -import org.eclipse.rdf4j.query.TupleQueryResult; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.sparqlbuilder.constraint.Expression; import org.eclipse.rdf4j.sparqlbuilder.constraint.Expressions; import org.eclipse.rdf4j.sparqlbuilder.constraint.Operand; import org.eclipse.rdf4j.sparqlbuilder.constraint.SparqlFunction; -import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.PropertyPath; import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.builder.PropertyPathBuilder; import org.eclipse.rdf4j.sparqlbuilder.core.Prefix; import org.eclipse.rdf4j.sparqlbuilder.core.Projectable; @@ -128,62 +119,13 @@ *

*/ public class SPARQLQueryBuilder - implements SPARQLQuery, SPARQLQueryPrimaryConditions, SPARQLQueryOptionalElements + implements SPARQLVariables, SPARQLQuery, SPARQLQueryPrimaryConditions, + SPARQLQueryOptionalElements { private final static Logger LOG = LoggerFactory.getLogger(SPARQLQueryBuilder.class); public static final int DEFAULT_LIMIT = 0; - public static final String VAR_SUBJECT_NAME = "subj"; - public static final String VAR_PREDICATE_NAME = "pred"; - public static final String VAR_OBJECT_NAME = "obj"; - public static final String VAR_MATCH_TERM_PROPERTY_NAME = "pMatch"; - public static final String VAR_PREF_LABEL_PROPERTY_NAME = "pPrefLabel"; - public static final String VAR_PREF_LABEL_NAME = "l"; - public static final String VAR_MATCH_TERM_NAME = "m"; - public static final String VAR_SCORE_NAME = "sc"; - public static final String VAR_DESCRIPTION_NAME = "d"; - public static final String VAR_DESCRIPTION_CANDIDATE_NAME = "dc"; - public static final String VAR_RANGE_NAME = "range"; - public static final String VAR_DOMAIN_NAME = "domain"; - - public static final Variable VAR_SUBJECT = var(VAR_SUBJECT_NAME); - public static final Variable VAR_SCORE = var(VAR_SCORE_NAME); - public static final Variable VAR_PREDICATE = var(VAR_PREDICATE_NAME); - public static final Variable VAR_OBJECT = var(VAR_OBJECT_NAME); - public static final Variable VAR_RANGE = var(VAR_RANGE_NAME); - public static final Variable VAR_DOMAIN = var(VAR_DOMAIN_NAME); - public static final Variable VAR_PREF_LABEL = var(VAR_PREF_LABEL_NAME); - public static final Variable VAR_MATCH_TERM = var(VAR_MATCH_TERM_NAME); - public static final Variable VAR_PREF_LABEL_PROPERTY = var(VAR_PREF_LABEL_PROPERTY_NAME); - public static final Variable VAR_MATCH_TERM_PROPERTY = var(VAR_MATCH_TERM_PROPERTY_NAME); - public static final Variable VAR_DESCRIPTION = var(VAR_DESCRIPTION_NAME); - public static final Variable VAR_DESC_CANDIDATE = var(VAR_DESCRIPTION_CANDIDATE_NAME); - - public static final Prefix PREFIX_LUCENE_SEARCH = prefix("search", - iri("http://www.openrdf.org/contrib/lucenesail#")); - public static final Iri LUCENE_QUERY = PREFIX_LUCENE_SEARCH.iri("query"); - public static final Iri LUCENE_PROPERTY = PREFIX_LUCENE_SEARCH.iri("property"); - public static final Iri LUCENE_SCORE = PREFIX_LUCENE_SEARCH.iri("score"); - public static final Iri LUCENE_SNIPPET = PREFIX_LUCENE_SEARCH.iri("snippet"); - - public static final Prefix PREFIX_FUSEKI_SEARCH = prefix("text", - iri("http://jena.apache.org/text#")); - public static final Iri FUSEKI_QUERY = PREFIX_FUSEKI_SEARCH.iri("query"); - - public static final Prefix PREFIX_STARDOG_SEARCH = prefix("fts", iri(PREFIX_STARDOG)); - - // Some versions of Virtuoso do not like it when we declare the bif prefix. - // public static final Prefix PREFIX_VIRTUOSO_SEARCH = prefix("bif", iri(PREFIX_VIRTUOSO)); - // public static final Iri VIRTUOSO_QUERY = PREFIX_VIRTUOSO_SEARCH.iri("contains"); - public static final Iri VIRTUOSO_QUERY = iri(PREFIX_VIRTUOSO, "contains"); - - public static final Iri OWL_INTERSECTIONOF = iri(OWL.INTERSECTIONOF.stringValue()); - public static final Iri RDF_REST = iri(RDF.REST.stringValue()); - public static final Iri RDF_FIRST = iri(RDF.FIRST.stringValue()); - public static final PropertyPath OWL_INTERSECTIONOF_PATH = PropertyPathBuilder - .of(OWL_INTERSECTIONOF).then(RDF_REST).zeroOrMore().then(RDF_FIRST).build(); - private static final RdfValue EMPTY_STRING = () -> "\"\""; private final Set prefixes = new LinkedHashSet<>(); @@ -302,13 +244,13 @@ protected List getAdditionalMatchingProperties(KnowledgeBase aKb) */ protected GraphPattern descendentsPattern(KnowledgeBase aKB, Iri aContext) { - Iri typeOfProperty = iri(aKB.getTypeIri()); - Iri subClassProperty = iri(aKB.getSubclassIri()); - Iri subPropertyProperty = iri(aKB.getSubPropertyIri()); + var typeOfProperty = iri(aKB.getTypeIri()); + var subClassProperty = iri(aKB.getSubclassIri()); + var subPropertyProperty = iri(aKB.getSubPropertyIri()); switch (this) { case ITEM: { - List classPatterns = new ArrayList<>(); + var classPatterns = new ArrayList(); classPatterns.add(VAR_SUBJECT.has( PropertyPathBuilder.of(subClassProperty).oneOrMore().build(), aContext)); classPatterns.add(VAR_SUBJECT.has(PropertyPathBuilder.of(typeOfProperty) @@ -320,7 +262,7 @@ protected GraphPattern descendentsPattern(KnowledgeBase aKB, Iri aContext) return GraphPatterns.union(classPatterns.stream().toArray(GraphPattern[]::new)); } case CLASS: { - List classPatterns = new ArrayList<>(); + var classPatterns = new ArrayList(); classPatterns.add(VAR_SUBJECT.has( PropertyPathBuilder.of(subClassProperty).oneOrMore().build(), aContext)); if (OWL.CLASS.stringValue().equals(aKB.getClassIri())) { @@ -345,15 +287,15 @@ protected GraphPattern descendentsPattern(KnowledgeBase aKB, Iri aContext) */ protected GraphPattern ancestorsPattern(KnowledgeBase aKB, Iri aContext) { - Iri typeOfProperty = iri(aKB.getTypeIri()); - Iri subClassProperty = iri(aKB.getSubclassIri()); - Iri subPropertyProperty = iri(aKB.getSubPropertyIri()); + var typeOfProperty = iri(aKB.getTypeIri()); + var subClassProperty = iri(aKB.getSubclassIri()); + var subPropertyProperty = iri(aKB.getSubPropertyIri()); switch (this) { case ITEM: case CLASS: case INSTANCE: { - List classPatterns = new ArrayList<>(); + var classPatterns = new ArrayList(); classPatterns.add(aContext.has( PropertyPathBuilder.of(subClassProperty).oneOrMore().build(), VAR_SUBJECT)); classPatterns.add(aContext.has(PropertyPathBuilder.of(typeOfProperty) @@ -448,22 +390,22 @@ protected GraphPattern parentsPattern(KnowledgeBase aKB, Iri aContext) */ protected GraphPattern rootsPattern(KnowledgeBase aKb) { - Iri classIri = iri(aKb.getClassIri()); - Iri subClassProperty = iri(aKb.getSubclassIri()); - Iri typeOfProperty = iri(aKb.getTypeIri()); - Variable otherSubclass = var("otherSubclass"); + var classIri = iri(aKb.getClassIri()); + var subClassProperty = iri(aKb.getSubclassIri()); + var typeOfProperty = iri(aKb.getTypeIri()); + var otherSubclass = var("otherSubclass"); switch (this) { case CLASS: { - List rootPatterns = new ArrayList<>(); + var rootPatterns = new ArrayList(); - Set rootConcepts = aKb.getRootConcepts(); + var rootConcepts = aKb.getRootConcepts(); if (rootConcepts != null && !rootConcepts.isEmpty()) { rootPatterns.add(new ValuesPattern(VAR_SUBJECT, rootConcepts.stream() .map(iri -> iri(iri)).collect(Collectors.toList()))); } else { - List classPatterns = new ArrayList<>(); + var classPatterns = new ArrayList(); classPatterns.add(VAR_SUBJECT.has(subClassProperty, otherSubclass) .filter(notEquals(VAR_SUBJECT, otherSubclass))); if (OWL.CLASS.stringValue().equals(aKb.getClassIri())) { @@ -487,6 +429,19 @@ protected GraphPattern rootsPattern(KnowledgeBase aKb) throw new IllegalStateException("Can only query for root classes"); } } + + protected Iri getDeprecationProperty(KnowledgeBase aKb) + { + switch (this) { + case ITEM: // fall-through + case CLASS: // fall-through + case INSTANCE: // fall-through + case PROPERTY: + return iri(aKb.getDeprecationPropertyIri()); + default: + throw new IllegalStateException("Unsupported mode: " + this); + } + } } /** @@ -562,7 +517,7 @@ private SPARQLQueryBuilder(KnowledgeBase aKB, Mode aMode) } } - private void addPattern(Priority aPriority, GraphPattern aPattern) + void addPattern(Priority aPriority, GraphPattern aPattern) { switch (aPriority) { case PRIMARY: @@ -594,6 +549,11 @@ private Projectable getDescriptionProjection() return VAR_DESC_CANDIDATE; } + private Projectable getDeprecationProjection() + { + return VAR_DEPRECATION; + } + @Override public SPARQLQueryOptionalElements includeInferred() { @@ -646,6 +606,31 @@ public SPARQLQueryOptionalElements caseInsensitive() return this; } + KnowledgeBase getKnowledgeBase() + { + return kb; + } + + boolean isCaseInsensitive() + { + return caseInsensitive; + } + + void addPrefix(Prefix aPrefix) + { + prefixes.add(aPrefix); + } + + void addProjection(Projectable aProjectable) + { + projections.add(aProjectable); + } + + void setReturnEmptyResult(boolean aReturnEmptyResult) + { + returnEmptyResult = aReturnEmptyResult; + } + /** * Generates a pattern which binds all sub-properties of the label property to the given * variable. @@ -663,7 +648,7 @@ private GraphPattern bindPrefLabelProperties(Variable aVariable) * Generates a pattern which binds all sub-properties of the matching properties to the given * variable. */ - private GraphPattern bindMatchTermProperties(Variable aVariable) + GraphPattern bindMatchTermProperties(Variable aVariable) { Iri pLabel = mode.getLabelProperty(kb); Iri pSubProperty = iri(kb.getSubPropertyIri()); @@ -747,7 +732,7 @@ public SPARQLQueryPrimaryConditions matchingDomain(String aIdentifier) @Override public SPARQLQueryBuilder withLabelMatchingExactlyAnyOf(String... aValues) { - String[] values = Arrays.stream(aValues) // + var values = Arrays.stream(aValues) // .map(SPARQLQueryBuilder::trimQueryString) // .filter(StringUtils::isNotBlank) // .toArray(String[]::new); @@ -757,37 +742,46 @@ public SPARQLQueryBuilder withLabelMatchingExactlyAnyOf(String... aValues) return this; } + getAdapter().withLabelMatchingExactlyAnyOf(aValues); + + addMatchTermProjections(projections); + labelImplicitlyRetrieved = true; + + return this; + } + + private FtsAdapter getAdapter() + { IRI ftsMode = getFtsMode(); if (FTS_LUCENE.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_RDF4J_FTS(values)); - } - else if (FTS_FUSEKI.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_Fuseki_FTS(values)); + return new FtsAdapterRdf4J(this); } - else if (FTS_VIRTUOSO.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_Virtuoso_FTS(values)); - } - else if (FTS_STARDOG.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_Stardog_FTS(values)); + + if (FTS_BLAZEGRAPH.equals(ftsMode)) { + return new FtsAdapterBlazegraph(this); } - else if (FTS_WIKIDATA.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_Wikidata_FTS(values)); + + if (FTS_FUSEKI.equals(ftsMode)) { + return new FtsAdapterFuseki(this); } - else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { - // For exact matching, we do not make use of regexes, so we keep this as a primary - // condition - unlike in withLabelContainingAnyOf or withLabelStartingWith - addPattern(PRIMARY, withLabelMatchingExactlyAnyOf_No_FTS(values)); + + if (FTS_VIRTUOSO.equals(ftsMode)) { + return new FtsAdapterVirtuoso(this); } - else { - throw new IllegalStateException( - "Unknown FTS mode: [" + kb.getFullTextSearchIri() + "]"); + + if (FTS_STARDOG.equals(ftsMode)) { + return new FtsAdapterStardog(this); } - addMatchTermProjections(projections); - labelImplicitlyRetrieved = true; + if (FTS_WIKIDATA.equals(ftsMode)) { + return new FtsAdapterWikidata(this); + } - return this; + if (FTS_NONE.equals(ftsMode) || ftsMode == null) { + return new FtsAdapterNoFts(this); + } + throw new IllegalStateException("Unknown FTS mode: [" + kb.getFullTextSearchIri() + "]"); } private IRI getFtsMode() @@ -833,161 +827,10 @@ private boolean isFtsLimited() return FTS_WIKIDATA.equals(ftsMode); } - private GraphPattern withLabelMatchingExactlyAnyOf_No_FTS(String[] aValues) - { - List values = new ArrayList<>(); - String language = kb.getDefaultLanguage(); - - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_noFTS(value); - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - if (language != null) { - values.add(literalOfLanguage(sanitizedValue, language)); - } - - values.add(literalOf(sanitizedValue)); - } - - return GraphPatterns.and(bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - new ValuesPattern(VAR_MATCH_TERM, values), - VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM)); - } - - private GraphPattern withLabelMatchingExactlyAnyOf_RDF4J_FTS(String[] aValues) - { - prefixes.add(PREFIX_LUCENE_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - // Strip single quotes and asterisks because they have special semantics - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(VAR_SUBJECT - .has(FTS_LUCENE, - bNode(LUCENE_QUERY, literalOf(sanitizedValue)).andHas(LUCENE_PROPERTY, - VAR_MATCH_TERM_PROPERTY)) - .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(equalsPattern(VAR_MATCH_TERM, value, kb))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingExactlyAnyOf_Fuseki_FTS(String[] aValues) - { - prefixes.add(PREFIX_FUSEKI_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - // We assume that the FTS is case insensitive and found that some FTSes (i.e. - // Fuseki) can have trouble matching if they get upper-case query when they - // internally lower-case# - if (caseInsensitive) { - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - sanitizedValue = sanitizedValue.toLowerCase(Locale.forLanguageTag(language)); - } - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, - VAR_MATCH_TERM_PROPERTY, sanitizedValue) // - .withLimit(getLimit()) // - .filter(equalsPattern(VAR_MATCH_TERM, value, kb))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingExactlyAnyOf_Stardog_FTS(String[] aValues) - { - prefixes.add(PREFIX_STARDOG_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, sanitizedValue) // - .withLimit(getLimit()) // - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(equalsPattern(VAR_MATCH_TERM, value, kb)))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingExactlyAnyOf_Virtuoso_FTS(String[] aValues) - { - // prefixes.add(PREFIX_VIRTUOSO_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, - literalOf("\"" + sanitizedValue + "\""))) - .filter(equalsPattern(VAR_MATCH_TERM, value, kb))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingExactlyAnyOf_Wikidata_FTS(String[] aValues) - { - // In our KB settings, the language can be unset, but the Wikidata entity search - // requires a preferred language. So we use English as the default. - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(equalsPattern(VAR_MATCH_TERM, value, kb)))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - @Override public SPARQLQueryBuilder withLabelMatchingAnyOf(String... aValues) { - String[] values = Arrays.stream(aValues) // + var values = Arrays.stream(aValues) // .map(SPARQLQueryBuilder::trimQueryString) // .filter(StringUtils::isNotBlank) // .toArray(String[]::new); @@ -997,36 +840,7 @@ public SPARQLQueryBuilder withLabelMatchingAnyOf(String... aValues) return this; } - IRI ftsMode = getFtsMode(); - - if (FTS_LUCENE.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingAnyOf_RDF4J_FTS(values)); - } - else if (FTS_FUSEKI.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingAnyOf_Fuseki_FTS(values)); - } - else if (FTS_VIRTUOSO.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingAnyOf_Virtuoso_FTS(values)); - } - else if (FTS_STARDOG.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingAnyOf_Stardog_FTS(values)); - } - else if (FTS_WIKIDATA.equals(ftsMode)) { - addPattern(PRIMARY, withLabelMatchingAnyOf_Wikidata_FTS(values)); - } - else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { - // The WikiData search service does not support properties. So we disable the use of the - // WikiData search service when looking for properties. But then, searching first by - // the label becomes very slow because withLabelMatchingAnyOf falls back to "containing" - // when no FTS is used. To avoid forcing the SPARQL server to perform a full scan - // of its database, we demote the label matching to a secondary condition, allowing the - // the matching by type (e.g. PRIMARY_RESTRICTIONS is-a property) to take precedence. - addPattern(SECONDARY, withLabelMatchingAnyOf_No_FTS(values)); - } - else { - throw new IllegalStateException( - "Unknown FTS mode: [" + kb.getFullTextSearchIri() + "]"); - } + getAdapter().withLabelMatchingAnyOf(aValues); addMatchTermProjections(projections); labelImplicitlyRetrieved = true; @@ -1034,168 +848,10 @@ else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { return this; } - private GraphPattern withLabelMatchingAnyOf_No_FTS(String[] aValues) - { - // Falling back to "contains" semantics if there is no FTS - return withLabelContainingAnyOf_No_FTS(aValues); - } - - private GraphPattern withLabelMatchingAnyOf_Stardog_FTS(String[] aValues) - { - prefixes.add(PREFIX_STARDOG_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - String fuzzyQuery = convertToFuzzyMatchingQuery(sanitizedValue); - - if (StringUtils.isBlank(sanitizedValue) || StringUtils.isBlank(fuzzyQuery)) { - continue; - } - - valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, fuzzyQuery) // - .withLimit(getLimit()) // - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingAnyOf_Wikidata_FTS(String[] aValues) - { - // In our KB settings, the language can be unset, but the Wikidata entity search - // requires a preferred language. So we use English as the default. - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingAnyOf_Virtuoso_FTS(String[] aValues) - { - // prefixes.add(PREFIX_VIRTUOSO_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - // If the query string entered by the user does not end with a space character, then - // we assume that the user may not yet have finished writing the word and add a - // wildcard - if (!value.endsWith(" ")) { - sanitizedValue = virtuosoStartsWithQuery(sanitizedValue); - } - - valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM).and( - VAR_MATCH_TERM.has(VIRTUOSO_QUERY, literalOf("\"" + sanitizedValue + "\"")))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingAnyOf_Fuseki_FTS(String[] aValues) - { - prefixes.add(PREFIX_FUSEKI_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - // We assume that the FTS is case insensitive and found that some FTSes (i.e. - // Fuseki) can have trouble matching if they get upper-case query when they - // internally lower-case# - if (caseInsensitive) { - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - sanitizedValue = sanitizedValue.toLowerCase(Locale.forLanguageTag(language)); - } - - String fuzzyQuery = convertToFuzzyMatchingQuery(sanitizedValue); - - if (isBlank(fuzzyQuery)) { - continue; - } - - valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, - VAR_MATCH_TERM_PROPERTY, fuzzyQuery).withLimit(getLimit())); - } - - return and(bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelMatchingAnyOf_RDF4J_FTS(String[] aValues) - { - prefixes.add(PREFIX_LUCENE_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - // Strip single quotes and asterisks because they have special semantics - String sanitizedValue = sanitizeQueryString_FTS(value); - - String fuzzyQuery = convertToFuzzyMatchingQuery(sanitizedValue); - - if (isBlank(sanitizedValue) || isBlank(fuzzyQuery)) { - continue; - } - - var labelFilterExpressions = new ArrayList>(); - labelFilterExpressions.add(Expressions.equals(str(var("label")), str(VAR_MATCH_TERM))); - labelFilterExpressions.add(matchKbLanguage(VAR_MATCH_TERM)); - - // If a KB item has multiple labels, we want to return only the ones which actually - // match the query term such that the user is not confused that the results contain - // items that don't match the query (even though they do through a label that is not - // returned). RDF4J only provides access to the matched term in a "highlighed" form - // where "" and "" match the search term. So we have to strip these markers - // out as part of the query. - valuePatterns.add(VAR_SUBJECT // - .has(FTS_LUCENE, bNode(LUCENE_QUERY, literalOf(fuzzyQuery)) // - .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY) - .andHas(LUCENE_SNIPPET, var("snippet"))) - .and(bind( - function(REPLACE, - function(REPLACE, var("snippet"), literalOf(""), - literalOf("")), - literalOf(""), literalOf("")), - var("label"))) - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM)) - .filter(and(labelFilterExpressions.toArray(Expression[]::new)))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - @Override public SPARQLQueryBuilder withLabelContainingAnyOf(String... aValues) { - String[] values = Arrays.stream(aValues) // + var values = Arrays.stream(aValues) // .map(SPARQLQueryBuilder::trimQueryString) // .filter(StringUtils::isNotBlank) // .toArray(String[]::new); @@ -1205,33 +861,7 @@ public SPARQLQueryBuilder withLabelContainingAnyOf(String... aValues) return this; } - IRI ftsMode = getFtsMode(); - - if (FTS_LUCENE.equals(ftsMode)) { - addPattern(PRIMARY, withLabelContainingAnyOf_RDF4J_FTS(values)); - } - else if (FTS_FUSEKI.equals(ftsMode)) { - addPattern(PRIMARY, withLabelContainingAnyOf_Fuseki_FTS(values)); - } - else if (FTS_VIRTUOSO.equals(ftsMode)) { - addPattern(PRIMARY, withLabelContainingAnyOf_Virtuoso_FTS(values)); - } - else if (FTS_STARDOG.equals(ftsMode)) { - addPattern(PRIMARY, withLabelContainingAnyOf_Stardog_FTS(values)); - } - else if (FTS_WIKIDATA.equals(ftsMode)) { - addPattern(PRIMARY, withLabelContainingAnyOf_Wikidata_FTS(values)); - } - else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { - // Label matching without FTS is slow, so we add this with low prio and hope that some - // other higher-prio condition exists which limites the number of candidates to a - // manageable level - addPattern(SECONDARY, withLabelContainingAnyOf_No_FTS(values)); - } - else { - throw new IllegalStateException( - "Unknown FTS mode: [" + kb.getFullTextSearchIri() + "]"); - } + getAdapter().withLabelContainingAnyOf(values); addMatchTermProjections(projections); labelImplicitlyRetrieved = true; @@ -1239,183 +869,17 @@ else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { return this; } - private GraphPattern withLabelContainingAnyOf_No_FTS(String... aValues) - { - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - if (StringUtils.isBlank(value)) { - continue; - } - - valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(containsPattern(VAR_MATCH_TERM, value))); - } - - return GraphPatterns.and(bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelContainingAnyOf_RDF4J_FTS(String[] aValues) - { - prefixes.add(PREFIX_LUCENE_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(VAR_SUBJECT - .has(FTS_LUCENE, - bNode(LUCENE_QUERY, literalOf(sanitizedValue + "*")) - .andHas(LUCENE_PROPERTY, VAR_MATCH_TERM_PROPERTY)) - .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(containsPattern(VAR_MATCH_TERM, value))); - } - - return GraphPatterns.and(bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelContainingAnyOf_Fuseki_FTS(String[] aValues) - { - prefixes.add(PREFIX_FUSEKI_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - // We assume that the FTS is case insensitive and found that some FTSes (i.e. - // Fuseki) can have trouble matching if they get upper-case query when they - // internally lower-case# - if (caseInsensitive) { - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - sanitizedValue = sanitizedValue.toLowerCase(Locale.forLanguageTag(language)); - } - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, - VAR_MATCH_TERM_PROPERTY, sanitizedValue) // - .withLimit(getLimit()) // - .filter(containsPattern(VAR_MATCH_TERM, value))); - } - - return GraphPatterns.and(bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelContainingAnyOf_Virtuoso_FTS(String[] aValues) - { - // prefixes.add(PREFIX_VIRTUOSO_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, - literalOf("\"" + sanitizedValue + "\""))) - .filter(containsPattern(VAR_MATCH_TERM, value))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelContainingAnyOf_Stardog_FTS(String[] aValues) - { - prefixes.add(PREFIX_STARDOG_SEARCH); - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new StardogEntitySearchService(VAR_MATCH_TERM, sanitizedValue) - .withLimit(getLimit()) // - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(containsPattern(VAR_MATCH_TERM, value)))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - - private GraphPattern withLabelContainingAnyOf_Wikidata_FTS(String[] aValues) - { - // In our KB settings, the language can be unset, but the Wikidata entity search - // requires a preferred language. So we use English as the default. - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - - List valuePatterns = new ArrayList<>(); - for (String value : aValues) { - String sanitizedValue = sanitizeQueryString_FTS(value); - - if (StringUtils.isBlank(sanitizedValue)) { - continue; - } - - valuePatterns.add(new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(containsPattern(VAR_MATCH_TERM, value)))); - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - union(valuePatterns.toArray(GraphPattern[]::new))); - } - @Override public SPARQLQueryBuilder withLabelStartingWith(String aPrefixQuery) { - String value = trimQueryString(aPrefixQuery); + var value = trimQueryString(aPrefixQuery); if (value == null || value.length() == 0) { returnEmptyResult = true; return this; } - IRI ftsMode = getFtsMode(); - - if (FTS_LUCENE.equals(ftsMode)) { - addPattern(PRIMARY, withLabelStartingWith_RDF4J_FTS(value)); - } - else if (FTS_FUSEKI.equals(ftsMode)) { - addPattern(PRIMARY, withLabelStartingWith_Fuseki_FTS(value)); - } - else if (FTS_VIRTUOSO.equals(ftsMode)) { - addPattern(PRIMARY, withLabelStartingWith_Virtuoso_FTS(value)); - } - else if (FTS_STARDOG.equals(ftsMode)) { - addPattern(PRIMARY, withLabelStartingWith_Stardog_FTS(value)); - } - else if (FTS_WIKIDATA.equals(ftsMode)) { - addPattern(PRIMARY, withLabelStartingWith_Wikidata_FTS(value)); - } - else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { - // Label matching without FTS is slow, so we add this with low prio and hope that some - // other higher-prio condition exists which limites the number of candidates to a - // manageable level - addPattern(SECONDARY, withLabelStartingWith_No_FTS(value)); - } - else { - throw new IllegalStateException( - "Unknown FTS mode: [" + kb.getFullTextSearchIri() + "]"); - } + getAdapter().withLabelStartingWith(value); addMatchTermProjections(projections); labelImplicitlyRetrieved = true; @@ -1423,203 +887,19 @@ else if (FTS_NONE.equals(ftsMode) || ftsMode == null) { return this; } - private GraphPattern withLabelStartingWith_No_FTS(String aPrefixQuery) - { - if (aPrefixQuery.isEmpty()) { - returnEmptyResult = true; - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))); - } - - private GraphPattern withLabelStartingWith_Stardog_FTS(String aPrefixQuery) - { - prefixes.add(PREFIX_STARDOG_SEARCH); - - // Strip single quotes and asterisks because they have special semantics - String sanitizedValue = sanitizeQueryString_FTS(aPrefixQuery); - - if (isBlank(sanitizedValue)) { - returnEmptyResult = true; - } - - String queryString = sanitizedValue.trim(); - - // If the query string entered by the user does not end with a space character, then - // we assume that the user may not yet have finished writing the word and add a - // wildcard - if (!aPrefixQuery.endsWith(" ")) { - queryString += "*"; - } - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - new StardogEntitySearchService(VAR_MATCH_TERM, queryString) // - .withLimit(getLimit()) // - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefixQuery)))); - } - - private GraphPattern withLabelStartingWith_Wikidata_FTS(String aPrefix) - { - // In our KB settings, the language can be unset, but the Wikidata entity search - // requires a preferred language. So we use English as the default. - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - - if (aPrefix.isEmpty()) { - returnEmptyResult = true; - } - - String sanitizedValue = sanitizeQueryString_FTS(aPrefix); - - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - new WikidataEntitySearchService(VAR_SUBJECT, sanitizedValue, language) - .and(VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefix)))); - } - - private GraphPattern withLabelStartingWith_Virtuoso_FTS(String aPrefixQuery) - { - // prefixes.add(PREFIX_VIRTUOSO_SEARCH); - - // Strip single quotes and asterisks because they have special semantics - String sanitizedQuery = sanitizeQueryString_FTS(aPrefixQuery); - - // If the query string entered by the user does not end with a space character, then - // we assume that the user may not yet have finished writing the word and add a - // wildcard - if (!aPrefixQuery.endsWith(" ")) { - sanitizedQuery = virtuosoStartsWithQuery(sanitizedQuery); - } - - // If the query string was reduced to nothing, then the query should always return an empty - // result. - if (sanitizedQuery.length() == 2) { - returnEmptyResult = true; - } - - // Locate all entries where the label contains the prefix (using the FTS) and then - // filter them by those which actually start with the prefix. - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), - VAR_SUBJECT.has(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .and(VAR_MATCH_TERM.has(VIRTUOSO_QUERY, - literalOf("\"" + sanitizedQuery + "\""))) - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))); - } - - private String virtuosoStartsWithQuery(String sanitizedQuery) - { - StringBuilder ftsQueryString = new StringBuilder(); - String[] queryTokens = sanitizedQuery.split(" "); - - for (int i = 0; i < queryTokens.length; i++) { - if (i > 0) { - ftsQueryString.append(" "); - } - - // Virtuoso requires that a token has at least 4 characters before it can be - // used with a wildcard. If the last token has less than 4 characters, we simply - // drop it to avoid the user hitting a point where the auto-suggesions suddenly - // are empty. If the token 4 or more, we add the wildcard. - if (i == (queryTokens.length - 1)) { - if (queryTokens[i].length() >= 4) { - ftsQueryString.append(queryTokens[i]); - ftsQueryString.append("*"); - } - } - else { - ftsQueryString.append(queryTokens[i]); - } - } - - return ftsQueryString.toString(); - } - - private GraphPattern withLabelStartingWith_RDF4J_FTS(String aPrefixQuery) - { - prefixes.add(PREFIX_LUCENE_SEARCH); - - // Strip single quotes and asterisks because they have special semantics - String sanitizedValue = sanitizeQueryString_FTS(aPrefixQuery); - - if (isBlank(sanitizedValue)) { - returnEmptyResult = true; - } - - String queryString = sanitizedValue.trim(); - - // If the query string entered by the user does not end with a space character, then - // we assume that the user may not yet have finished writing the word and add a - // wildcard - if (!aPrefixQuery.endsWith(" ")) { - queryString += "*"; - } - - // Locate all entries where the label contains the prefix (using the FTS) and then - // filter them by those which actually start with the prefix. - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - VAR_SUBJECT - .has(FTS_LUCENE, - bNode(LUCENE_QUERY, literalOf(queryString)).andHas(LUCENE_PROPERTY, - VAR_MATCH_TERM_PROPERTY)) - .andHas(VAR_MATCH_TERM_PROPERTY, VAR_MATCH_TERM) - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))); - } - - private GraphPattern withLabelStartingWith_Fuseki_FTS(String aPrefixQuery) - { - prefixes.add(PREFIX_FUSEKI_SEARCH); - - String queryString = aPrefixQuery.trim(); - - // We assume that the FTS is case insensitive and found that some FTSes (i.e. - // Fuseki) can have trouble matching if they get upper-case query when they - // internally lower-case# - if (caseInsensitive) { - String language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; - queryString = queryString.toLowerCase(Locale.forLanguageTag(language)); - } - - if (queryString.isEmpty()) { - returnEmptyResult = true; - } - - // If the query string entered by the user does not end with a space character, then - // we assume that the user may not yet have finished writing the word and add a - // wildcard - if (!aPrefixQuery.endsWith(" ")) { - queryString += "*"; - } - - // Locate all entries where the label contains the prefix (using the FTS) and then - // filter them by those which actually start with the prefix. - return and( // - bindMatchTermProperties(VAR_MATCH_TERM_PROPERTY), // - new FusekiFtsQuery(VAR_SUBJECT, VAR_SCORE, VAR_MATCH_TERM, VAR_MATCH_TERM_PROPERTY, - queryString) // - .withLimit(getLimit()) // - .filter(startsWithPattern(VAR_MATCH_TERM, aPrefixQuery))); - } - - private Expression startsWithPattern(Variable aVariable, String aPrefixQuery) + Expression startsWithPattern(Variable aVariable, String aPrefixQuery) { return matchString(STRSTARTS, aVariable, aPrefixQuery); } - private Expression containsPattern(Variable aVariable, String aSubstring) + Expression containsPattern(Variable aVariable, String aSubstring) { return matchString(CONTAINS, aVariable, aSubstring); } - private String asRegexp(String aValue) + private static String asRegexp(String aValue) { - String value = aValue; + var value = aValue; // Escape metacharacters // value = value.replaceAll("[{}()\\[\\].+*?^$\\\\|]", "\\\\\\\\$0"); // Replace metacharacters with a match for any single char (.+ would be too slow) @@ -1631,21 +911,22 @@ private String asRegexp(String aValue) return value; } - private Expression equalsPattern(Variable aVariable, String aValue, KnowledgeBase aKB) + Expression equalsPattern(Variable aVariable, String aValue, KnowledgeBase aKB) { - Operand variable = aVariable; + var variable = aVariable; - String regexFlags = ""; + var regexFlags = ""; if (caseInsensitive) { regexFlags += "i"; } // Match using REGEX to be resilient against extra whitespace // Match exactly - String value = "^" + asRegexp(aValue) + "$"; + var value = "^" + asRegexp(aValue) + "$"; - List> expressions = new ArrayList<>(); - expressions.add(function(REGEX, variable, literalOf(value), literalOf(regexFlags))); + var expressions = new ArrayList>(); + expressions.add( + function(REGEX, function(STR, variable), literalOf(value), literalOf(regexFlags))); expressions.add(matchKbLanguage(aVariable)); return and(expressions.toArray(Expression[]::new)); @@ -1655,7 +936,7 @@ private Expression matchString(SparqlFunction aFunction, Variable aVariable, { Operand variable = aVariable; - String regexFlags = ""; + var regexFlags = ""; if (caseInsensitive) { regexFlags += "i"; } @@ -1676,14 +957,15 @@ private Expression matchString(SparqlFunction aFunction, Variable aVariable, "Only STRSTARTS and CONTAINS are supported, but got [" + aFunction + "]"); } - List> expressions = new ArrayList<>(); - expressions.add(function(REGEX, variable, literalOf(value), literalOf(regexFlags))); + var expressions = new ArrayList>(); + expressions.add( + function(REGEX, function(STR, variable), literalOf(value), literalOf(regexFlags))); expressions.add(matchKbLanguage(aVariable)); return and(expressions.toArray(Expression[]::new)).parenthesize(); } - private Expression matchKbLanguage(Variable aVariable) + Expression matchKbLanguage(Variable aVariable) { String language = kb.getDefaultLanguage(); @@ -1873,15 +1155,27 @@ public SPARQLQueryOptionalElements retrieveDescription() // Retain only the first description projections.add(getDescriptionProjection()); - Iri descProperty = mode.getDescriptionProperty(kb); + var descProperty = mode.getDescriptionProperty(kb); retrieveOptionalWithLanguage(descProperty, VAR_DESC_CANDIDATE); return this; } + @Override + public SPARQLQueryOptionalElements retrieveDeprecation() + { + // Retain only the first deprecation statement + projections.add(getDeprecationProjection()); + + var deprecationProperty = mode.getDeprecationProperty(kb); + retrieveOptionalWithLanguage(deprecationProperty, VAR_DEPRECATION); + + return this; + } + private void retrieveOptionalWithLanguage(RdfPredicate aProperty, Variable aVariable) { - GraphPattern pattern = VAR_SUBJECT.has(aProperty, aVariable) // + var pattern = VAR_SUBJECT.has(aProperty, aVariable) // .filter(matchKbLanguage(aVariable)); // Virtuoso has trouble with multiple OPTIONAL clauses causing results which would @@ -1902,7 +1196,7 @@ public SPARQLQueryOptionalElements retrieveDomainAndRange() return this; } - private int getLimit() + int getLimit() { return limitOverride > 0 ? limitOverride : kb.getMaxResults(); } @@ -2101,28 +1395,30 @@ public Optional asHandle(RepositoryConnection aConnection, boolean aAl private List evaluateListQuery(TupleQuery tupleQuery, boolean aAll) throws QueryEvaluationException { - try (TupleQueryResult result = tupleQuery.evaluate()) { - List handles = new ArrayList<>(); + try (var result = tupleQuery.evaluate()) { + var handles = new ArrayList(); while (result.hasNext()) { - BindingSet bindings = result.next(); + var bindings = result.next(); if (bindings.size() == 0) { continue; } // LOG.trace("[{}] Bindings: {}", toHexString(hashCode()), bindings); - String id = bindings.getBinding(VAR_SUBJECT_NAME).getValue().stringValue(); + var id = bindings.getBinding(VAR_SUBJECT_NAME).getValue().stringValue(); if (!id.contains(":") || (!aAll && hasImplicitNamespace(kb, id))) { continue; } - KBHandle handle = new KBHandle(id); + var handle = new KBHandle(id); handle.setKB(kb); extractLabel(handle, bindings); extractDescription(handle, bindings); extractRange(handle, bindings); extractDomain(handle, bindings); + extractScore(handle, bindings); + extractDeprecation(handle, bindings); handles.add(handle); } @@ -2207,8 +1503,7 @@ else if (matchTerm != null) { private Optional extractLanguage(Binding aBinding) { - if (aBinding != null && aBinding.getValue() instanceof Literal) { - Literal literal = (Literal) aBinding.getValue(); + if (aBinding != null && aBinding.getValue() instanceof Literal literal) { return literal.getLanguage(); } @@ -2217,8 +1512,8 @@ private Optional extractLanguage(Binding aBinding) private void extractDescription(KBHandle aTargetHandle, BindingSet aSourceBindings) { - Binding description = aSourceBindings.getBinding(VAR_DESCRIPTION_NAME); - Binding descCandidate = aSourceBindings.getBinding(VAR_DESCRIPTION_CANDIDATE_NAME); + var description = aSourceBindings.getBinding(VAR_DESCRIPTION_NAME); + var descCandidate = aSourceBindings.getBinding(VAR_DESCRIPTION_CANDIDATE_NAME); if (description != null) { aTargetHandle.setDescription(description.getValue().stringValue()); } @@ -2229,7 +1524,7 @@ else if (descCandidate != null) { private void extractDomain(KBHandle aTargetHandle, BindingSet aSourceBindings) { - Binding domain = aSourceBindings.getBinding(VAR_DOMAIN_NAME); + var domain = aSourceBindings.getBinding(VAR_DOMAIN_NAME); if (domain != null) { aTargetHandle.setDomain(domain.getValue().stringValue()); } @@ -2237,12 +1532,39 @@ private void extractDomain(KBHandle aTargetHandle, BindingSet aSourceBindings) private void extractRange(KBHandle aTargetHandle, BindingSet aSourceBindings) { - Binding range = aSourceBindings.getBinding(VAR_RANGE_NAME); + var range = aSourceBindings.getBinding(VAR_RANGE_NAME); if (range != null) { aTargetHandle.setRange(range.getValue().stringValue()); } } + private void extractScore(KBHandle aTargetHandle, BindingSet aSourceBindings) + { + var score = aSourceBindings.getBinding(VAR_SCORE_NAME); + if (score != null) { + aTargetHandle.setScore(Double.valueOf(score.getValue().stringValue())); + } + } + + private void extractDeprecation(KBHandle aTargetHandle, BindingSet aSourceBindings) + { + var deprecation = aSourceBindings.getBinding(VAR_DEPRECATION_NAME); + if (deprecation != null) { + if (deprecation.getValue() instanceof Literal literal) { + try { + aTargetHandle.setDeprecated(literal.booleanValue()); + } + catch (IllegalArgumentException e) { + // Anything other than a falsy value is considered to be true + aTargetHandle.setDeprecated(true); + } + } + else { + aTargetHandle.setDeprecated(true); + } + } + } + /** * Removes leading and trailing space and single quote characters which could cause the query * string to escape its quotes in the SPARQL query. @@ -2251,7 +1573,7 @@ private void extractRange(KBHandle aTargetHandle, BindingSet aSourceBindings) * a query string * @return the stripped query string */ - public static String trimQueryString(String aQuery) + static String trimQueryString(String aQuery) { if (aQuery == null || aQuery.length() == 0) { return aQuery; @@ -2287,15 +1609,7 @@ public static String trimQueryString(String aQuery) return aQuery; } - public static String sanitizeQueryString_noFTS(String aQuery) - { - return aQuery - // character classes to replace with a simple space - .replaceAll("[\\p{Space}\\p{Cntrl}]+", " ") // - .trim(); - } - - public static String sanitizeQueryString_FTS(String aQuery) + static String sanitizeQueryString_FTS(String aQuery) { return aQuery // character classes to replace with a simple space @@ -2305,15 +1619,15 @@ public static String sanitizeQueryString_FTS(String aQuery) .replaceAll("[\\u00AD]", "").trim(); } - public static String convertToFuzzyMatchingQuery(String aQuery) + public static String convertToFuzzyMatchingQuery(String aQuery, String aOperator) { - StringJoiner joiner = new StringJoiner(" "); - String[] terms = aQuery.split("\\s"); - for (String term : terms) { + var joiner = new StringJoiner(" "); + var terms = aQuery.split("\\s"); + for (var term : terms) { // We only do the fuzzy search if there are few terms because if there are many terms, // the search becomes too slow if we do a fuzzy match for each of them. if (term.length() > 4 && terms.length <= 3) { - joiner.add(term + "~"); + joiner.add(term + aOperator); } // REC: excluding terms of 3 or less characters helps reducing the problem that a // mention of "Counties of Catherlagh" matches "Anne of Austria", but actually @@ -2326,6 +1640,13 @@ else if (term.length() >= 3) { return joiner.toString(); } + public static String toLowerCase(KnowledgeBase kb, String aValue) + { + var language = kb.getDefaultLanguage() != null ? kb.getDefaultLanguage() : "en"; + aValue = aValue.toLowerCase(Locale.forLanguageTag(language)); + return aValue; + } + @Override public boolean equals(final Object other) { @@ -2333,9 +1654,9 @@ public boolean equals(final Object other) return false; } - SPARQLQueryBuilder castOther = (SPARQLQueryBuilder) other; - String query = selectQuery().getQueryString(); - String otherQuery = castOther.selectQuery().getQueryString(); + var castOther = (SPARQLQueryBuilder) other; + var query = selectQuery().getQueryString(); + var otherQuery = castOther.selectQuery().getQueryString(); return query.equals(otherQuery); } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryOptionalElements.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryOptionalElements.java index c9ad5ee1314..acb180f5b63 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryOptionalElements.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryOptionalElements.java @@ -36,6 +36,8 @@ public interface SPARQLQueryOptionalElements SPARQLQueryOptionalElements retrieveDomainAndRange(); + SPARQLQueryOptionalElements retrieveDeprecation(); + SPARQLQueryOptionalElements limit(int aLimit); SPARQLQueryOptionalElements caseSensitive(); diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLVariables.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLVariables.java new file mode 100644 index 00000000000..b9c13150826 --- /dev/null +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLVariables.java @@ -0,0 +1,89 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.IriConstants.PREFIX_BLAZEGRAPH; +import static de.tudarmstadt.ukp.inception.kb.IriConstants.PREFIX_STARDOG; +import static de.tudarmstadt.ukp.inception.kb.IriConstants.PREFIX_VIRTUOSO; +import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.prefix; +import static org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.var; +import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri; + +import org.eclipse.rdf4j.model.vocabulary.OWL; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.PropertyPath; +import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.builder.PropertyPathBuilder; +import org.eclipse.rdf4j.sparqlbuilder.core.Prefix; +import org.eclipse.rdf4j.sparqlbuilder.core.Variable; +import org.eclipse.rdf4j.sparqlbuilder.rdf.Iri; + +public interface SPARQLVariables +{ + String VAR_SUBJECT_NAME = "subj"; + String VAR_PREDICATE_NAME = "pred"; + String VAR_OBJECT_NAME = "obj"; + String VAR_MATCH_TERM_PROPERTY_NAME = "pMatch"; + String VAR_PREF_LABEL_PROPERTY_NAME = "pPrefLabel"; + String VAR_PREF_LABEL_NAME = "l"; + String VAR_MATCH_TERM_NAME = "m"; + String VAR_SCORE_NAME = "sc"; + String VAR_DESCRIPTION_NAME = "d"; + String VAR_DESCRIPTION_CANDIDATE_NAME = "dc"; + String VAR_RANGE_NAME = "range"; + String VAR_DOMAIN_NAME = "domain"; + String VAR_DEPRECATION_NAME = "dp"; + + Variable VAR_SUBJECT = var(VAR_SUBJECT_NAME); + Variable VAR_SCORE = var(VAR_SCORE_NAME); + Variable VAR_PREDICATE = var(VAR_PREDICATE_NAME); + Variable VAR_OBJECT = var(VAR_OBJECT_NAME); + Variable VAR_RANGE = var(VAR_RANGE_NAME); + Variable VAR_DOMAIN = var(VAR_DOMAIN_NAME); + Variable VAR_PREF_LABEL = var(VAR_PREF_LABEL_NAME); + Variable VAR_MATCH_TERM = var(VAR_MATCH_TERM_NAME); + Variable VAR_PREF_LABEL_PROPERTY = var(VAR_PREF_LABEL_PROPERTY_NAME); + Variable VAR_MATCH_TERM_PROPERTY = var(VAR_MATCH_TERM_PROPERTY_NAME); + Variable VAR_DESCRIPTION = var(VAR_DESCRIPTION_NAME); + Variable VAR_DESC_CANDIDATE = var(VAR_DESCRIPTION_CANDIDATE_NAME); + Variable VAR_DEPRECATION = var(VAR_DEPRECATION_NAME); + + Prefix PREFIX_LUCENE_SEARCH = prefix("search", + iri("http://www.openrdf.org/contrib/lucenesail#")); + Iri LUCENE_QUERY = PREFIX_LUCENE_SEARCH.iri("query"); + Iri LUCENE_PROPERTY = PREFIX_LUCENE_SEARCH.iri("property"); + Iri LUCENE_SCORE = PREFIX_LUCENE_SEARCH.iri("score"); + Iri LUCENE_SNIPPET = PREFIX_LUCENE_SEARCH.iri("snippet"); + + Prefix PREFIX_FUSEKI_SEARCH = prefix("text", iri("http://jena.apache.org/text#")); + Iri FUSEKI_QUERY = PREFIX_FUSEKI_SEARCH.iri("query"); + + Prefix PREFIX_STARDOG_SEARCH = prefix("fts", iri(PREFIX_STARDOG)); + + Prefix PREFIX_BLAZEGRAPH_SEARCH = prefix("bds", iri(PREFIX_BLAZEGRAPH)); + + // Some versions of Virtuoso do not like it when we declare the bif prefix. + // Prefix PREFIX_VIRTUOSO_SEARCH = prefix("bif", iri(PREFIX_VIRTUOSO)); + // Iri VIRTUOSO_QUERY = PREFIX_VIRTUOSO_SEARCH.iri("contains"); + Iri VIRTUOSO_QUERY = iri(PREFIX_VIRTUOSO, "contains"); + + Iri OWL_INTERSECTIONOF = iri(OWL.INTERSECTIONOF.stringValue()); + Iri RDF_REST = iri(RDF.REST.stringValue()); + Iri RDF_FIRST = iri(RDF.FIRST.stringValue()); + PropertyPath OWL_INTERSECTIONOF_PATH = PropertyPathBuilder.of(OWL_INTERSECTIONOF).then(RDF_REST) + .zeroOrMore().then(RDF_FIRST).build(); +} diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogEntitySearchService.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogEntitySearchService.java index a1a8c35dc42..9ab121eefcb 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogEntitySearchService.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogEntitySearchService.java @@ -26,7 +26,6 @@ import org.eclipse.rdf4j.sparqlbuilder.core.Prefix; import org.eclipse.rdf4j.sparqlbuilder.core.Variable; import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern; -import org.eclipse.rdf4j.sparqlbuilder.graphpattern.TriplePattern; import org.eclipse.rdf4j.sparqlbuilder.rdf.Iri; @SuppressWarnings("unused") @@ -77,12 +76,12 @@ public StardogEntitySearchService withLimit(int aLimit) @Override public String getQueryString() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.append("SERVICE "); sb.append(FTS_TEXT_MATCH.getQueryString()); sb.append(" { \n"); - TriplePattern pattern = bNode() // + var pattern = bNode() // .has(FTS_QUERY, literalOf(query)) // .andHas(FTS_RESULT, result); diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseInfo.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseInfo.java index 2df76604c88..e31c85585e6 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseInfo.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseInfo.java @@ -37,7 +37,13 @@ public class KnowledgeBaseInfo private String authorName; @JsonProperty("website-url") - private String websiteURL; + private String websiteUrl; + + @JsonProperty("license-name") + private String licenseName; + + @JsonProperty("license-url") + private String licenseUrl; public String getDescription() { @@ -69,14 +75,34 @@ public void setAuthorName(String aAuthorName) authorName = aAuthorName; } - public String getWebsiteURL() + public String getWebsiteUrl() + { + return websiteUrl; + } + + public void setWebsiteUrl(String aWebsiteURL) + { + websiteUrl = aWebsiteURL; + } + + public String getLicenseName() + { + return licenseName; + } + + public void setLicenseName(String aLicenseName) + { + licenseName = aLicenseName; + } + + public String getLicenseUrl() { - return websiteURL; + return licenseUrl; } - public void setWebsiteURL(String aWebsiteURL) + public void setLicenseUrl(String aLicenseUrl) { - websiteURL = aWebsiteURL; + licenseUrl = aLicenseUrl; } @Override @@ -92,12 +118,15 @@ public boolean equals(Object o) return Objects.equals(description, that.description) && Objects.equals(hostInstitutionName, that.hostInstitutionName) && Objects.equals(authorName, that.authorName) - && Objects.equals(websiteURL, that.websiteURL); + && Objects.equals(websiteUrl, that.websiteUrl) + && Objects.equals(licenseName, that.licenseName) + && Objects.equals(licenseUrl, that.licenseUrl); } @Override public int hashCode() { - return Objects.hash(description, hostInstitutionName, authorName, websiteURL); + return Objects.hash(description, hostInstitutionName, authorName, websiteUrl, licenseName, + licenseUrl); } } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseMapping.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseMapping.java index 1ac017f9521..6438ce0e589 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseMapping.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseMapping.java @@ -19,6 +19,10 @@ import java.io.Serializable; +import org.eclipse.rdf4j.model.vocabulary.OWL; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -29,31 +33,34 @@ public class KnowledgeBaseMapping private static final long serialVersionUID = 8967034943386456692L; @JsonProperty("class") - private String classIri; + private String classIri = RDFS.CLASS.stringValue(); @JsonProperty("subclass-of") - private String subclassIri; + private String subclassIri = RDFS.SUBCLASSOF.stringValue(); @JsonProperty("instance-of") - private String typeIri; + private String typeIri = RDF.TYPE.stringValue(); @JsonProperty("subproperty-of") - private String subPropertyIri; + private String subPropertyIri = RDFS.SUBPROPERTYOF.stringValue(); @JsonProperty("description") - private String descriptionIri; + private String descriptionIri = RDFS.COMMENT.stringValue(); @JsonProperty("label") - private String labelIri; + private String labelIri = RDFS.LABEL.stringValue(); @JsonProperty("property-type") - private String propertyTypeIri; + private String propertyTypeIri = RDF.PROPERTY.stringValue(); @JsonProperty("property-label") - private String propertyLabelIri; + private String propertyLabelIri = RDFS.LABEL.stringValue(); @JsonProperty("property-description") - private String propertyDescriptionIri; + private String propertyDescriptionIri = RDFS.COMMENT.stringValue(); + + @JsonProperty("deprecation-property") + private String deprecationPropertyIri = OWL.DEPRECATED.stringValue(); @JsonCreator public KnowledgeBaseMapping(@JsonProperty("class") String aClassIri, @@ -64,7 +71,8 @@ public KnowledgeBaseMapping(@JsonProperty("class") String aClassIri, @JsonProperty("label") String aLabelIri, @JsonProperty("property-type") String aPropertyTypeIri, @JsonProperty("property-label") String aPropertyLabelIri, - @JsonProperty("property-description") String aPropertyDescriptionIri) + @JsonProperty("property-description") String aPropertyDescriptionIri, + @JsonProperty("deprecation-property") String aDeprecationPropertyIri) { classIri = aClassIri; @@ -76,6 +84,7 @@ public KnowledgeBaseMapping(@JsonProperty("class") String aClassIri, propertyTypeIri = aPropertyTypeIri; propertyLabelIri = aPropertyLabelIri; propertyDescriptionIri = aPropertyDescriptionIri; + deprecationPropertyIri = aDeprecationPropertyIri; } public KnowledgeBaseMapping() @@ -172,4 +181,14 @@ public void setPropertyDescriptionIri(String aPropertyDescriptionIri) { propertyDescriptionIri = aPropertyDescriptionIri; } + + public void setDeprecationPropertyIri(String aDeprecationPropertyIri) + { + deprecationPropertyIri = aDeprecationPropertyIri; + } + + public String getDeprecationPropertyIri() + { + return deprecationPropertyIri; + } } diff --git a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseProfile.java b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseProfile.java index b07edb01ab7..11f38f3fb89 100644 --- a/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseProfile.java +++ b/inception/inception-kb/src/main/java/de/tudarmstadt/ukp/inception/kb/yaml/KnowledgeBaseProfile.java @@ -17,13 +17,12 @@ */ package de.tudarmstadt.ukp.inception.kb.yaml; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyList; import java.io.IOException; import java.io.InputStreamReader; -import java.io.Reader; import java.io.Serializable; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,7 +46,7 @@ public class KnowledgeBaseProfile private String name; @JsonProperty("disabled") - private boolean disabled; + private boolean disabled = false; @JsonProperty("type") private RepositoryType type; @@ -56,7 +55,7 @@ public class KnowledgeBaseProfile private KnowledgeBaseAccess access; @JsonProperty("mapping") - private KnowledgeBaseMapping mapping; + private KnowledgeBaseMapping mapping = new KnowledgeBaseMapping(); @JsonProperty("root-concepts") private List rootConcepts; @@ -68,7 +67,7 @@ public class KnowledgeBaseProfile private KnowledgeBaseInfo info; @JsonProperty("reification") - private Reification reification; + private Reification reification = Reification.NONE; @JsonProperty("default-language") private String defaultLanguage; @@ -217,9 +216,9 @@ public void setDefaultDataset(String aDefaultDataset) public static Map readKnowledgeBaseProfiles() throws IOException { - try (Reader r = new InputStreamReader( + try (var r = new InputStreamReader( KnowledgeBaseProfile.class.getResourceAsStream(KNOWLEDGEBASE_PROFILES_YAML), - StandardCharsets.UTF_8)) { + UTF_8)) { ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); return mapper.readValue(r, new TypeReference>() { diff --git a/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/model/db-changelog.xml b/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/model/db-changelog.xml index 9d9502feb50..bf3cd2e9260 100644 --- a/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/model/db-changelog.xml +++ b/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/model/db-changelog.xml @@ -474,4 +474,20 @@ + + + + + + + + + + + + + + diff --git a/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/yaml/knowledgebase-profiles.yaml b/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/yaml/knowledgebase-profiles.yaml index 1ecee543af8..7a8371a58e6 100644 --- a/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/yaml/knowledgebase-profiles.yaml +++ b/inception/inception-kb/src/main/resources/de/tudarmstadt/ukp/inception/kb/yaml/knowledgebase-profiles.yaml @@ -23,7 +23,6 @@ babel_net: type: REMOTE default-language: en default-dataset: http://babelnet.org/rdf/ - reification: NONE access: access-url: http://babelnet.org/sparql/ full-text-search: bif:contains @@ -37,8 +36,9 @@ babel_net: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | BabelNet is a multilingual lexicalized semantic network and ontology that was automatically created by linking Wikipedia to WordNet. host-institution-name: Linguistic Computing Laboratory at Sapienza University of Rome @@ -49,7 +49,6 @@ db_pedia: name: DBpedia (German) type: REMOTE default-language: de - reification: NONE access: access-url: http://de.dbpedia.org/sparql full-text-search: bif:contains @@ -63,8 +62,9 @@ db_pedia: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | DBpedia is a crowd-sourced community effort to extract structured content from the information created in various Wikimedia projects. host-institution-name: Leipzig University, University of Mannheim, OpenLink Software @@ -77,7 +77,6 @@ wikidata: name: Wikidata (direct mapping) type: REMOTE default-language: en - reification: NONE access: access-url: https://query.wikidata.org/sparql full-text-search: https://www.mediawiki.org/ontology#API/search @@ -91,8 +90,9 @@ wikidata: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.wikidata.org/prop/direct/P1647 + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | Wikidata is a free and open knowledge base and acts as central storage for the structured data of its Wikimedia sister projects including Wikipedia, Wikivoyage, Wikisource, and others. host-institution-name: Wikimedia Foundation, Inc. @@ -105,7 +105,6 @@ yago: name: YAGO v4 type: REMOTE default-language: de - reification: NONE access: access-url: https://yago-knowledge.org/sparql/query # YAGO runs BlazeGraph these days and they seem not to have set up a full text index @@ -120,6 +119,7 @@ yago: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: description: YAGO is a semantic knowledge base, derived from Wikipedia WordNet and GeoNames. host-institution-name: Max-Planck-Institute Saarbrücken @@ -132,7 +132,6 @@ zbw-stw-economics: name: STW Thesaurus for Economics type: REMOTE default-language: en - reification: NONE access: access-url: http://zbw.eu/beta/sparql/stw/query full-text-search: text:query @@ -146,8 +145,9 @@ zbw-stw-economics: property-label: http://www.w3.org/2004/02/skos/core#prefLabel property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | Thesaurus that provides vocabulary on any economic subject. Almost 6,000 standardized subject headings and about 20,000 additional entry terms to support individual keywords. host-institution-name: ZBW - Leibniz Information Centre for Economics @@ -158,7 +158,6 @@ zbw-gnd: name: Integrated Authority File (GND) type: REMOTE default-language: en - reification: NONE access: access-url: http://zbw.eu/beta/sparql/gnd/query full-text-search: text:query @@ -172,8 +171,9 @@ zbw-gnd: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | Mapping Integrated Authority File (GND).The Integrated Authority File (GND) is a controlled keyword system, that is mainly used for indexing in German libraries. host-institution-name: ZBW - Leibniz Information Centre for Economics @@ -186,7 +186,6 @@ agrovoc: name: AGROVOC Thesaurus type: REMOTE default-language: en - reification: NONE access: access-url: https://agrovoc.fao.org/sparql full-text-search: text:query @@ -200,6 +199,7 @@ agrovoc: subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf property-label: http://www.w3.org/2004/02/skos/core#prefLabel property-description: http://www.w3.org/2000/01/rdf-schema#comment + deprecation-property: http://www.w3.org/2002/07/owl#deprecated additional-matching-properties: - http://www.w3.org/2004/02/skos/core#altLabel info: @@ -236,7 +236,6 @@ wine_ontology: name: Wine Ontology type: LOCAL default-language: en - reification: NONE access: access-url: classpath:de/tudarmstadt/ukp/inception/kb/pre-defined-knowledge-bases/wine.rdf full-text-search: http://www.openrdf.org/contrib/lucenesail#matches @@ -250,8 +249,9 @@ wine_ontology: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | An example OWL ontology. Derived from the DAML Wine ontology at http://ontolingua.stanford.edu/doc/chimaera/ontologies/wines.daml host-institution-name: "-" @@ -262,7 +262,6 @@ olia_penn.owl: name: OLiA - Penn Treebank tagset (morphosyntax) type: LOCAL default-language: en - reification: NONE access: access-url: http://purl.org/olia/penn.owl full-text-search: http://www.openrdf.org/contrib/lucenesail#matches @@ -276,10 +275,104 @@ olia_penn.owl: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: - description: > + description: | Ontologies of Linguistic Annotation (OLiA) host-institution-name: Applied Computational Linguistics (ACoLi) Lab at the Goethe University Frankfurt, Germany author-name: Christian Chiarcos and Maria Sukhareva website-url: http://www.acoli.informatik.uni-frankfurt.de/resources/olia/ - \ No newline at end of file + +iao: + name: Information Artifact Ontology (IAO) (v2022-11-07) + type: LOCAL + default-language: en + access: + access-url: https://raw.githubusercontent.com/information-artifact-ontology/IAO/v2022-11-07/iao.owl + full-text-search: http://www.openrdf.org/contrib/lucenesail#matches + mapping: + class: http://www.w3.org/2000/01/rdf-schema#Class + subclass-of: http://www.w3.org/2000/01/rdf-schema#subClassOf + instance-of: http://www.w3.org/1999/02/22-rdf-syntax-ns#type + label: http://www.w3.org/2000/01/rdf-schema#label + property-type: http://www.w3.org/1999/02/22-rdf-syntax-ns#Property + description: http://www.w3.org/2000/01/rdf-schema#comment + property-label: http://www.w3.org/2000/01/rdf-schema#label + property-description: http://www.w3.org/2000/01/rdf-schema#comment + subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated + info: + description: | + The Information Artifact Ontology (IAO) is a new ontology of information entities, originally driven by work by + the OBI digital entity and realizable information entity branch. + + For more information, please refer to the project's website. + website-url: https://github.com/information-artifact-ontology/IAO/ + +hpo: + name: Human Phenotype Ontology + type: LOCAL + default-language: en + access: + access-url: https://purl.obolibrary.org/obo/hp/hp-simple-non-classified.owl + full-text-search: http://www.openrdf.org/contrib/lucenesail#matches + mapping: + class: http://www.w3.org/2002/07/owl#Class + subclass-of: http://www.w3.org/2000/01/rdf-schema#subClassOf + instance-of: http://www.w3.org/1999/02/22-rdf-syntax-ns#type + description: http://purl.obolibrary.org/obo/IAO_0000115 + label: http://www.w3.org/2000/01/rdf-schema#label + property-type: http://www.w3.org/1999/02/22-rdf-syntax-ns#Property + subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + property-label: http://www.w3.org/2000/01/rdf-schema#label + property-description: http://www.w3.org/2000/01/rdf-schema#comment + deprecation-property: http://www.w3.org/2002/07/owl#deprecated + additional-matching-properties: + - http://www.geneontology.org/formats/oboInOwl#hasExactSynonym + info: + description: | + The Human Phenotype Ontology (HPO) project provides an ontology of medically relevant phenotypes, + disease-phenotype annotations, and the algorithms that operate on these. + + Find out more at [http://www.human-phenotype-ontology.org](http://www.human-phenotype-ontology.org). + host-institution-name: The Jackson Laboratory + website-url: https://hpo.jax.org/app/ + license-url: https://hpo.jax.org/app/license + +snomed-ct: + name: SNOMED Clinical Terms (manual steps required!) + type: LOCAL + default-language: en + access: + full-text-search: http://www.openrdf.org/contrib/lucenesail#matches + mapping: + class: http://www.w3.org/2002/07/owl#Class + subclass-of: http://www.w3.org/2000/01/rdf-schema#subClassOf + instance-of: http://www.w3.org/1999/02/22-rdf-syntax-ns#type + description: http://www.w3.org/2004/02/skos/core#definition + label: http://www.w3.org/2004/02/skos/core#prefLabel + property-type: http://www.w3.org/1999/02/22-rdf-syntax-ns#Property + subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + property-label: http://www.w3.org/2000/01/rdf-schema#label + property-description: http://www.w3.org/2000/01/rdf-schema#comment + deprecation-property: http://www.w3.org/2002/07/owl#deprecated + root-concepts: + - http://snomed.info/id/138875005 + additional-matching-properties: + - http://www.w3.org/2004/02/skos/core#altLabel + - http://www.w3.org/2000/01/rdf-schema#label + info: + description: | + SNOMED CT is a systematically organized collection of medical terms providing codes, terms, synonyms and + definitions used in clinical documentation and reporting. + + **Note: Manual steps are required.** Choosing this profile will **not automatically download** and install the resources. + + Here is what you need to do to obtain and load the actual SNOMED CT data: + + - Download SNOMED CT RD2 data files, e.g. from the [National Library of Medicine](https://www.nlm.nih.gov/healthit/snomedct/international.html) + - The RD2 package contains a data file in _OWL Functional Syntax_ wiht the ending `.owl` - rename the file to end in `.ofn` + - Import the renamed file into the knowledge base created from this profile (this may take a while) + host-institution-name: SNOMED International + website-url: https://www.snomed.org + license-url: https://www.snomed.org/get-snomed diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java index 373ef86e830..ed0fd5a861e 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/FullTextIndexUpgradeTest.java @@ -44,6 +44,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; @@ -103,7 +104,7 @@ void setup() project = new Project("test"); entityManager.persist(project); - repoProperties = new RepositoryProperties(); + repoProperties = new RepositoryPropertiesImpl(); kbProperties = new KnowledgeBasePropertiesImpl(); repoProperties.setPath(new File(WORK_DIR)); } diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseExporterTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseExporterTest.java index 35285386eca..028ea9b3a71 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseExporterTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseExporterTest.java @@ -31,7 +31,6 @@ import static org.mockito.Mockito.when; import java.io.File; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.zip.ZipFile; @@ -104,24 +103,22 @@ public void setUp() throws Exception public void thatExportingWorks() throws Exception { // Export the project - FullProjectExportRequest exportRequest = new FullProjectExportRequest(sourceProject, null, - false); - ProjectExportTaskMonitor monitor = new ProjectExportTaskMonitor(sourceProject, null, - "test"); - ExportedProject exportedProject = new ExportedProject(); + var exportRequest = new FullProjectExportRequest(sourceProject, null, false); + var monitor = new ProjectExportTaskMonitor(sourceProject, null, "test"); + var exportedProject = new ExportedProject(); sut.exportData(exportRequest, monitor, exportedProject, temporaryFolder); // Import the project again - ArgumentCaptor exportKbCaptor = ArgumentCaptor.forClass(KnowledgeBase.class); + var exportKbCaptor = ArgumentCaptor.forClass(KnowledgeBase.class); doNothing().when(kbService).registerKnowledgeBase(exportKbCaptor.capture(), any()); - ProjectImportRequest importRequest = new ProjectImportRequest(true); - ZipFile zipFile = mock(ZipFile.class); + var importRequest = new ProjectImportRequest(true); + var zipFile = mock(ZipFile.class); sut.importData(importRequest, targetProject, exportedProject, zipFile); // Check how many localKBs have been exported - List exportedKbs = exportKbCaptor.getAllValues(); + var exportedKbs = exportKbCaptor.getAllValues(); int numOfLocalKBs = exportedKbs.stream() .filter(kb -> kb.getType().equals(RepositoryType.LOCAL)) .collect(Collectors.toList()).size(); @@ -139,11 +136,9 @@ public void thatExportingWorks() throws Exception public void thatRemappingConceptFeaturesOnImportWorks() throws Exception { // Export the project - FullProjectExportRequest exportRequest = new FullProjectExportRequest(sourceProject, null, - false); - ProjectExportTaskMonitor monitor = new ProjectExportTaskMonitor(sourceProject, null, - "test"); - ExportedProject exportedProject = new ExportedProject(); + var exportRequest = new FullProjectExportRequest(sourceProject, null, false); + var monitor = new ProjectExportTaskMonitor(sourceProject, null, "test"); + var exportedProject = new ExportedProject(); sut.exportData(exportRequest, monitor, exportedProject, temporaryFolder); // Mock that the KB ID changes during import when registerKnowledgeBase is called @@ -158,13 +153,12 @@ public void thatRemappingConceptFeaturesOnImportWorks() throws Exception .thenReturn(features(targetProject)); // Capture remapped features - ArgumentCaptor importedAnnotationFeatureCaptor = ArgumentCaptor - .forClass(AnnotationFeature.class); + var importedAnnotationFeatureCaptor = ArgumentCaptor.forClass(AnnotationFeature.class); doNothing().when(schemaService).createFeature(importedAnnotationFeatureCaptor.capture()); // Import the project again - ProjectImportRequest importRequest = new ProjectImportRequest(true); - ZipFile zipFile = mock(ZipFile.class); + var importRequest = new ProjectImportRequest(true); + var zipFile = mock(ZipFile.class); sut.importData(importRequest, targetProject, exportedProject, zipFile); // Verify that features were actually processed @@ -182,38 +176,37 @@ public void thatRemappingConceptFeaturesOnImportWorks() throws Exception private List knowledgeBases() throws Exception { - KnowledgeBase kb1 = buildKnowledgeBase("kb1"); + var kb1 = buildKnowledgeBase("kb1"); kb1.setType(RepositoryType.LOCAL); kb1.setClassIri(WIKIDATA_CLASS.stringValue()); - KnowledgeBase kb2 = buildKnowledgeBase("kb2"); + var kb2 = buildKnowledgeBase("kb2"); kb2.setType(RepositoryType.REMOTE); kb2.setClassIri(OWL.CLASS.stringValue()); - KnowledgeBase kb3 = buildKnowledgeBase("kb3"); + var kb3 = buildKnowledgeBase("kb3"); kb3.setType(RepositoryType.REMOTE); kb3.setClassIri(RDFS.CLASS.stringValue()); - KnowledgeBase kb4 = buildKnowledgeBase("kb4"); + var kb4 = buildKnowledgeBase("kb4"); kb4.setType(RepositoryType.LOCAL); kb4.setClassIri(RDFS.CLASS.stringValue()); - return Arrays.asList(kb1, kb2, kb3); + return asList(kb1, kb2, kb3); } private List features(Project aProject) throws Exception { - AnnotationLayer layer1 = new AnnotationLayer("layer", "layer", WebAnnoConst.SPAN_TYPE, - aProject, false, TOKENS, NO_OVERLAP); + var layer1 = new AnnotationLayer("layer", "layer", WebAnnoConst.SPAN_TYPE, aProject, false, + TOKENS, NO_OVERLAP); - AnnotationFeature feat1 = new AnnotationFeature(1, layer1, "conceptFeature", "kb:conceptA"); - ConceptFeatureTraits traits1 = new ConceptFeatureTraits(); + var feat1 = new AnnotationFeature(1, layer1, "conceptFeature", "kb:conceptA"); + var traits1 = new ConceptFeatureTraits(); traits1.setRepositoryId("id-kb1"); feat1.setTraits(JSONUtil.toJsonString(traits1)); - AnnotationFeature feat2 = new AnnotationFeature(1, layer1, "conceptFeature", - "kb-multi:conceptA"); - ConceptFeatureTraits traits2 = new ConceptFeatureTraits(); + var feat2 = new AnnotationFeature(1, layer1, "conceptFeature", "kb-multi:conceptA"); + var traits2 = new ConceptFeatureTraits(); traits2.setRepositoryId("id-kb1"); feat2.setTraits(JSONUtil.toJsonString(traits1)); @@ -222,7 +215,7 @@ private List features(Project aProject) throws Exception private KnowledgeBase buildKnowledgeBase(String name) throws Exception { - KnowledgeBase kb = new KnowledgeBase(); + var kb = new KnowledgeBase(); kb.setRepositoryId("id-" + name); kb.setName(name); kb.setProject(sourceProject); @@ -234,6 +227,7 @@ private KnowledgeBase buildKnowledgeBase(String name) throws Exception kb.setPropertyLabelIri(RDFS.LABEL.stringValue()); kb.setPropertyDescriptionIri(RDFS.COMMENT.stringValue()); kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + kb.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); kb.setMaxResults(1000); kb.setRootConcepts(asList("http://www.ics.forth.gr/isl/CRMinf/I1_Argumentation", "http://www.ics.forth.gr/isl/CRMinf/I1_Argumentation")); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseProfileDeserializationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseProfileDeserializationTest.java index 2ed30cdd6d8..c4607c38e49 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseProfileDeserializationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseProfileDeserializationTest.java @@ -17,15 +17,14 @@ */ package de.tudarmstadt.ukp.inception.kb; +import static org.assertj.core.api.Assertions.assertThat; + import java.io.IOException; import java.io.InputStreamReader; -import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -46,13 +45,13 @@ public class KnowledgeBaseProfileDeserializationTest @Test public void checkThatDeserializationWorks() throws IOException { - String name = "Test KB"; - List rootConcepts = new ArrayList<>(); - RepositoryType type = RepositoryType.LOCAL; - Reification reification = Reification.WIKIDATA; - String defaultLanguage = "en"; + var name = "Test KB"; + var rootConcepts = new ArrayList(); + var type = RepositoryType.LOCAL; + var reification = Reification.WIKIDATA; + var defaultLanguage = "en"; - KnowledgeBaseProfile referenceProfile = new KnowledgeBaseProfile(); + var referenceProfile = new KnowledgeBaseProfile(); referenceProfile.setName(name); referenceProfile.setType(type); @@ -63,56 +62,52 @@ public void checkThatDeserializationWorks() throws IOException referenceProfile.setAccess(createReferenceAccess()); referenceProfile.setInfo(createReferenceInfo()); - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + var resolver = new PathMatchingResourcePatternResolver(); Map profiles; - try (Reader r = new InputStreamReader( + try (var r = new InputStreamReader( resolver.getResource(KNOWLEDGEBASE_TEST_PROFILES_YAML).getInputStream())) { - ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + var mapper = new ObjectMapper(new YAMLFactory()); profiles = mapper.readValue(r, new TypeReference>() { }); } - KnowledgeBaseProfile testProfile = profiles.get("test_profile"); - Assertions.assertThat(testProfile).usingRecursiveComparison().isEqualTo(referenceProfile); + var testProfile = profiles.get("test_profile"); + assertThat(testProfile).usingRecursiveComparison().isEqualTo(referenceProfile); } private KnowledgeBaseInfo createReferenceInfo() { - String description = "This is a knowledge base for testing the kb profiles"; - String host = "a host"; - String author = "INCEpTION team"; - String website = "https://inception-project.github.io/"; - KnowledgeBaseInfo referenceInfo = new KnowledgeBaseInfo(); - referenceInfo.setDescription(description); - referenceInfo.setAuthorName(author); - referenceInfo.setHostInstitutionName(host); - referenceInfo.setWebsiteURL(website); + var referenceInfo = new KnowledgeBaseInfo(); + referenceInfo.setDescription("This is a knowledge base for testing the kb profiles"); + referenceInfo.setAuthorName("INCEpTION team"); + referenceInfo.setHostInstitutionName("a host"); + referenceInfo.setWebsiteUrl("https://inception-project.github.io/"); return referenceInfo; } private KnowledgeBaseMapping createReferenceMapping() { - String classIri = "http://www.w3.org/2000/01/rdf-schema#Class"; - String subclassIri = "http://www.w3.org/2000/01/rdf-schema#subClassOf"; - String typeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; - String subPropertyIri = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf"; - String label = "http://www.w3.org/2000/01/rdf-schema#label"; - String propertyTypeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"; - String descriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; - String propertyLabelIri = "http://www.w3.org/2000/01/rdf-schema#label"; - String propertyDescriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; - KnowledgeBaseMapping referenceMapping = new KnowledgeBaseMapping(classIri, subclassIri, - typeIri, subPropertyIri, descriptionIri, label, propertyTypeIri, propertyLabelIri, - propertyDescriptionIri); + var classIri = "http://www.w3.org/2000/01/rdf-schema#Class"; + var subclassIri = "http://www.w3.org/2000/01/rdf-schema#subClassOf"; + var typeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; + var subPropertyIri = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf"; + var label = "http://www.w3.org/2000/01/rdf-schema#label"; + var propertyTypeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"; + var descriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; + var propertyLabelIri = "http://www.w3.org/2000/01/rdf-schema#label"; + var propertyDescriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; + var deprecationPropertyIri = "http://www.w3.org/2002/07/owl#deprecated"; + var referenceMapping = new KnowledgeBaseMapping(classIri, subclassIri, typeIri, + subPropertyIri, descriptionIri, label, propertyTypeIri, propertyLabelIri, + propertyDescriptionIri, deprecationPropertyIri); return referenceMapping; } private KnowledgeBaseAccess createReferenceAccess() { - String url = "http://someurl/sparql"; - String fullTextSearchIri = "http://www.openrdf.org/contrib/lucenesail#matches"; - KnowledgeBaseAccess referenceAccess = new KnowledgeBaseAccess(url, fullTextSearchIri); - return referenceAccess; + var url = "http://someurl/sparql"; + var fullTextSearchIri = "http://www.openrdf.org/contrib/lucenesail#matches"; + return new KnowledgeBaseAccess(url, fullTextSearchIri); } } diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplImportExportIntegrationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplImportExportIntegrationTest.java index b8bc3bb0253..15e28ba052f 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplImportExportIntegrationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplImportExportIntegrationTest.java @@ -45,6 +45,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBConcept; @@ -92,7 +93,7 @@ public static void setUpOnce() @BeforeEach public void setUp() { - RepositoryProperties repoProps = new RepositoryProperties(); + RepositoryProperties repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(temporaryFolder); KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); EntityManager entityManager = testEntityManager.getEntityManager(); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplIntegrationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplIntegrationTest.java index 3a101b2e9e6..9e59d924f4d 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplIntegrationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplIntegrationTest.java @@ -49,6 +49,7 @@ import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -61,8 +62,7 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import de.tudarmstadt.ukp.clarin.webanno.model.Project; -import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; -import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBConcept; import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; @@ -117,10 +117,10 @@ public static void setUpOnce() public void setUp(Reification reification) throws Exception { - RepositoryProperties repoProps = new RepositoryProperties(); + var repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(temporaryFolder); - KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); - EntityManager entityManager = testEntityManager.getEntityManager(); + var kbProperties = new KnowledgeBasePropertiesImpl(); + var entityManager = testEntityManager.getEntityManager(); testFixtures = new TestFixtures(testEntityManager); sut = new KnowledgeBaseServiceImpl(repoProps, kbProperties, entityManager); project = createProject(PROJECT_NAME); @@ -1999,6 +1999,24 @@ public void checkIfKBIsEnabledById_WithDisabledKBAndNonExistingId_ShouldReturnFa .isFalse(); } + @Test + public void importKnowledgeBase_OBO() throws Exception + { + setUp(Reification.NONE); + sut.registerKnowledgeBase(kb, sut.getNativeConfig()); + importKnowledgeBase("data/example1.obo"); + assertThat(sut.getIndexSize(kb)).isGreaterThan(30000); + } + + @Test + public void importKnowledgeBase_OBO_gzip() throws Exception + { + setUp(Reification.NONE); + sut.registerKnowledgeBase(kb, sut.getNativeConfig()); + importKnowledgeBase("data/example1.obo.gz"); + assertThat(sut.getIndexSize(kb)).isGreaterThan(30000); + } + // Helper private Project createProject(String name) { diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplQualifierIntegrationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplQualifierIntegrationTest.java index 329ea333110..6de2fdc29c2 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplQualifierIntegrationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplQualifierIntegrationTest.java @@ -42,6 +42,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBConcept; @@ -89,7 +90,7 @@ public class KnowledgeBaseServiceImplQualifierIntegrationTest @BeforeEach public void setUp() throws Exception { - RepositoryProperties repoProps = new RepositoryProperties(); + RepositoryProperties repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(temporaryFolder); KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); EntityManager entityManager = testEntityManager.getEntityManager(); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplWikiDataIntegrationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplWikiDataIntegrationTest.java index 873f80ba5ae..8208ed1ddd0 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplWikiDataIntegrationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceImplWikiDataIntegrationTest.java @@ -47,6 +47,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBConcept; @@ -104,7 +105,7 @@ private void setUp(Reification reification) throws Exception String wikidataAccessUrl = PROFILES.get("wikidata").getAccess().getAccessUrl(); assumeEndpointIsAvailable(wikidataAccessUrl); - RepositoryProperties repoProps = new RepositoryProperties(); + RepositoryProperties repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(tempDir); KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); EntityManager entityManager = testEntityManager.getEntityManager(); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceRemoteTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceRemoteTest.java index 7566b74c73a..0e6ea7ad9bb 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceRemoteTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseServiceRemoteTest.java @@ -55,6 +55,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; @@ -107,7 +108,7 @@ public void setUp(TestConfiguration aSutConfig) throws Exception KnowledgeBase kb = aSutConfig.getKnowledgeBase(); - RepositoryProperties repoProps = new RepositoryProperties(); + RepositoryProperties repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(repoPath); KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); EntityManager entityManager = testEntityManager.getEntityManager(); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseSubPropertyLabelTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseSubPropertyLabelTest.java index a9c82d938fe..6578dfb2d97 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseSubPropertyLabelTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnowledgeBaseSubPropertyLabelTest.java @@ -47,6 +47,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBaseProperties; import de.tudarmstadt.ukp.inception.kb.config.KnowledgeBasePropertiesImpl; import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; @@ -97,7 +98,7 @@ public static void setUpOnce() @BeforeEach public void setUp() { - RepositoryProperties repoProps = new RepositoryProperties(); + RepositoryProperties repoProps = new RepositoryPropertiesImpl(); repoProps.setPath(tempDir); KnowledgeBaseProperties kbProperties = new KnowledgeBasePropertiesImpl(); EntityManager entityManager = testEntityManager.getEntityManager(); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnwoledgeBaseSchemaProfileTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnwoledgeBaseSchemaProfileTest.java index 43349108c29..b57d99355f2 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnwoledgeBaseSchemaProfileTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/KnwoledgeBaseSchemaProfileTest.java @@ -25,30 +25,31 @@ import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseMapping; import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseProfile; -public class KnwoledgeBaseSchemaProfileTest +class KnwoledgeBaseSchemaProfileTest { @Test - public void checkKBProfileAndKBObject_ShouldReturnMatchingSchemaProfile() + void checkKBProfileAndKBObject_ShouldReturnMatchingSchemaProfile() { - String name = "Test KB"; - String classIri = "http://www.w3.org/2002/07/owl#Class"; - String subclassIri = "http://www.w3.org/2000/01/rdf-schema#subClassOf"; - String typeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; - String subPropertyIri = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf"; - String label = "http://www.w3.org/2000/01/rdf-schema#label"; - String propertyTypeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"; - String descriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; - String propertyLabelIri = "http://www.w3.org/2000/01/rdf-schema#label"; - String propertyDescriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; + var name = "Test KB"; + var classIri = "http://www.w3.org/2002/07/owl#Class"; + var subclassIri = "http://www.w3.org/2000/01/rdf-schema#subClassOf"; + var typeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; + var subPropertyIri = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf"; + var label = "http://www.w3.org/2000/01/rdf-schema#label"; + var propertyTypeIri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"; + var descriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; + var propertyLabelIri = "http://www.w3.org/2000/01/rdf-schema#label"; + var propertyDescriptionIri = "http://www.w3.org/2000/01/rdf-schema#comment"; + var deprecationPropertyIri = "http://www.w3.org/2002/07/owl#deprecated"; - KnowledgeBaseMapping testMapping = new KnowledgeBaseMapping(classIri, subclassIri, typeIri, - subPropertyIri, descriptionIri, label, propertyTypeIri, propertyLabelIri, - propertyDescriptionIri); - KnowledgeBaseProfile testProfile = new KnowledgeBaseProfile(); + var testMapping = new KnowledgeBaseMapping(classIri, subclassIri, typeIri, subPropertyIri, + descriptionIri, label, propertyTypeIri, propertyLabelIri, propertyDescriptionIri, + deprecationPropertyIri); + var testProfile = new KnowledgeBaseProfile(); testProfile.setName(name); testProfile.setMapping(testMapping); - KnowledgeBase testKb = new KnowledgeBase(); + var testKb = new KnowledgeBase(); testKb.applyMapping(testMapping); assertThat(SchemaProfile.checkSchemaProfile(testProfile)) diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphRepositoryTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphRepositoryTest.java new file mode 100644 index 00000000000..0a0aeabfe7e --- /dev/null +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/BlazegraphRepositoryTest.java @@ -0,0 +1,143 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_BLAZEGRAPH; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.buildSparqlRepository; +import static java.time.Duration.ofMinutes; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Path; +import java.util.List; + +import org.eclipse.rdf4j.repository.Repository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import de.tudarmstadt.ukp.inception.kb.RepositoryType; +import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; +import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.Scenario; + +@Testcontainers(disabledWithoutDocker = true) +public class BlazegraphRepositoryTest +{ + private static final int BLAZEGRAPH_PORT = 8080; + + private @TempDir Path temp; + private Repository repository; + private KnowledgeBase kb; + + @Container + private static final GenericContainer BLAZEGRAPH = new GenericContainer<>( + "islandora/blazegraph:3.1.3") // + .withExposedPorts(BLAZEGRAPH_PORT).withEnv("BLAZEGRAPH_HOST", "0.0.0.0") + .waitingFor(Wait.forHttp("/bigdata/sparql").forPort(BLAZEGRAPH_PORT) + .withStartupTimeout(ofMinutes(2))); + + @BeforeEach + public void setUp(TestInfo aTestInfo) throws Exception + { + String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); + System.out.printf("\n=== %s === %s =====================\n", methodName, + aTestInfo.getDisplayName()); + + suspendSslVerification(); + + assertThat(BLAZEGRAPH.isRunning()).isTrue(); + + forceFtsCreation(); + + kb = new KnowledgeBase(); + kb.setDefaultLanguage("en"); + kb.setType(RepositoryType.REMOTE); + kb.setFullTextSearchIri(FTS_BLAZEGRAPH.stringValue()); + kb.setMaxResults(100); + + SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping(kb); + + repository = buildSparqlRepository("http://" + BLAZEGRAPH.getHost() + ":" + + BLAZEGRAPH.getMappedPort(BLAZEGRAPH_PORT) + "/bigdata/sparql"); + + try (var conn = repository.getConnection()) { + conn.clear(); + } + } + + private void forceFtsCreation() throws URISyntaxException, IOException, InterruptedException + { + var httpClient = HttpClient.newBuilder().build(); + + var namespace = "kb"; // Default namespace + + var request = HttpRequest.newBuilder() + .uri(new URI("http://" + BLAZEGRAPH.getHost() + ":" + + BLAZEGRAPH.getMappedPort(BLAZEGRAPH_PORT) + "/bigdata/namespace/" + + namespace + "/textIndex?force-index-create=true")) + .POST(HttpRequest.BodyPublishers.noBody()).build(); + + var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + assertThat(response.statusCode()).isEqualTo(200); + } + + @AfterEach + public void tearDown() throws Exception + { + restoreSslVerification(); + } + + private static List tests() throws Exception + { + var exclusions = asList( // + // Not really clear why this one does not return any results. But since the FTS + // version of the test passes, I assume it is not critical - we will usually search + // with FTS enabled + "testWithLabelMatchingExactlyAnyOf_subproperty_noFTS"); + + return SPARQLQueryBuilderLocalTestScenarios.tests().stream() // + .filter(scenario -> !exclusions.contains(scenario.name)) // + .map(scenario -> Arguments.of(scenario.name, scenario)) // + .toList(); + } + + @ParameterizedTest(name = "{index}: test {0}") + @MethodSource("tests") + public void runTests(String aScenarioName, Scenario aScenario) throws Exception + { + aScenario.implementation.accept(repository, kb); + } +} diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FusekiRepositoryTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FusekiRepositoryTest.java index c94a87fe6b2..ebc3e187095 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FusekiRepositoryTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/FusekiRepositoryTest.java @@ -20,7 +20,7 @@ import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_FUSEKI; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.buildSparqlRepository; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.buildSparqlRepository; import static java.util.Arrays.asList; import java.io.IOException; @@ -52,7 +52,7 @@ import de.tudarmstadt.ukp.inception.kb.RepositoryType; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; -import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.Scenario; +import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.Scenario; @Disabled("Tests do not seem to run with Jakarta atm - needs investigation") public class FusekiRepositoryTest @@ -83,7 +83,7 @@ public void setUp(TestInfo aTestInfo) throws Exception kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); kb.setMaxResults(100); - SPARQLQueryBuilderTest.initRdfsMapping(kb); + SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping(kb); repository = buildSparqlRepository( "http://localhost:" + fusekiServer.getPort() + "/fuseki"); @@ -111,7 +111,7 @@ private static List tests() throws Exception // This test returns one match term less than in the RDF4J case - not clear why "thatMatchingAgainstAdditionalSearchPropertiesWorks2"); - return SPARQLQueryBuilderTest.tests().stream() // + return SPARQLQueryBuilderLocalTestScenarios.tests().stream() // .filter(scenario -> !exclusions.contains(scenario.name)) .map(scenario -> Arguments.of(scenario.name, scenario)) .collect(Collectors.toList()); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Rdf4JRepositoryTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Rdf4JRepositoryTest.java index 44669376229..5474aa68cf1 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Rdf4JRepositoryTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/Rdf4JRepositoryTest.java @@ -20,9 +20,9 @@ import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_LUCENE; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.DATA_ADDITIONAL_SEARCH_PROPERTIES_2; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.TURTLE_PREFIX; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.importDataFromString; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.DATA_ADDITIONAL_SEARCH_PROPERTIES_2; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.TURTLE_PREFIX; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.importDataFromString; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.contentOf; import static org.eclipse.rdf4j.rio.RDFFormat.TURTLE; @@ -50,7 +50,7 @@ import de.tudarmstadt.ukp.inception.kb.RepositoryType; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; -import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.Scenario; +import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.Scenario; public class Rdf4JRepositoryTest { @@ -81,7 +81,7 @@ public void setUp(TestInfo aTestInfo) kb.setFullTextSearchIri(FTS_LUCENE.stringValue()); kb.setMaxResults(100); - SPARQLQueryBuilderTest.initRdfsMapping(kb); + SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping(kb); try (RepositoryConnection conn = repository.getConnection()) { conn.clear(); @@ -100,7 +100,7 @@ private static List tests() throws Exception { var exclusions = asList(); - return SPARQLQueryBuilderTest.tests().stream() // + return SPARQLQueryBuilderLocalTestScenarios.tests().stream() // .filter(scenario -> !exclusions.contains(scenario.name)) .map(scenario -> Arguments.of(scenario.name, scenario)) .collect(Collectors.toList()); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/RepositoryTestSuite.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/RepositoryTestSuite.java index f0bb51af3b3..82cbd67e68b 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/RepositoryTestSuite.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/RepositoryTestSuite.java @@ -22,10 +22,11 @@ @Suite @SelectClasses({ // + BlazegraphRepositoryTest.class, // FusekiRepositoryTest.class, // Rdf4JRepositoryTest.class, // - StardogRepositoryTest.class // -}) + StardogRepositoryTest.class, // + VirtuosoRepositoryTest.class }) public class RepositoryTestSuite { diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderGenericTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderGenericTest.java index 42664f3a96d..ac0c845f4ef 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderGenericTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderGenericTest.java @@ -31,11 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -44,22 +40,21 @@ import org.junit.jupiter.params.provider.MethodSource; import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; -import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseProfile; @Tag("slow") public class SPARQLQueryBuilderGenericTest { // YAGO seems to have problem atm 29-04-2023 - private static final List SKIPPED_PROFILES = asList("babel_net", "yago"); + private static final List SKIPPED_PROFILES = asList("babel_net", "yago", "hpo", + "snomed-ct"); public static List data() throws Exception { - Map profiles = KnowledgeBaseProfile - .readKnowledgeBaseProfiles(); + var profiles = KnowledgeBaseProfile.readKnowledgeBaseProfiles(); - List dataList = new ArrayList<>(); - for (Entry entry : profiles.entrySet()) { + var dataList = new ArrayList(); + for (var entry : profiles.entrySet()) { if (SKIPPED_PROFILES.contains(entry.getKey())) { continue; } @@ -73,10 +68,10 @@ public static List data() throws Exception @MethodSource("data") public void thatRootConceptsCanBeRetrieved(KnowledgeBaseProfile aProfile) throws IOException { - KnowledgeBase kb = buildKnowledgeBase(aProfile); - Repository repo = buildRepository(aProfile); + var kb = buildKnowledgeBase(aProfile); + var repo = buildRepository(aProfile); - List roots = asHandles(repo, SPARQLQueryBuilder.forClasses(kb).roots()); + var roots = asHandles(repo, SPARQLQueryBuilder.forClasses(kb).roots()); assertThat(roots).isNotEmpty(); } @@ -98,11 +93,11 @@ public void tearDown() public void thatChildrenOfRootConceptHaveRootConceptAsParent(KnowledgeBaseProfile aProfile) throws IOException { - KnowledgeBase kb = buildKnowledgeBase(aProfile); - Repository repo = buildRepository(aProfile); + var kb = buildKnowledgeBase(aProfile); + var repo = buildRepository(aProfile); - List roots = asHandles(repo, SPARQLQueryBuilder.forClasses(kb).roots().limit(3)); - Set rootIdentifiers = roots.stream().map(KBHandle::getIdentifier).collect(toSet()); + var roots = asHandles(repo, SPARQLQueryBuilder.forClasses(kb).roots().limit(3)); + var rootIdentifiers = roots.stream().map(KBHandle::getIdentifier).collect(toSet()); assertThat(roots).extracting(KBHandle::getIdentifier).allMatch(_root -> { try (RepositoryConnection conn = repo.getConnection()) { @@ -130,11 +125,11 @@ public void thatChildrenOfRootConceptHaveRootConceptAsParent(KnowledgeBaseProfil @MethodSource("data") public void thatRegexMetaCharactersAreSafe(KnowledgeBaseProfile aProfile) throws IOException { - KnowledgeBase kb = buildKnowledgeBase(aProfile); - Repository repo = buildRepository(aProfile); + var kb = buildKnowledgeBase(aProfile); + var repo = buildRepository(aProfile); - try (RepositoryConnection conn = repo.getConnection()) { - SPARQLQueryOptionalElements builder = SPARQLQueryBuilder.forItems(kb) + try (var conn = repo.getConnection()) { + var builder = SPARQLQueryBuilder.forItems(kb) .withLabelMatchingExactlyAnyOf(".[]*+{}()lala").limit(3); System.out.printf("Query : %n"); @@ -153,15 +148,15 @@ public void thatRegexMetaCharactersAreSafe(KnowledgeBaseProfile aProfile) throws public void thatLineBreaksAndWhitespaceAreSafe_noFts(KnowledgeBaseProfile aProfile) throws IOException { - KnowledgeBase kb = buildKnowledgeBase(aProfile); - Repository repo = buildRepository(aProfile); + var kb = buildKnowledgeBase(aProfile); + var repo = buildRepository(aProfile); - String originalFtsIri = kb.getFullTextSearchIri(); + var originalFtsIri = kb.getFullTextSearchIri(); - try (RepositoryConnection conn = repo.getConnection()) { + try (var conn = repo.getConnection()) { kb.setFullTextSearchIri(FTS_NONE.stringValue()); - SPARQLQueryOptionalElements builder = SPARQLQueryBuilder // + var builder = SPARQLQueryBuilder // .forItems(kb) // .withLabelMatchingExactlyAnyOf("Lord\n\r\tLady") // .limit(3); diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java new file mode 100644 index 00000000000..4ed10dba63a --- /dev/null +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderLocalTestScenarios.java @@ -0,0 +1,1401 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClientBuilder; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.asHandles; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.exists; +import static de.tudarmstadt.ukp.inception.kb.util.TestFixtures.isReachable; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.eclipse.rdf4j.rio.RDFFormat.RDFXML; +import static org.eclipse.rdf4j.rio.RDFFormat.TURTLE; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.function.FailableBiConsumer; +import org.apache.commons.lang3.tuple.Pair; +import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; +import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.OWL; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.model.vocabulary.SKOS; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sparql.SPARQLRepository; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.Rio; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; + +import de.tudarmstadt.ukp.inception.kb.RepositoryType; +import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; +import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; + +public class SPARQLQueryBuilderLocalTestScenarios +{ + static final String TURTLE_PREFIX = String.join("\n", // + "@base .", // + "@prefix rdf: .", // + "@prefix rdfs: .", // + "@prefix so: .", // + "@prefix skos: .", // + "@prefix owl: ."); + + static final String DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE = contentOf( + new File("src/test/resources/turtle/data_labels_and_descriptions_with_language.ttl"), + UTF_8); + + static final String DATA_LABELS_WITHOUT_LANGUAGE = String.join("\n", // + "<#green-goblin>", // + " rdfs:label 'Green Goblin' .", // + "", // + "<#lucky-green>", // + " rdfs:label 'Lucky Green' .", // + "", // + "<#red-goblin>", // + " rdfs:label 'Red Goblin' ."); + + static final String DATA_MULTIPLE_LABELS = String.join("\n", // + "<#example>", // + " rdfs:label 'specimen' ;", // + " rdfs:label 'sample' ;", // + " rdfs:label 'instance' ;", // + " rdfs:label 'case' ."); + + static final String DATA_ADDITIONAL_SEARCH_PROPERTIES = contentOf( + new File("src/test/resources/turtle/data_additional_search_properties.ttl"), UTF_8); + + static final String DATA_ADDITIONAL_SEARCH_PROPERTIES_2 = contentOf( + new File("src/test/resources/turtle/data_additional_search_properties_2.ttl"), UTF_8); + + static final String LABEL_SUBPROPERTY = String.join("\n", // + "<#sublabel>", // + " rdfs:subPropertyOf rdfs:label .", // + "", // + "<#green-goblin>", // + " <#sublabel> 'Green Goblin' ."); + + /** + * This dataset contains a hierarchy of classes and instances with a naming scheme. There is an + * implicit and an explicit root class. All classes have "class" in their name. Subclasses start + * with "subclass" and then a number. Instances start with the number of the class to which they + * belong followed by a number. + */ + static final String DATA_CLASS_RDFS_HIERARCHY = String.join("\n", // + "<#explicitRoot>", // + " rdf:type rdfs:Class .", // + "<#subclass1>", // + " rdf:type rdfs:Class ;", // + " rdfs:subClassOf <#explicitRoot> .", // + "<#subclass1-1>", // + " rdfs:subClassOf <#subclass1> .", // + "<#subclass1-1-1>", // + " rdfs:subClassOf <#subclass1-1> .", // + "<#subclass2>", // + " rdfs:subClassOf <#explicitRoot> .", // + "<#subclass3>", // + " rdfs:subClassOf <#implicitRoot> .", // + "<#0-instance-1>", // + " rdf:type <#root> .", // + "<#1-instance-1>", // + " rdf:type <#subclass1> .", // + "<#2-instance-2>", // + " rdf:type <#subclass2> .", // + "<#3-instance-3>", // + " rdf:type <#subclass3> .", // + "<#1-1-1-instance-4>", // + " rdf:type <#subclass1-1-1> ."); + + /** + * This dataset contains properties, some in a hierarchical relationship. There is again a + * naming scheme: all properties have "property" in their name. Subproperties start with + * "subproperty" and then a number. The dataset also contains some non-properties to be able to + * ensure that queries limited to properties do not return non-properties. + */ + static final String DATA_PROPERTIES = String.join("\n", // + "<#explicitRoot>", // + " rdf:type rdfs:Class .", // + "<#property-1>", // + " rdf:type rdf:Property ;", // + " skos:prefLabel 'Property 1' ;", // + " so:description 'Property One' ;", // + " rdfs:domain <#explicitRoot> ;", // + " rdfs:range xsd:string .", // + "<#property-2>", // + " rdf:type rdf:Property ;", // + " skos:prefLabel 'Property 2' ;", // + " so:description 'Property Two' ;", // + " rdfs:domain <#subclass1> ;", // + " rdfs:range xsd:Integer .", // + "<#property-3>", // + " rdf:type rdf:Property ;", // + " skos:prefLabel 'Property 3' ;", // + " so:description 'Property Three' .", // + "<#subproperty-1-1>", // + " rdfs:subPropertyOf <#property-1> ;", // + " skos:prefLabel 'Subproperty 1-1' ;", // + " so:description 'Property One-One' .", // + "<#subproperty-1-1-1>", // + " rdfs:subPropertyOf <#subproperty-1-1> ;", // + " skos:prefLabel 'Subproperty 1-1-1' ;", // + " so:description 'Property One-One-One' .", // + "<#subclass1>", // + " rdf:type rdfs:Class ;", // + " rdfs:subClassOf <#explicitRoot> ;", // + " <#implicit-property-1> 'value1' ."); + + static final String DATA_DEPRECATED = """ + <#class-1> + rdf:type rdfs:Class ; + rdfs:label 'Class 1' ; + owl:deprecated true . + <#class-2> + rdf:type rdfs:Class ; + rdfs:label 'Class 2' ; + owl:deprecated false . + <#class-3> + rdf:type rdfs:Class ; + rdfs:label 'Class 3' ; + owl:deprecated 'lala' . + <#class-4> + rdf:type rdfs:Class ; + rdfs:label 'Class 4' ; + owl:deprecated 0 . + <#class-5> + rdf:type rdfs:Class ; + rdfs:label 'Class 5' ; + owl:deprecated 1 . + <#instance-1> + rdf:type <#class-1> ; + rdfs:label 'Instance 1' ; + owl:deprecated true . + <#property-1> + rdf:type rdf:Property ; + rdfs:label 'Property 1' ; + owl:deprecated true . + """; + + private KnowledgeBase kb; + + @BeforeEach + public void setUp() + { + suspendSslVerification(); + + kb = new KnowledgeBase(); + kb.setDefaultLanguage("en"); + kb.setType(RepositoryType.LOCAL); + kb.setFullTextSearchIri(null); + kb.setMaxResults(100); + + initRdfsMapping(kb); + } + + @BeforeEach + public void testWatcher(TestInfo aTestInfo) + { + String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); + System.out.printf("\n=== %s === %s =====================\n", methodName, + aTestInfo.getDisplayName()); + + suspendSslVerification(); + } + + @AfterEach + public void tearDown() + { + restoreSslVerification(); + } + + static List tests() throws Exception + { + return asList( // + new Scenario("testWithLabelStartingWith_withLanguage_FTS_1", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withLanguage_FTS_1), + new Scenario("testWithLabelStartingWith_withLanguage_FTS_2", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withLanguage_FTS_2), + new Scenario("testWithLabelStartingWith_withLanguage_FTS_3", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withLanguage_FTS_3), + new Scenario("testWithLabelStartingWith_withLanguage_FTS_4", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withLanguage_FTS_4), + new Scenario("testWithLabelStartingWith_withLanguage_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withLanguage_noFTS), + new Scenario("testWithLabelContainingAnyOf_pets_ttl_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelContainingAnyOf_pets_ttl_noFTS), + new Scenario("thatRootsCanBeRetrieved_ontolex", + SPARQLQueryBuilderLocalTestScenarios::thatRootsCanBeRetrieved_ontolex), + new Scenario("testWithLabelContainingAnyOf_withLanguage_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelContainingAnyOf_withLanguage_noFTS), + new Scenario("testWithLabelContainingAnyOf_withLanguage", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelContainingAnyOf_withLanguage), + new Scenario("testWithLabelMatchingAnyOf_withLanguage", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingAnyOf_withLanguage), + new Scenario("testWithLabelMatchingAnyOf_withLanguage_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingAnyOf_withLanguage_noFTS), + new Scenario("testWithLabelStartingWith_withoutLanguage", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withoutLanguage), + new Scenario("testWithLabelStartingWith_withoutLanguage_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_withoutLanguage_noFTS), + new Scenario("testWithLabelMatchingExactlyAnyOf_subproperty", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingExactlyAnyOf_subproperty), + new Scenario("testWithLabelMatchingExactlyAnyOf_subproperty_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingExactlyAnyOf_subproperty_noFTS), + new Scenario("testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS), + new Scenario("testWithLabelMatchingExactlyAnyOf_withLanguage", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelMatchingExactlyAnyOf_withLanguage), + new Scenario("thatExistsReturnsTrueWhenDataQueriedForExists", + SPARQLQueryBuilderLocalTestScenarios::thatExistsReturnsTrueWhenDataQueriedForExists), + new Scenario("thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved", + SPARQLQueryBuilderLocalTestScenarios::thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved), + new Scenario("thatLabelsAndDescriptionsWithLanguageArePreferred", + SPARQLQueryBuilderLocalTestScenarios::thatLabelsAndDescriptionsWithLanguageArePreferred), + new Scenario("thatSearchOverMultipleLabelsWorks", + SPARQLQueryBuilderLocalTestScenarios::thatSearchOverMultipleLabelsWorks), + new Scenario("thatMatchingAgainstAdditionalSearchPropertiesWorks", + SPARQLQueryBuilderLocalTestScenarios::thatMatchingAgainstAdditionalSearchPropertiesWorks), + new Scenario("thatMatchingAgainstAdditionalSearchPropertiesWorks2", + SPARQLQueryBuilderLocalTestScenarios::thatMatchingAgainstAdditionalSearchPropertiesWorks2), + new Scenario("thatExistsReturnsFalseWhenDataQueriedForDoesNotExist", + SPARQLQueryBuilderLocalTestScenarios::thatExistsReturnsFalseWhenDataQueriedForDoesNotExist), + new Scenario("thatExplicitClassCanBeRetrievedByItsIdentifier", + SPARQLQueryBuilderLocalTestScenarios::thatExplicitClassCanBeRetrievedByItsIdentifier), + new Scenario("thatImplicitClassCanBeRetrievedByItsIdentifier", + SPARQLQueryBuilderLocalTestScenarios::thatImplicitClassCanBeRetrievedByItsIdentifier), + new Scenario("thatNonClassCannotBeRetrievedByItsIdentifier", + SPARQLQueryBuilderLocalTestScenarios::thatNonClassCannotBeRetrievedByItsIdentifier), + new Scenario("thatCanRetrieveItemInfoForIdentifier", + SPARQLQueryBuilderLocalTestScenarios::thatCanRetrieveItemInfoForIdentifier), + new Scenario("thatAllPropertiesCanBeRetrieved", + SPARQLQueryBuilderLocalTestScenarios::thatAllPropertiesCanBeRetrieved), + new Scenario("thatDeprecationStatusCanBeRetrieved", + SPARQLQueryBuilderLocalTestScenarios::thatDeprecationStatusCanBeRetrieved), + new Scenario("thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), + new Scenario("thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), + new Scenario("thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults), + new Scenario("thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults), + new Scenario("thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults), + new Scenario("thatNonRootClassCanBeUsedAsExplicitRootClass", + SPARQLQueryBuilderLocalTestScenarios::thatNonRootClassCanBeUsedAsExplicitRootClass), + new Scenario("thatQueryLimitedToClassesDoesNotReturnInstances", + SPARQLQueryBuilderLocalTestScenarios::thatQueryLimitedToClassesDoesNotReturnInstances), + new Scenario("thatQueryLimitedToInstancesDoesNotReturnClasses", + SPARQLQueryBuilderLocalTestScenarios::thatQueryLimitedToInstancesDoesNotReturnClasses), + new Scenario("thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults), + new Scenario("thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults), + new Scenario("thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), + new Scenario("thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), + new Scenario("thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults), + new Scenario("thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults), + new Scenario("thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), + new Scenario("thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), + new Scenario("thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), + new Scenario("thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", + SPARQLQueryBuilderLocalTestScenarios::thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), + new Scenario("testWithLabelStartingWith_OLIA", + SPARQLQueryBuilderLocalTestScenarios::testWithLabelStartingWith_OLIA) + + ); + } + + static class Scenario + { + final String name; + final FailableBiConsumer implementation; + + public Scenario(String aName, + FailableBiConsumer aImplementation) + { + name = aName; + implementation = aImplementation; + } + } + + static Repository buildSparqlRepository(String aUrl) + { + var repo = new SPARQLRepository(aUrl); + repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build()); + repo.init(); + return repo; + } + + static Repository buildSparqlRepository(String aQueryUrl, String aUpdateUrl) + { + var repo = new SPARQLRepository(aQueryUrl, aUpdateUrl); + repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build()); + repo.init(); + return repo; + } + + /** + * Checks that {@code SPARQLQueryBuilder#exists(RepositoryConnection, boolean)} can return + * {@code true} by querying for a list of all classes in {@link #DATA_CLASS_RDFS_HIERARCHY} + * which contains a number of classes. + * + * @throws Exception + * - + */ + static void thatExistsReturnsTrueWhenDataQueriedForExists(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB)); + + assertThat(result).isTrue(); + } + + /** + * If the KB has no default language set, then only labels and descriptions with no language at + * all should be returned. + * + * @throws Exception + * - + */ + static void thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setDefaultLanguage(null); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withIdentifier("http://example.org/#green-goblin") // + .retrieveLabel() // + .retrieveDescription()); + + assertThat(results).isNotEmpty(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "description", "language") + .containsExactlyInAnyOrder(KBHandle.builder() + .withIdentifier("http://example.org/#green-goblin").withName("Green Goblin") + .withDescription("Little green monster").build()); + } + + /** + * If the KB has a default language set, then labels/descriptions in that language should be + * preferred it is permitted to fall back to labels/descriptions without any language. The + * dataset contains only labels for French but no descriptions, so it should fall back to + * returning the description without any language. + * + * @throws Exception + * - + */ + static void thatLabelsAndDescriptionsWithLanguageArePreferred(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + // The dataset contains only labels for French but no descriptions + aKB.setDefaultLanguage("fr"); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withIdentifier("http://example.org/#green-goblin") // + .retrieveLabel() // + .retrieveDescription()); + + assertThat(results).isNotEmpty(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "description", "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Goblin vert", "Little green monster", "fr")); + } + + static void thatSearchOverMultipleLabelsWorks(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_MULTIPLE_LABELS); + + for (String term : asList("specimen", "sample", "instance", "case")) { + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelMatchingAnyOf(term) // + .retrieveLabel()); + + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#example", term)); + } + } + + static void thatMatchingAgainstAdditionalSearchPropertiesWorks(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setLabelIri("http://www.w3.org/2000/01/rdf-schema#prefLabel"); + aKB.setAdditionalMatchingProperties(asList("http://www.w3.org/2000/01/rdf-schema#label")); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_ADDITIONAL_SEARCH_PROPERTIES); + + for (String term : asList("specimen", "sample", "instance", "case")) { + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelMatchingAnyOf(term) // + .retrieveLabel()); + + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name") + .containsExactlyInAnyOrder( + new KBHandle("http://example.org/#example", "specimen")); + } + } + + static void thatMatchingAgainstAdditionalSearchPropertiesWorks2(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setLabelIri("http://www.w3.org/2000/01/rdf-schema#prefLabel"); + aKB.setAdditionalMatchingProperties(asList("http://www.w3.org/2000/01/rdf-schema#label")); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_ADDITIONAL_SEARCH_PROPERTIES_2); + + var queriesWithMatchTerms = asList(// + Pair.of("hand", // + asList("Hand structure (body structure)", "Hand structure", "Hand")), + Pair.of("hand structure", // + asList("Hand structure (body structure)", "Hand structure", "Hand")), + Pair.of("body structure", // + asList("Hand structure (body structure)", "Hand structure"))); + + for (var queryPair : queriesWithMatchTerms) { + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelMatchingAnyOf(queryPair.getKey()) // + .retrieveLabel()); + + var expectedKBHandle = new KBHandle("http://example.org/#example", + "Hand structure (body structure)"); + queryPair.getValue().forEach(v -> expectedKBHandle.addMatchTerm(v, null)); + + assertThat(results) // + .usingRecursiveFieldByFieldElementComparator( + RecursiveComparisonConfiguration.builder() // + .withComparedFields("identifier", "name", "matchTerms") // + .withIgnoredCollectionOrderInFields("matchTerms") // + .build()) + .containsExactlyInAnyOrder(expectedKBHandle); + } + } + + /** + * Checks that {@code SPARQLQueryBuilder#exists(RepositoryConnection, boolean)} can return + * {@code false} by querying for the parent of a root class in + * {@link #DATA_CLASS_RDFS_HIERARCHY} which does not exist. + * + * @throws Exception + * - + */ + static void thatExistsReturnsFalseWhenDataQueriedForDoesNotExist(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + boolean result = exists(aRepository, + SPARQLQueryBuilder.forClasses(aKB).parentsOf("http://example.org/#explicitRoot")); + + assertThat(result).isFalse(); + } + + /** + * Checks that an explicitly defined class can be retrieved using its identifier. + * + * @throws Exception + * - + */ + static void thatExplicitClassCanBeRetrievedByItsIdentifier(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) + .withIdentifier("http://example.org/#explicitRoot")); + + assertThat(result).isTrue(); + } + + /** + * Checks that an implicitly defined class can be retrieved using its identifier. + * + * @throws Exception + * - + */ + static void thatImplicitClassCanBeRetrievedByItsIdentifier(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) + .withIdentifier("http://example.org/#implicitRoot")); + + assertThat(result).isTrue(); + } + + /** + * Checks that a either explicitly nor implicitly defined class can be retrieved using its + * identifier. + * + * @throws Exception + * - + */ + static void thatNonClassCannotBeRetrievedByItsIdentifier(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) + .withIdentifier("http://example.org/#DoesNotExist")); + + assertThat(result).isFalse(); + } + + /** + * Checks that item information can be obtained for a given subject. + * + * @throws Exception + * - + */ + static void thatCanRetrieveItemInfoForIdentifier(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withIdentifier("http://example.org/#red-goblin") // + .retrieveLabel() // + .retrieveDescription()); + + assertThat(results).isNotEmpty(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "description", "language") + .containsExactlyInAnyOrder(KBHandle.builder() + .withIdentifier("http://example.org/#red-goblin").withName("Red Goblin") + .withDescription("Little red monster").build()); + } + + @SuppressWarnings("deprecation") + static void thatAllPropertiesCanBeRetrieved(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forProperties(aKB) // + .retrieveLabel() // + .retrieveDescription() // + .retrieveDomainAndRange()); + + assertThat(results).isNotEmpty(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "description", "range", "domain") + .containsExactlyInAnyOrder( + new KBHandle("http://example.org/#property-1", "Property 1", "Property One", + null, "http://example.org/#explicitRoot", + "http://www.w3.org/2001/XMLSchema#string"), + new KBHandle("http://example.org/#property-2", "Property 2", "Property Two", + null, "http://example.org/#subclass1", + "http://www.w3.org/2001/XMLSchema#Integer"), + KBHandle.builder().withIdentifier("http://example.org/#property-3") + .withName("Property 3").withDescription("Property Three").build(), + KBHandle.builder().withIdentifier("http://example.org/#subproperty-1-1") + .withName("Subproperty 1-1").withDescription("Property One-One") + .build(), + KBHandle.builder().withIdentifier("http://example.org/#subproperty-1-1-1") + .withName("Subproperty 1-1-1") + .withDescription("Property One-One-One").build()); + } + + static void thatDeprecationStatusCanBeRetrieved(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_DEPRECATED); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .retrieveLabel() // + .retrieveDeprecation()); + + assertThat(results).isNotEmpty(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "deprecated") + .containsExactlyInAnyOrder( + KBHandle.builder().withIdentifier("http://example.org/#class-1") + .withName("Class 1").withDeprecated(true).build(), + KBHandle.builder().withIdentifier("http://example.org/#class-2") + .withName("Class 2").withDeprecated(false).build(), + KBHandle.builder().withIdentifier("http://example.org/#class-3") + .withName("Class 3").withDeprecated(true).build(), + KBHandle.builder().withIdentifier("http://example.org/#class-4") + .withName("Class 4").withDeprecated(false).build(), + KBHandle.builder().withIdentifier("http://example.org/#class-5") + .withName("Class 5").withDeprecated(true).build(), + KBHandle.builder().withIdentifier("http://example.org/#instance-1") + .withName("Instance 1").withDeprecated(true).build(), + KBHandle.builder().withIdentifier("http://example.org/#property-1") + .withName("Property 1").withDeprecated(true).build()); + } + + static void thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forProperties(aKB) // + .descendantsOf("http://example.org/#property-1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#subproperty-1-1", + "http://example.org/#subproperty-1-1-1"); + } + + static void thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); + + var results = asHandles(aRepository, + SPARQLQueryBuilder.forProperties(aKB).childrenOf("http://example.org/#property-1")); + + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getIdentifier) + .containsExactlyInAnyOrder("http://example.org/#subproperty-1-1"); + } + + static void thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forProperties(aKB) + .matchingDomain("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getIdentifier).containsExactlyInAnyOrder( + // property-1 is inherited by #subclass1 from #explicitRoot + "http://example.org/#property-1", + // property-2 is declared on #subclass1 + "http://example.org/#property-2", + // property-3 defines no domain + "http://example.org/#property-3"); + // other properties all either define or inherit an incompatible domain + } + + static void thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .roots()); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .containsExactlyInAnyOrder("explicitRoot", "implicitRoot"); + } + + static void thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + aKB.setRootConcepts(asList("http://example.org/#implicitRoot")); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB).roots()); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .containsExactlyInAnyOrder("implicitRoot"); + } + + static void thatNonRootClassCanBeUsedAsExplicitRootClass(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + aKB.setRootConcepts( + asList("http://example.org/#implicitRoot", "http://example.org/#subclass2")); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB).roots()); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .containsExactlyInAnyOrder("implicitRoot", "subclass2"); + } + + static void thatQueryLimitedToClassesDoesNotReturnInstances(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB)); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .noneMatch(label -> label.contains("instance")); + } + + static void thatQueryLimitedToInstancesDoesNotReturnClasses(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forInstances(aKB)); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .noneMatch(label -> label.contains("class")); + } + + static void thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .ancestorsOf("http://example.org/#subclass1-1-1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#explicitRoot", + "http://example.org/#subclass1", "http://example.org/#subclass1-1"); + } + + static void thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .parentsOf("http://example.org/#subclass1-1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#subclass1"); + } + + static void thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .childrenOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#subclass1-1"); + } + + static void thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .descendantsOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#subclass1-1", + "http://example.org/#subclass1-1-1"); + } + + static void thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .parentsOf("http://example.org/#1-1-1-instance-4")); + + assertThat(results).isNotEmpty(); // + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#subclass1-1-1"); + } + + static void thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .ancestorsOf("http://example.org/#1-1-1-instance-4")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#explicitRoot", + "http://example.org/#subclass1", "http://example.org/#subclass1-1", + "http://example.org/#subclass1-1-1"); + } + + static void thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forInstances(aKB) // + .childrenOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://example.org/#1-instance-1"); + } + + static void thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forInstances(aKB) // + .descendantsOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .allMatch(label -> label.matches("http://example.org/#1(-1)*-instance-.*")); + } + + static void thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .childrenOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder( // + "http://example.org/#1-instance-1", "http://example.org/#subclass1-1"); + } + + static void thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( + Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); + + var results = asHandles(aRepository, SPARQLQueryBuilder.forItems(aKB) // + .descendantsOf("http://example.org/#subclass1")); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .allMatch(label -> label.matches("http://example.org/#1(-1)*-instance-.*") + || label.startsWith("http://example.org/#subclass1-")); + } + + static void testWithLabelMatchingAnyOf_withLanguage_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + testWithLabelMatchingAnyOf_withLanguage(aRepository, aKB); + } + + static void testWithLabelMatchingAnyOf_withLanguage(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelMatchingAnyOf("Gobli")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.contains("Goblin")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "language").containsExactlyInAnyOrder( + new KBHandle("http://example.org/#red-goblin", "Red Goblin"), new KBHandle( + "http://example.org/#green-goblin", "Green Goblin", null, "en")); + } + + static void testWithLabelContainingAnyOf_withLanguage_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + testWithLabelContainingAnyOf_withLanguage(aRepository, aKB); + } + + static void testWithLabelContainingAnyOf_withLanguage(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelContainingAnyOf("Goblin")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.contains("Goblin")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "language").containsExactlyInAnyOrder( + new KBHandle("http://example.org/#red-goblin", "Red Goblin"), new KBHandle( + "http://example.org/#green-goblin", "Green Goblin", null, "en")); + } + + static void testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + testWithLabelMatchingExactlyAnyOf_withLanguage(aRepository, aKB); + } + + static void testWithLabelMatchingExactlyAnyOf_withLanguage(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelMatchingExactlyAnyOf("Green Goblin")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.equals("Green Goblin")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Green Goblin", null, "en")); + } + + static void testWithLabelMatchingExactlyAnyOf_subproperty_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + testWithLabelMatchingExactlyAnyOf_subproperty(aRepository, aKB); + } + + static void testWithLabelMatchingExactlyAnyOf_subproperty(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + aKB.setLabelIri(RDFS.LABEL.stringValue()); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, LABEL_SUBPROPERTY); + + // The label "Green Goblin" is not assigned directly via rdfs:label but rather via a + // subproperty of it. Thus, this test also checks if the label sub-property support works. + var results = asHandles(aRepository, + SPARQLQueryBuilder.forItems(aKB).withLabelMatchingExactlyAnyOf("Green Goblin")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.equals("Green Goblin")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "language").containsExactlyInAnyOrder( + new KBHandle("http://example.org/#green-goblin", "Green Goblin")); + } + + static void testWithLabelStartingWith_withoutLanguage_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + testWithLabelStartingWith_withoutLanguage(aRepository, aKB); + } + + static void testWithLabelStartingWith_withoutLanguage(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_LABELS_WITHOUT_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Green")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "language").containsExactlyInAnyOrder( + new KBHandle("http://example.org/#green-goblin", "Green Goblin")); + } + + static void testWithLabelStartingWith_withLanguage_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green Goblin")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Green")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Green Goblin", null, "en")); + } + + static void testWithLabelStartingWith_withLanguage_FTS_1(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + // Single word - actually, we add a wildcard here so anything that starts with "Green" + // would also be matched + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Green")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Green Goblin", null, "en")); + } + + static void testWithLabelStartingWith_withLanguage_FTS_2(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + // Two words with the second being very short - this is no problem for the LUCENE FTS + // and we simply add a wildcard to match "Green Go*" + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green Go")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Green Go")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "description", "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Green Goblin", null, "en")); + } + + static void testWithLabelStartingWith_withLanguage_FTS_3(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + // Two words with the second being very short and a space following - in this case we + // assume that the user is in fact searching for "Barack Ob" and do either drop the + // last element nor add a wildcard + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green Go ")); + + assertThat(results).isEmpty(); + } + + static void testWithLabelStartingWith_withLanguage_FTS_4(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, + DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); + + // Two words with the second being very short - this is no problem for the LUCENE FTS + // and we simply add a wildcard to match "Green Go*" + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelStartingWith("Green Go")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Green Go")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results) + .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", + "description", "language") + .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", + "Green Goblin", null, "en")); + } + + static void testWithLabelStartingWith_OLIA(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + aKB.setLabelIri("http://purl.org/olia/system.owl#hasTag"); + + importDataFromFile(aRepository, aKB, "src/test/resources/data/penn.owl"); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forInstances(aKB) // + .withLabelStartingWith("N")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel).containsExactlyInAnyOrder("NN", "NNP", + "NNPS", "NNS"); + } + + static void testWithLabelContainingAnyOf_pets_ttl_noFTS(Repository aRepository, + KnowledgeBase aKB) + throws Exception + { + aKB.setFullTextSearchIri(null); + + importDataFromFile(aRepository, aKB, "src/test/resources/data/pets.ttl"); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forItems(aKB) // + .withLabelContainingAnyOf("Socke")); + + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.contains("Socke")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", + "name", "language") + .containsExactlyInAnyOrder(new KBHandle("http://mbugert.de/pets#socke", "Socke")); + } + + static void thatRootsCanBeRetrieved_ontolex(Repository aRepository, KnowledgeBase aKB) + throws Exception + { + importDataFromFile(aRepository, aKB, + "src/test/resources/data/wordnet-ontolex-ontology.owl"); + + initOwlMapping(aKB); + + var results = asHandles(aRepository, SPARQLQueryBuilder // + .forClasses(aKB) // + .roots() // + .retrieveLabel()); + + assertThat(results).isNotEmpty(); + + assertThat(results).extracting(KBHandle::getUiLabel).contains("Adjective position", + "Lexical domain", "Part of speech", "Phrase type", "Synset"); + } + + static void importDataFromFile(Repository aRepository, KnowledgeBase aKB, String aFilename) + throws IOException + { + // Detect the file format + var format = Rio.getParserFormatForFileName(aFilename).orElse(RDFXML); + + System.out.printf("Loading %s data from %s%n", format, aFilename); + + // Load files into the repository + try (var is = new FileInputStream(aFilename)) { + importData(aRepository, aKB, format, is); + } + } + + static void importDataFromString(Repository aRepository, KnowledgeBase aKB, RDFFormat aFormat, + String... aRdfData) + throws IOException + { + var data = String.join("\n", aRdfData); + + // Load files into the repository + try (var is = IOUtils.toInputStream(data, UTF_8)) { + importData(aRepository, aKB, aFormat, is); + } + } + + static void importData(Repository aRepository, KnowledgeBase aKB, RDFFormat aFormat, + InputStream aIS) + throws IOException + { + try (var conn = aRepository.getConnection()) { + // If the RDF file contains relative URLs, then they probably start with a hash. + // To avoid having two hashes here, we drop the hash from the base prefix configured + // by the user. + String prefix = StringUtils.removeEnd(aKB.getBasePrefix(), "#"); + if (aKB.getDefaultDatasetIri() != null) { + var ctx = SimpleValueFactory.getInstance().createIRI(aKB.getDefaultDatasetIri()); + conn.add(aIS, prefix, aFormat, ctx); + } + else { + conn.add(aIS, prefix, aFormat); + } + } + } + + static void initRdfsMapping(KnowledgeBase aKB) + { + aKB.setClassIri(RDFS.CLASS.stringValue()); + aKB.setSubclassIri(RDFS.SUBCLASSOF.stringValue()); + aKB.setTypeIri(RDF.TYPE.stringValue()); + aKB.setLabelIri(RDFS.LABEL.stringValue()); + aKB.setPropertyTypeIri(RDF.PROPERTY.stringValue()); + aKB.setDescriptionIri(RDFS.COMMENT.stringValue()); + // We are intentionally not using RDFS.LABEL here to ensure we can test the label + // and property label separately + aKB.setPropertyLabelIri(SKOS.PREF_LABEL.stringValue()); + // We are intentionally not using RDFS.COMMENT here to ensure we can test the description + // and property description separately + aKB.setPropertyDescriptionIri("http://schema.org/description"); + aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + aKB.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); + } + + static void initOwlMapping(KnowledgeBase aKB) + { + aKB.setClassIri(OWL.CLASS.stringValue()); + aKB.setSubclassIri(RDFS.SUBCLASSOF.stringValue()); + aKB.setTypeIri(RDF.TYPE.stringValue()); + aKB.setLabelIri(RDFS.LABEL.stringValue()); + aKB.setPropertyTypeIri(RDF.PROPERTY.stringValue()); + aKB.setDescriptionIri(RDFS.COMMENT.stringValue()); + aKB.setPropertyLabelIri(RDF.PROPERTY.stringValue()); + aKB.setPropertyDescriptionIri(RDFS.COMMENT.stringValue()); + aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + aKB.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); + } + + static void initWikidataMapping(KnowledgeBase aKB) + { + aKB.setClassIri("http://www.wikidata.org/entity/Q35120"); + aKB.setSubclassIri("http://www.wikidata.org/prop/direct/P279"); + aKB.setTypeIri("http://www.wikidata.org/prop/direct/P31"); + aKB.setLabelIri("http://www.w3.org/2000/01/rdf-schema#label"); + aKB.setPropertyTypeIri("http://www.wikidata.org/entity/Q18616576"); + aKB.setDescriptionIri("http://schema.org/description"); + aKB.setPropertyLabelIri("http://www.w3.org/2000/01/rdf-schema#label"); + aKB.setPropertyDescriptionIri("http://www.w3.org/2000/01/rdf-schema#comment"); + aKB.setSubPropertyIri("http://www.wikidata.org/prop/direct/P1647"); + } + + public static void assertIsReachable(Repository aRepository) + { + if (!(aRepository instanceof SPARQLRepository)) { + return; + } + + SPARQLRepository sparqlRepository = (SPARQLRepository) aRepository; + + assumeTrue(isReachable(sparqlRepository.toString()), + "Remote repository at [" + sparqlRepository + "] is not reachable"); + } +} diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderRemoteServicesTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderRemoteServicesTest.java new file mode 100644 index 00000000000..23478d750e3 --- /dev/null +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderRemoteServicesTest.java @@ -0,0 +1,650 @@ +/* + * 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.kb.querybuilder; + +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_FUSEKI; +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_VIRTUOSO; +import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_WIKIDATA; +import static de.tudarmstadt.ukp.inception.kb.RepositoryType.REMOTE; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; +import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.sanitizeQueryString_FTS; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.asHandles; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.assertThatChildrenOfExplicitRootCanBeRetrieved; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.assertIsReachable; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.buildSparqlRepository; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping; +import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.initWikidataMapping; +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; + +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.repository.Repository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import de.tudarmstadt.ukp.inception.kb.RepositoryType; +import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; +import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; + +public class SPARQLQueryBuilderRemoteServicesTest +{ + private KnowledgeBase kb; + private Repository ukpVirtuosoRepo; + private Repository zbwStw; + private Repository zbwGnd; + private Repository wikidata; + private Repository dbpedia; + private Repository yago; + private Repository hucit; + + @BeforeEach + public void setUp() + { + suspendSslVerification(); + + kb = new KnowledgeBase(); + kb.setDefaultLanguage("en"); + kb.setType(RepositoryType.LOCAL); + kb.setFullTextSearchIri(null); + kb.setMaxResults(100); + + initRdfsMapping(kb); + + ukpVirtuosoRepo = buildSparqlRepository( + "http://knowledgebase.ukp.informatik.tu-darmstadt.de:8890/sparql"); + wikidata = buildSparqlRepository("https://query.wikidata.org/sparql"); + dbpedia = buildSparqlRepository("http://de.dbpedia.org/sparql"); + yago = buildSparqlRepository("https://yago-knowledge.org/sparql/query"); + hucit = buildSparqlRepository("http://nlp.dainst.org:8888/sparql"); + // Web: http://zbw.eu/beta/sparql-lab/?endpoint=http://zbw.eu/beta/sparql/stw/query + zbwStw = buildSparqlRepository("http://zbw.eu/beta/sparql/stw/query"); + // Web: http://zbw.eu/beta/sparql-lab/?endpoint=http://zbw.eu/beta/sparql/gnd/query + zbwGnd = buildSparqlRepository("http://zbw.eu/beta/sparql/gnd/query"); + } + + @BeforeEach + public void testWatcher(TestInfo aTestInfo) + { + String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); + System.out.printf("\n=== %s === %s =====================\n", methodName, + aTestInfo.getDisplayName()); + + suspendSslVerification(); + } + + @AfterEach + public void tearDown() + { + restoreSslVerification(); + } + + @Tag("slow") + @Test + void thatPropertyQueryListWorks_Wikidata() + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder.forProperties(kb).limit(10)); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).hasSize(10); + } + + @Tag("slow") + @Test + void thatPropertyQueryLabelStartingWith_Wikidata() + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, + SPARQLQueryBuilder.forProperties(kb).withLabelStartingWith("educated")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().startsWith("educated")); + } + + /** + * This query tries to find all humans in the Star Trek universe + * ({@code http://www.wikidata.org/entity/Q924827}) who are named Amanda. It tests + * whether the call to {@link SPARQLQueryBuilder#childrenOf(String)} disables the FTS. If the + * FTS is not disabled, then no result would be returned because there are so many Amandas in + * Wikidata that the popular ones returned by the FTS do not include any from the Star Trek + * universe. + * + * @throws Exception + * - + */ + @Tag("slow") + @Test + void thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults_Wikidata() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forInstances(kb) // + .childrenOf("http://www.wikidata.org/entity/Q924827") // + .withLabelStartingWith("Amanda") // + .retrieveLabel()); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .extracting(KBHandle::getIdentifier) // + .containsExactlyInAnyOrder("http://www.wikidata.org/entity/Q1412447"); + } + + @Tag("slow") + @Test + void testWithLabelContainingAnyOf_Virtuoso_withLanguage_FTS() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelContainingAnyOf("Tower")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().contains("tower")); + } + + @Tag("slow") + @Test + void testWithLabelContainingAnyOf_Wikidata_FTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelContainingAnyOf("Tower")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().contains("tower")); + } + + @Tag("slow") + @Test + void testWithLabelContainingAnyOf_Fuseki_FTS() throws Exception + { + assertIsReachable(zbwGnd); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); + kb.setLabelIri(RDFS.LABEL.stringValue()); + kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + + var results = asHandles(zbwGnd, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelContainingAnyOf("Schapiro-Frisch", "Stiker-Métral")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel).allMatch( + label -> label.contains("Schapiro-Frisch") || label.contains("Stiker-Métral")); + } + + @Tag("slow") + @Test + void testWithLabelContainingAnyOf_classes_HUCIT_FTS() throws Exception + { + assertIsReachable(hucit); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + var results = asHandles(hucit, SPARQLQueryBuilder // + .forClasses(kb) // + .withLabelContainingAnyOf("work")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().contains("work")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_1() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + // Single word - actually, we add a wildcard here so anything that starts with "Barack" + // would also be matched + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Barack")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Barack")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_2() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + // Two words with the second being very short - in this case, we drop the very short word + // so that the user doesn't stop getting suggestions while writing because Virtuoso doesn't + // do wildcards on words shorter than 4 characters + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Barack Ob")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Barack")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_3() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + // Two words with the second being very short and a space following - in this case we + // assmume that the user is in fact searching for "Barack Ob" and do either drop the + // last element nor add a wildcard + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Barack Ob ")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Barack")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_4() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + // Two words with the second being 4+ chars - we add a wildcard here so anything + // starting with "Barack Obam" should match + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Barack Obam")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Barack Obam")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Wikidata_FTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Barack")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().startsWith("barack")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_Fuseki_FTS() throws Exception + { + assertIsReachable(zbwGnd); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); + kb.setLabelIri(RDFS.LABEL.stringValue()); + kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + + var results = asHandles(zbwGnd, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Thom")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().startsWith("thom")); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_Fuseki_noFTS_STW() throws Exception + { + assertIsReachable(zbwStw); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(null); + + var results = asHandles(zbwStw, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelMatchingExactlyAnyOf("Labour")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> "Labour".equals(label)); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_Fuseki_FTS_GND() throws Exception + { + assertIsReachable(zbwGnd); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); + kb.setLabelIri(RDFS.LABEL.stringValue()); + kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); + + // The label "Gadebusch, Thomas Henricus" is not assigned directly via rdfs:label but rather + // via a subproperty of it. Thus, this test also checks if the label sub-property support + // works. + // + // gndo:variantNameForThePerson "Gadebusch, Thomas + // Henricus"; + // gndo:variantNameEntityForThePerson _:node1fhgbdto1x8884759 . + var results = asHandles(zbwGnd, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelMatchingExactlyAnyOf("Gadebusch, Thomas Henricus")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> "Gadebusch, Thomas Henricus".equals(label)); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_Wikidata_noFTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(null); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelMatchingExactlyAnyOf("Labour")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> "Labour".equals(label)); + } + + @Tag("slow") + @Test + public void testWithPropertyMatchingAnyOf_Wikidata_noFTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(null); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forProperties(kb) // + .withLabelMatchingAnyOf("academic")); + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.toLowerCase().contains("academic")); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_Wikidata_FTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelMatchingExactlyAnyOf("Labour")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.equalsIgnoreCase("Labour")); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_multiple_Wikidata_FTS() throws Exception + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forInstances(kb) // + .withLabelMatchingExactlyAnyOf("Labour", "Tory")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> "Labour".equals(label) || "Tory".equals(label)); + } + + @Tag("slow") + @Test + public void testWithLabelMatchingExactlyAnyOf_Virtuoso_withLanguage_FTS() throws Exception + { + assertIsReachable(ukpVirtuosoRepo); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); + + var results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelMatchingExactlyAnyOf("Green Goblin")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> "Green Goblin".equals(label)); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_HUCIT_noFTS() throws Exception + { + assertIsReachable(hucit); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(null); + + var results = asHandles(hucit, SPARQLQueryBuilder // + .forItems(kb) // + .withLabelStartingWith("Achilles")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Achilles")); + } + + @Tag("slow") + @Test + public void testWithLabelStartingWith_onlyDescendants_HUCIT_noFTS() throws Exception + { + assertIsReachable(hucit); + + kb.setType(REMOTE); + kb.setFullTextSearchIri(null); + + var results = asHandles(hucit, SPARQLQueryBuilder // + .forInstances(kb) // + .descendantsOf("http://erlangen-crm.org/efrbroo/F1_Work") // + .withLabelStartingWith("Achilles")); + + assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(KBHandle::getUiLabel) + .allMatch(label -> label.startsWith("Achilles")); + } + + @Tag("slow") + @Test + public void thatChildrenOfExplicitRootCanBeRetrieved_DBPedia() + { + assertIsReachable(dbpedia); + + kb.setType(REMOTE); + + assertThatChildrenOfExplicitRootCanBeRetrieved(kb, dbpedia, + "http://www.w3.org/2002/07/owl#Thing", 0); + } + + @Disabled("YAGO seems to have problem atm 29-04-2023") + @Tag("slow") + @Test + public void thatChildrenOfExplicitRootCanBeRetrieved_YAGO() + { + assertIsReachable(yago); + + kb.setType(REMOTE); + + // YAGO has the habit of timing out on some requests. Unfortunately, there is no clear + // pattern when this happens - might be due to server load on the YAGO side. Thus, to + // keep the load lower, we only validate 5 children. + assertThatChildrenOfExplicitRootCanBeRetrieved(kb, yago, "http://schema.org/Thing", 5); + } + + @Tag("slow") + @Test + public void thatParentsCanBeRetrieved_Wikidata() + { + assertIsReachable(wikidata); + + kb.setType(REMOTE); + initWikidataMapping(kb); + + var results = asHandles(wikidata, SPARQLQueryBuilder // + .forClasses(kb) // + .ancestorsOf("http://www.wikidata.org/entity/Q5") // + .retrieveLabel()); + + assertThat(results).isNotEmpty(); + assertThat(results) // + .as("Root concept http://www.wikidata.org/entity/Q35120 should be included") // + .extracting(KBHandle::getIdentifier) // + .contains("http://www.wikidata.org/entity/Q35120"); + } + + @Tag("slow") + @Test + public void thatRootsCanBeRetrieved_DBPedia() + { + assertIsReachable(dbpedia); + + kb.setType(REMOTE); + + var results = asHandles(dbpedia, SPARQLQueryBuilder.forClasses(kb).roots().retrieveLabel()); + + assertThat(results).isNotEmpty(); + + assertThat(results) // + .extracting(KBHandle::getUiLabel) // + .contains("Thing"); + } + + @Tag("slow") + @Test + public void thatParentsCanBeRetrieved_DBPedia() + { + assertIsReachable(dbpedia); + + kb.setType(REMOTE); + + var results = asHandles(dbpedia, SPARQLQueryBuilder // + .forClasses(kb) // + .ancestorsOf("http://dbpedia.org/ontology/Organisation") // + .retrieveLabel()); + + assertThat(results).isNotEmpty(); + + assertThat(results) // + .extracting(KBHandle::getName) // + .contains("agent", "Thing"); + } + + @Test + public void thatLineBreaksAreSanitized() throws Exception + { + assertThat(sanitizeQueryString_FTS("Green\n\rGoblin")).isEqualTo("Green Goblin"); + } +} diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderTest.java index 2339a2f50b3..324f6afdb10 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/SPARQLQueryBuilderTest.java @@ -17,1895 +17,16 @@ */ package de.tudarmstadt.ukp.inception.kb.querybuilder; -import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_FUSEKI; -import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_VIRTUOSO; -import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_WIKIDATA; -import static de.tudarmstadt.ukp.inception.kb.RepositoryType.REMOTE; -import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.newPerThreadSslCheckingHttpClientBuilder; -import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; -import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilder.sanitizeQueryString_FTS; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.asHandles; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.assertThatChildrenOfExplicitRootCanBeRetrieved; -import static de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderAsserts.exists; -import static de.tudarmstadt.ukp.inception.kb.util.TestFixtures.isReachable; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.contentOf; -import static org.eclipse.rdf4j.rio.RDFFormat.RDFXML; -import static org.eclipse.rdf4j.rio.RDFFormat.TURTLE; -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.util.List; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.function.FailableBiConsumer; -import org.apache.commons.lang3.tuple.Pair; -import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; -import org.eclipse.rdf4j.model.impl.SimpleValueFactory; -import org.eclipse.rdf4j.model.vocabulary.OWL; -import org.eclipse.rdf4j.model.vocabulary.RDF; -import org.eclipse.rdf4j.model.vocabulary.RDFS; -import org.eclipse.rdf4j.model.vocabulary.SKOS; -import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryConnection; -import org.eclipse.rdf4j.repository.sparql.SPARQLRepository; -import org.eclipse.rdf4j.rio.RDFFormat; -import org.eclipse.rdf4j.rio.Rio; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInfo; - -import de.tudarmstadt.ukp.inception.kb.RepositoryType; -import de.tudarmstadt.ukp.inception.kb.graph.KBHandle; -import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; public class SPARQLQueryBuilderTest { - static final String TURTLE_PREFIX = String.join("\n", // - "@base .", // - "@prefix rdf: .", // - "@prefix rdfs: .", // - "@prefix so: .", // - "@prefix skos: ."); - - static final String DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE = contentOf( - new File("src/test/resources/turtle/data_labels_and_descriptions_with_language.ttl"), - UTF_8); - - static final String DATA_LABELS_WITHOUT_LANGUAGE = String.join("\n", // - "<#green-goblin>", // - " rdfs:label 'Green Goblin' .", // - "", // - "<#lucky-green>", // - " rdfs:label 'Lucky Green' .", // - "", // - "<#red-goblin>", // - " rdfs:label 'Red Goblin' ."); - - static final String DATA_MULTIPLE_LABELS = String.join("\n", // - "<#example>", // - " rdfs:label 'specimen' ;", // - " rdfs:label 'sample' ;", // - " rdfs:label 'instance' ;", // - " rdfs:label 'case' ."); - - static final String DATA_ADDITIONAL_SEARCH_PROPERTIES = contentOf( - new File("src/test/resources/turtle/data_additional_search_properties.ttl"), UTF_8); - - static final String DATA_ADDITIONAL_SEARCH_PROPERTIES_2 = contentOf( - new File("src/test/resources/turtle/data_additional_search_properties_2.ttl"), UTF_8); - - static final String LABEL_SUBPROPERTY = String.join("\n", // - "<#sublabel>", // - " rdfs:subPropertyOf rdfs:label .", // - "", // - "<#green-goblin>", // - " <#sublabel> 'Green Goblin' ."); - - /** - * This dataset contains a hierarchy of classes and instances with a naming scheme. There is an - * implicit and an explicit root class. All classes have "class" in their name. Subclasses start - * with "subclass" and then a number. Instances start with the number of the class to which they - * belong followed by a number. - */ - static final String DATA_CLASS_RDFS_HIERARCHY = String.join("\n", // - "<#explicitRoot>", // - " rdf:type rdfs:Class .", // - "<#subclass1>", // - " rdf:type rdfs:Class ;", // - " rdfs:subClassOf <#explicitRoot> .", // - "<#subclass1-1>", // - " rdfs:subClassOf <#subclass1> .", // - "<#subclass1-1-1>", // - " rdfs:subClassOf <#subclass1-1> .", // - "<#subclass2>", // - " rdfs:subClassOf <#explicitRoot> .", // - "<#subclass3>", // - " rdfs:subClassOf <#implicitRoot> .", // - "<#0-instance-1>", // - " rdf:type <#root> .", // - "<#1-instance-1>", // - " rdf:type <#subclass1> .", // - "<#2-instance-2>", // - " rdf:type <#subclass2> .", // - "<#3-instance-3>", // - " rdf:type <#subclass3> .", // - "<#1-1-1-instance-4>", // - " rdf:type <#subclass1-1-1> ."); - - /** - * This dataset contains properties, some in a hierarchical relationship. There is again a - * naming scheme: all properties have "property" in their name. Subproperties start with - * "subproperty" and then a number. The dataset also contains some non-properties to be able to - * ensure that queries limited to properties do not return non-properties. - */ - static final String DATA_PROPERTIES = String.join("\n", // - "<#explicitRoot>", // - " rdf:type rdfs:Class .", // - "<#property-1>", // - " rdf:type rdf:Property ;", // - " skos:prefLabel 'Property 1' ;", // - " so:description 'Property One' ;", // - " rdfs:domain <#explicitRoot> ;", // - " rdfs:range xsd:string .", // - "<#property-2>", // - " rdf:type rdf:Property ;", // - " skos:prefLabel 'Property 2' ;", // - " so:description 'Property Two' ;", // - " rdfs:domain <#subclass1> ;", // - " rdfs:range xsd:Integer .", // - "<#property-3>", // - " rdf:type rdf:Property ;", // - " skos:prefLabel 'Property 3' ;", // - " so:description 'Property Three' .", // - "<#subproperty-1-1>", // - " rdfs:subPropertyOf <#property-1> ;", // - " skos:prefLabel 'Subproperty 1-1' ;", // - " so:description 'Property One-One' .", // - "<#subproperty-1-1-1>", // - " rdfs:subPropertyOf <#subproperty-1-1> ;", // - " skos:prefLabel 'Subproperty 1-1-1' ;", // - " so:description 'Property One-One-One' .", // - "<#subclass1>", // - " rdf:type rdfs:Class ;", // - " rdfs:subClassOf <#explicitRoot> ;", // - " <#implicit-property-1> 'value1' ."); - - private KnowledgeBase kb; - private Repository ukpVirtuosoRepo; - private Repository zbwStw; - private Repository zbwGnd; - private Repository wikidata; - private Repository dbpedia; - private Repository yago; - private Repository hucit; - - @BeforeEach - public void setUp() - { - suspendSslVerification(); - - kb = new KnowledgeBase(); - kb.setDefaultLanguage("en"); - kb.setType(RepositoryType.LOCAL); - kb.setFullTextSearchIri(null); - kb.setMaxResults(100); - - initRdfsMapping(kb); - - ukpVirtuosoRepo = buildSparqlRepository( - "http://knowledgebase.ukp.informatik.tu-darmstadt.de:8890/sparql"); - wikidata = buildSparqlRepository("https://query.wikidata.org/sparql"); - dbpedia = buildSparqlRepository("http://de.dbpedia.org/sparql"); - yago = buildSparqlRepository("https://yago-knowledge.org/sparql/query"); - hucit = buildSparqlRepository("http://nlp.dainst.org:8888/sparql"); - // Web: http://zbw.eu/beta/sparql-lab/?endpoint=http://zbw.eu/beta/sparql/stw/query - zbwStw = buildSparqlRepository("http://zbw.eu/beta/sparql/stw/query"); - // Web: http://zbw.eu/beta/sparql-lab/?endpoint=http://zbw.eu/beta/sparql/gnd/query - zbwGnd = buildSparqlRepository("http://zbw.eu/beta/sparql/gnd/query"); - } - - @BeforeEach - public void testWatcher(TestInfo aTestInfo) - { - String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); - System.out.printf("\n=== %s === %s =====================\n", methodName, - aTestInfo.getDisplayName()); - - suspendSslVerification(); - } - - @AfterEach - public void tearDown() - { - restoreSslVerification(); - } - - static List tests() throws Exception - { - return asList( // - new Scenario("testWithLabelStartingWith_withLanguage_FTS_1", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withLanguage_FTS_1), - new Scenario("testWithLabelStartingWith_withLanguage_FTS_2", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withLanguage_FTS_2), - new Scenario("testWithLabelStartingWith_withLanguage_FTS_3", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withLanguage_FTS_3), - new Scenario("testWithLabelStartingWith_withLanguage_FTS_4", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withLanguage_FTS_4), - new Scenario("testWithLabelStartingWith_withLanguage_noFTS", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withLanguage_noFTS), - new Scenario("testWithLabelContainingAnyOf_pets_ttl_noFTS", - SPARQLQueryBuilderTest::testWithLabelContainingAnyOf_pets_ttl_noFTS), - new Scenario("thatRootsCanBeRetrieved_ontolex", - SPARQLQueryBuilderTest::thatRootsCanBeRetrieved_ontolex), - new Scenario("testWithLabelContainingAnyOf_withLanguage_noFTS", - SPARQLQueryBuilderTest::testWithLabelContainingAnyOf_withLanguage_noFTS), - new Scenario("testWithLabelContainingAnyOf_withLanguage", - SPARQLQueryBuilderTest::testWithLabelContainingAnyOf_withLanguage), - new Scenario("testWithLabelMatchingAnyOf_withLanguage", - SPARQLQueryBuilderTest::testWithLabelMatchingAnyOf_withLanguage), - new Scenario("testWithLabelMatchingAnyOf_withLanguage_noFTS", - SPARQLQueryBuilderTest::testWithLabelMatchingAnyOf_withLanguage_noFTS), - new Scenario("testWithLabelStartingWith_withoutLanguage", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withoutLanguage), - new Scenario("testWithLabelStartingWith_withoutLanguage_noFTS", - SPARQLQueryBuilderTest::testWithLabelStartingWith_withoutLanguage_noFTS), - new Scenario("testWithLabelMatchingExactlyAnyOf_subproperty", - SPARQLQueryBuilderTest::testWithLabelMatchingExactlyAnyOf_subproperty), - new Scenario("testWithLabelMatchingExactlyAnyOf_subproperty_noFTS", - SPARQLQueryBuilderTest::testWithLabelMatchingExactlyAnyOf_subproperty_noFTS), - new Scenario("testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS", - SPARQLQueryBuilderTest::testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS), - new Scenario("testWithLabelMatchingExactlyAnyOf_withLanguage", - SPARQLQueryBuilderTest::testWithLabelMatchingExactlyAnyOf_withLanguage), - new Scenario("thatExistsReturnsTrueWhenDataQueriedForExists", - SPARQLQueryBuilderTest::thatExistsReturnsTrueWhenDataQueriedForExists), - new Scenario("thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved", - SPARQLQueryBuilderTest::thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved), - new Scenario("thatLabelsAndDescriptionsWithLanguageArePreferred", - SPARQLQueryBuilderTest::thatLabelsAndDescriptionsWithLanguageArePreferred), - new Scenario("thatSearchOverMultipleLabelsWorks", - SPARQLQueryBuilderTest::thatSearchOverMultipleLabelsWorks), - new Scenario("thatMatchingAgainstAdditionalSearchPropertiesWorks", - SPARQLQueryBuilderTest::thatMatchingAgainstAdditionalSearchPropertiesWorks), - new Scenario("thatMatchingAgainstAdditionalSearchPropertiesWorks2", - SPARQLQueryBuilderTest::thatMatchingAgainstAdditionalSearchPropertiesWorks2), - new Scenario("thatExistsReturnsFalseWhenDataQueriedForDoesNotExist", - SPARQLQueryBuilderTest::thatExistsReturnsFalseWhenDataQueriedForDoesNotExist), - new Scenario("thatExplicitClassCanBeRetrievedByItsIdentifier", - SPARQLQueryBuilderTest::thatExplicitClassCanBeRetrievedByItsIdentifier), - new Scenario("thatImplicitClassCanBeRetrievedByItsIdentifier", - SPARQLQueryBuilderTest::thatImplicitClassCanBeRetrievedByItsIdentifier), - new Scenario("thatNonClassCannotBeRetrievedByItsIdentifier", - SPARQLQueryBuilderTest::thatNonClassCannotBeRetrievedByItsIdentifier), - new Scenario("thatCanRetrieveItemInfoForIdentifier", - SPARQLQueryBuilderTest::thatCanRetrieveItemInfoForIdentifier), - new Scenario("thatAllPropertiesCanBeRetrieved", - SPARQLQueryBuilderTest::thatAllPropertiesCanBeRetrieved), - new Scenario("thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), - new Scenario("thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), - new Scenario("thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults), - new Scenario("thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults), - new Scenario("thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults), - new Scenario("thatNonRootClassCanBeUsedAsExplicitRootClass", - SPARQLQueryBuilderTest::thatNonRootClassCanBeUsedAsExplicitRootClass), - new Scenario("thatQueryLimitedToClassesDoesNotReturnInstances", - SPARQLQueryBuilderTest::thatQueryLimitedToClassesDoesNotReturnInstances), - new Scenario("thatQueryLimitedToInstancesDoesNotReturnClasses", - SPARQLQueryBuilderTest::thatQueryLimitedToInstancesDoesNotReturnClasses), - new Scenario("thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults), - new Scenario("thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults), - new Scenario("thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), - new Scenario("thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), - new Scenario("thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults), - new Scenario("thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults), - new Scenario("thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), - new Scenario("thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), - new Scenario("thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults), - new Scenario("thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults", - SPARQLQueryBuilderTest::thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults), - new Scenario("testWithLabelStartingWith_OLIA", - SPARQLQueryBuilderTest::testWithLabelStartingWith_OLIA) - - ); - } - - static class Scenario - { - final String name; - final FailableBiConsumer implementation; - - public Scenario(String aName, - FailableBiConsumer aImplementation) - { - name = aName; - implementation = aImplementation; - } - } - - static Repository buildSparqlRepository(String aUrl) - { - SPARQLRepository repo = new SPARQLRepository(aUrl); - repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build()); - repo.init(); - return repo; - } - - static Repository buildSparqlRepository(String aQueryUrl, String aUpdateUrl) - { - SPARQLRepository repo = new SPARQLRepository(aQueryUrl, aUpdateUrl); - repo.setHttpClient(newPerThreadSslCheckingHttpClientBuilder().build()); - repo.init(); - return repo; - } - - /** - * Checks that {@code SPARQLQueryBuilder#exists(RepositoryConnection, boolean)} can return - * {@code true} by querying for a list of all classes in {@link #DATA_CLASS_RDFS_HIERARCHY} - * which contains a number of classes. - * - * @throws Exception - * - - */ - static void thatExistsReturnsTrueWhenDataQueriedForExists(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB)); - - assertThat(result).isTrue(); - } - - /** - * If the KB has no default language set, then only labels and descriptions with no language at - * all should be returned. - * - * @throws Exception - * - - */ - static void thatOnlyLabelsAndDescriptionsWithNoLanguageAreRetrieved(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setDefaultLanguage(null); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withIdentifier("http://example.org/#green-goblin") // - .retrieveLabel() // - .retrieveDescription()); - - assertThat(results).isNotEmpty(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", "Little green monster")); - } - - /** - * If the KB has a default language set, then labels/descriptions in that language should be - * preferred it is permitted to fall back to labels/descriptions without any language. The - * dataset contains only labels for French but no descriptions, so it should fall back to - * returning the description without any language. - * - * @throws Exception - * - - */ - static void thatLabelsAndDescriptionsWithLanguageArePreferred(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - // The dataset contains only labels for French but no descriptions - aKB.setDefaultLanguage("fr"); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withIdentifier("http://example.org/#green-goblin") // - .retrieveLabel() // - .retrieveDescription()); - - assertThat(results).isNotEmpty(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Goblin vert", "Little green monster", "fr")); - } - - static void thatSearchOverMultipleLabelsWorks(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_MULTIPLE_LABELS); - - for (String term : asList("specimen", "sample", "instance", "case")) { - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelMatchingAnyOf(term) // - .retrieveLabel()); - - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#example", term)); - } - } - - static void thatMatchingAgainstAdditionalSearchPropertiesWorks(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setLabelIri("http://www.w3.org/2000/01/rdf-schema#prefLabel"); - aKB.setAdditionalMatchingProperties(asList("http://www.w3.org/2000/01/rdf-schema#label")); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_ADDITIONAL_SEARCH_PROPERTIES); - - for (String term : asList("specimen", "sample", "instance", "case")) { - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelMatchingAnyOf(term) // - .retrieveLabel()); - - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name") - .containsExactlyInAnyOrder( - new KBHandle("http://example.org/#example", "specimen")); - } - } - - static void thatMatchingAgainstAdditionalSearchPropertiesWorks2(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setLabelIri("http://www.w3.org/2000/01/rdf-schema#prefLabel"); - aKB.setAdditionalMatchingProperties(asList("http://www.w3.org/2000/01/rdf-schema#label")); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_ADDITIONAL_SEARCH_PROPERTIES_2); - - var queriesWithMatchTerms = asList(// - Pair.of("hand", // - asList("Hand structure (body structure)", "Hand structure", "Hand")), - Pair.of("hand structure", // - asList("Hand structure (body structure)", "Hand structure", "Hand")), - Pair.of("body structure", // - asList("Hand structure (body structure)", "Hand structure"))); - - for (var queryPair : queriesWithMatchTerms) { - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelMatchingAnyOf(queryPair.getKey()) // - .retrieveLabel()); - - var expectedKBHandle = new KBHandle("http://example.org/#example", - "Hand structure (body structure)"); - queryPair.getValue().forEach(v -> expectedKBHandle.addMatchTerm(v, null)); - - assertThat(results) // - .usingRecursiveFieldByFieldElementComparator( - RecursiveComparisonConfiguration.builder() // - .withComparedFields("identifier", "name", "matchTerms") // - .withIgnoredCollectionOrderInFields("matchTerms") // - .build()) - .containsExactlyInAnyOrder(expectedKBHandle); - } - } - - /** - * Checks that {@code SPARQLQueryBuilder#exists(RepositoryConnection, boolean)} can return - * {@code false} by querying for the parent of a root class in - * {@link #DATA_CLASS_RDFS_HIERARCHY} which does not exist. - * - * @throws Exception - * - - */ - static void thatExistsReturnsFalseWhenDataQueriedForDoesNotExist(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - boolean result = exists(aRepository, - SPARQLQueryBuilder.forClasses(aKB).parentsOf("http://example.org/#explicitRoot")); - - assertThat(result).isFalse(); - } - - /** - * Checks that an explicitly defined class can be retrieved using its identifier. - * - * @throws Exception - * - - */ - static void thatExplicitClassCanBeRetrievedByItsIdentifier(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) - .withIdentifier("http://example.org/#explicitRoot")); - - assertThat(result).isTrue(); - } - - /** - * Checks that an implicitly defined class can be retrieved using its identifier. - * - * @throws Exception - * - - */ - static void thatImplicitClassCanBeRetrievedByItsIdentifier(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) - .withIdentifier("http://example.org/#implicitRoot")); - - assertThat(result).isTrue(); - } - - /** - * Checks that a either explicitly nor implicitly defined class can be retrieved using its - * identifier. - * - * @throws Exception - * - - */ - static void thatNonClassCannotBeRetrievedByItsIdentifier(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - boolean result = exists(aRepository, SPARQLQueryBuilder.forClasses(aKB) - .withIdentifier("http://example.org/#DoesNotExist")); - - assertThat(result).isFalse(); - } - - /** - * Checks that item information can be obtained for a given subject. - * - * @throws Exception - * - - */ - static void thatCanRetrieveItemInfoForIdentifier(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withIdentifier("http://example.org/#red-goblin") // - .retrieveLabel() // - .retrieveDescription()); - - assertThat(results).isNotEmpty(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#red-goblin", - "Red Goblin", "Little red monster")); - } - - @SuppressWarnings("deprecation") - static void thatAllPropertiesCanBeRetrieved(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forProperties(aKB) // - .retrieveLabel() // - .retrieveDescription() // - .retrieveDomainAndRange()); - - assertThat(results).isNotEmpty(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "range", "domain") - .containsExactlyInAnyOrder( - new KBHandle("http://example.org/#property-1", "Property 1", "Property One", - null, "http://example.org/#explicitRoot", - "http://www.w3.org/2001/XMLSchema#string"), - new KBHandle("http://example.org/#property-2", "Property 2", "Property Two", - null, "http://example.org/#subclass1", - "http://www.w3.org/2001/XMLSchema#Integer"), - new KBHandle("http://example.org/#property-3", "Property 3", - "Property Three"), - new KBHandle("http://example.org/#subproperty-1-1", "Subproperty 1-1", - "Property One-One"), - new KBHandle("http://example.org/#subproperty-1-1-1", "Subproperty 1-1-1", - "Property One-One-One")); - } - - static void thatPropertyQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forProperties(aKB) // - .descendantsOf("http://example.org/#property-1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#subproperty-1-1", - "http://example.org/#subproperty-1-1-1"); - } - - @Tag("slow") - @Test - public void thatPropertyQueryListWorks_Wikidata() - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, - SPARQLQueryBuilder.forProperties(kb).limit(10)); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).hasSize(10); - } - - @Tag("slow") - @Test - public void thatPropertyQueryLabelStartingWith_Wikidata() - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, - SPARQLQueryBuilder.forProperties(kb).withLabelStartingWith("educated")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().startsWith("educated")); - } - - static void thatPropertyQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); - - List results = asHandles(aRepository, - SPARQLQueryBuilder.forProperties(aKB).childrenOf("http://example.org/#property-1")); - - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getIdentifier) - .containsExactlyInAnyOrder("http://example.org/#subproperty-1-1"); - } - - static void thatPropertyQueryLimitedToDomainDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_PROPERTIES); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forProperties(aKB) - .matchingDomain("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getIdentifier).containsExactlyInAnyOrder( - // property-1 is inherited by #subclass1 from #explicitRoot - "http://example.org/#property-1", - // property-2 is declared on #subclass1 - "http://example.org/#property-2", - // property-3 defines no domain - "http://example.org/#property-3"); - // other properties all either define or inherit an incompatible domain - } - - static void thatQueryLimitedToRootClassesDoesNotReturnOutOfScopeResults(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .roots()); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .containsExactlyInAnyOrder("explicitRoot", "implicitRoot"); - } - - static void thatQueryWithExplicitRootClassDoesNotReturnOutOfScopeResults(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - aKB.setRootConcepts(asList("http://example.org/#implicitRoot")); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB).roots()); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .containsExactlyInAnyOrder("implicitRoot"); - } - - static void thatNonRootClassCanBeUsedAsExplicitRootClass(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - aKB.setRootConcepts( - asList("http://example.org/#implicitRoot", "http://example.org/#subclass2")); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB).roots()); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .containsExactlyInAnyOrder("implicitRoot", "subclass2"); - } - - static void thatQueryLimitedToClassesDoesNotReturnInstances(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forClasses(aKB)); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .noneMatch(label -> label.contains("instance")); - } - - static void thatQueryLimitedToInstancesDoesNotReturnClasses(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forInstances(aKB)); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .noneMatch(label -> label.contains("class")); - } - - static void thatClassQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .ancestorsOf("http://example.org/#subclass1-1-1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#explicitRoot", - "http://example.org/#subclass1", "http://example.org/#subclass1-1"); - } - - static void thatClassQueryLimitedToParentsDoesNotReturnOutOfScopeResults(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .parentsOf("http://example.org/#subclass1-1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#subclass1"); - } - - static void thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .childrenOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#subclass1-1"); - } - - /** - * This query tries to find all humans in the Star Trek universe - * ({@code http://www.wikidata.org/entity/Q924827}) who are named Amanda. It tests - * whether the call to {@link SPARQLQueryBuilder#childrenOf(String)} disables the FTS. If the - * FTS is not disabled, then no result would be returned because there are so many Amandas in - * Wikidata that the popular ones returned by the FTS do not include any from the Star Trek - * universe. - * - * @throws Exception - * - - */ - @Tag("slow") - @Test - public void thatClassQueryLimitedToChildrenDoesNotReturnOutOfScopeResults_Wikidata() - throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forInstances(kb) // - .childrenOf("http://www.wikidata.org/entity/Q924827") // - .withLabelStartingWith("Amanda") // - .retrieveLabel()); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://www.wikidata.org/entity/Q1412447"); - } - - static void thatClassQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .descendantsOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#subclass1-1", - "http://example.org/#subclass1-1-1"); - } - - static void thatInstanceQueryLimitedToParentsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .parentsOf("http://example.org/#1-1-1-instance-4")); - - assertThat(results).isNotEmpty(); // - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#subclass1-1-1"); - } - - static void thatInstanceQueryLimitedToAnchestorsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .ancestorsOf("http://example.org/#1-1-1-instance-4")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#explicitRoot", - "http://example.org/#subclass1", "http://example.org/#subclass1-1", - "http://example.org/#subclass1-1-1"); - } - - static void thatInstanceQueryLimitedToChildrenDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forInstances(aKB) // - .childrenOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder("http://example.org/#1-instance-1"); - } - - static void thatInstanceQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forInstances(aKB) // - .descendantsOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .allMatch(label -> label.matches("http://example.org/#1(-1)*-instance-.*")); - } - - static void thatItemQueryLimitedToChildrenDoesNotReturnOutOfScopeResults(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .childrenOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .containsExactlyInAnyOrder( // - "http://example.org/#1-instance-1", "http://example.org/#subclass1-1"); - } - - static void thatItemQueryLimitedToDescendantsDoesNotReturnOutOfScopeResults( - Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_CLASS_RDFS_HIERARCHY); - - List results = asHandles(aRepository, SPARQLQueryBuilder.forItems(aKB) // - .descendantsOf("http://example.org/#subclass1")); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .extracting(KBHandle::getIdentifier) // - .allMatch(label -> label.matches("http://example.org/#1(-1)*-instance-.*") - || label.startsWith("http://example.org/#subclass1-")); - } - - static void testWithLabelMatchingAnyOf_withLanguage_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - testWithLabelMatchingAnyOf_withLanguage(aRepository, aKB); - } - - static void testWithLabelMatchingAnyOf_withLanguage(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelMatchingAnyOf("Gobli")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.contains("Goblin")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", - "name", "language").containsExactlyInAnyOrder( - new KBHandle("http://example.org/#red-goblin", "Red Goblin"), new KBHandle( - "http://example.org/#green-goblin", "Green Goblin", null, "en")); - } - - static void testWithLabelContainingAnyOf_withLanguage_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - testWithLabelContainingAnyOf_withLanguage(aRepository, aKB); - } - - static void testWithLabelContainingAnyOf_withLanguage(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelContainingAnyOf("Goblin")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.contains("Goblin")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", - "name", "language").containsExactlyInAnyOrder( - new KBHandle("http://example.org/#red-goblin", "Red Goblin"), new KBHandle( - "http://example.org/#green-goblin", "Green Goblin", null, "en")); - } - - @Tag("slow") - @Test - public void testWithLabelContainingAnyOf_Virtuoso_withLanguage_FTS() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelContainingAnyOf("Tower")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().contains("tower")); - } - - @Tag("slow") - @Test - public void testWithLabelContainingAnyOf_Wikidata_FTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelContainingAnyOf("Tower")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().contains("tower")); - } - - @Tag("slow") - @Test - public void testWithLabelContainingAnyOf_Fuseki_FTS() throws Exception - { - assertIsReachable(zbwGnd); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); - kb.setLabelIri(RDFS.LABEL.stringValue()); - kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - - List results = asHandles(zbwGnd, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelContainingAnyOf("Schapiro-Frisch", "Stiker-Métral")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel).allMatch( - label -> label.contains("Schapiro-Frisch") || label.contains("Stiker-Métral")); - } - - @Tag("slow") - @Test - public void testWithLabelContainingAnyOf_classes_HUCIT_FTS() throws Exception - { - assertIsReachable(hucit); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - List results = asHandles(hucit, SPARQLQueryBuilder // - .forClasses(kb) // - .withLabelContainingAnyOf("work")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().contains("work")); - } - - static void testWithLabelMatchingExactlyAnyOf_withLanguage_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - testWithLabelMatchingExactlyAnyOf_withLanguage(aRepository, aKB); - } - - static void testWithLabelMatchingExactlyAnyOf_withLanguage(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelMatchingExactlyAnyOf("Green Goblin")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.equals("Green Goblin")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", null, "en")); - } - - static void testWithLabelMatchingExactlyAnyOf_subproperty_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - testWithLabelMatchingExactlyAnyOf_subproperty(aRepository, aKB); - } - - static void testWithLabelMatchingExactlyAnyOf_subproperty(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - aKB.setLabelIri(RDFS.LABEL.stringValue()); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, LABEL_SUBPROPERTY); - - // The label "Green Goblin" is not assigned directly via rdfs:label but rather via a - // subproperty of it. Thus, this test also checks if the label sub-property support works. - List results = asHandles(aRepository, - SPARQLQueryBuilder.forItems(aKB).withLabelMatchingExactlyAnyOf("Green Goblin")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.equals("Green Goblin")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", - "name", "language").containsExactlyInAnyOrder( - new KBHandle("http://example.org/#green-goblin", "Green Goblin")); - } - - static void testWithLabelStartingWith_withoutLanguage_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - testWithLabelStartingWith_withoutLanguage(aRepository, aKB); - } - - static void testWithLabelStartingWith_withoutLanguage(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, DATA_LABELS_WITHOUT_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Green")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", - "name", "language").containsExactlyInAnyOrder( - new KBHandle("http://example.org/#green-goblin", "Green Goblin")); - } - - static void testWithLabelStartingWith_withLanguage_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green Goblin")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Green")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", null, "en")); - } - - static void testWithLabelStartingWith_withLanguage_FTS_1(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - // Single word - actually, we add a wildcard here so anything that starts with "Green" - // would also be matched - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Green")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", null, "en")); - } - - static void testWithLabelStartingWith_withLanguage_FTS_2(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - // Two words with the second being very short - this is no problem for the LUCENE FTS - // and we simply add a wildcard to match "Green Go*" - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green Go")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Green Go")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", null, "en")); - } - - static void testWithLabelStartingWith_withLanguage_FTS_3(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - // Two words with the second being very short and a space following - in this case we - // assume that the user is in fact searching for "Barack Ob" and do either drop the - // last element nor add a wildcard - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green Go ")); - - assertThat(results).isEmpty(); - } - - static void testWithLabelStartingWith_withLanguage_FTS_4(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - importDataFromString(aRepository, aKB, TURTLE, TURTLE_PREFIX, - DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - - // Two words with the second being very short - this is no problem for the LUCENE FTS - // and we simply add a wildcard to match "Green Go*" - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelStartingWith("Green Go")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Green Go")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results) - .usingRecursiveFieldByFieldElementComparatorOnFields("identifier", "name", - "description", "language") - .containsExactlyInAnyOrder(new KBHandle("http://example.org/#green-goblin", - "Green Goblin", null, "en")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_1() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - // Single word - actually, we add a wildcard here so anything that starts with "Barack" - // would also be matched - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Barack")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Barack")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_2() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - // Two words with the second being very short - in this case, we drop the very short word - // so that the user doesn't stop getting suggestions while writing because Virtuoso doesn't - // do wildcards on words shorter than 4 characters - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Barack Ob")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Barack")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_3() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - // Two words with the second being very short and a space following - in this case we - // assmume that the user is in fact searching for "Barack Ob" and do either drop the - // last element nor add a wildcard - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Barack Ob ")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Barack")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Virtuoso_withLanguage_FTS_4() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - // Two words with the second being 4+ chars - we add a wildcard here so anything - // starting with "Barack Obam" should match - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Barack Obam")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Barack Obam")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Wikidata_FTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Barack")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().startsWith("barack")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_Fuseki_FTS() throws Exception - { - assertIsReachable(zbwGnd); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); - kb.setLabelIri(RDFS.LABEL.stringValue()); - kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - - List results = asHandles(zbwGnd, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Thom")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().startsWith("thom")); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_Fuseki_noFTS_STW() throws Exception - { - assertIsReachable(zbwStw); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(null); - - List results = asHandles(zbwStw, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelMatchingExactlyAnyOf("Labour")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> "Labour".equals(label)); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_Fuseki_FTS_GND() throws Exception - { - assertIsReachable(zbwGnd); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_FUSEKI.stringValue()); - kb.setLabelIri(RDFS.LABEL.stringValue()); - kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - - // The label "Gadebusch, Thomas Henricus" is not assigned directly via rdfs:label but rather - // via a subproperty of it. Thus, this test also checks if the label sub-property support - // works. - // - // gndo:variantNameForThePerson "Gadebusch, Thomas - // Henricus"; - // gndo:variantNameEntityForThePerson _:node1fhgbdto1x8884759 . - List results = asHandles(zbwGnd, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelMatchingExactlyAnyOf("Gadebusch, Thomas Henricus")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> "Gadebusch, Thomas Henricus".equals(label)); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_Wikidata_noFTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(null); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelMatchingExactlyAnyOf("Labour")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> "Labour".equals(label)); - } - - @Tag("slow") - @Test - public void testWithPropertyMatchingAnyOf_Wikidata_noFTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(null); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forProperties(kb) // - .withLabelMatchingAnyOf("academic")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.toLowerCase().contains("academic")); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_Wikidata_FTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelMatchingExactlyAnyOf("Labour")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.equalsIgnoreCase("Labour")); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_multiple_Wikidata_FTS() throws Exception - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_WIKIDATA.stringValue()); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forInstances(kb) // - .withLabelMatchingExactlyAnyOf("Labour", "Tory")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> "Labour".equals(label) || "Tory".equals(label)); - } - - @Tag("slow") - @Test - public void testWithLabelMatchingExactlyAnyOf_Virtuoso_withLanguage_FTS() throws Exception - { - assertIsReachable(ukpVirtuosoRepo); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); - - List results = asHandles(ukpVirtuosoRepo, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelMatchingExactlyAnyOf("Green Goblin")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> "Green Goblin".equals(label)); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_HUCIT_noFTS() throws Exception - { - assertIsReachable(hucit); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(null); - - List results = asHandles(hucit, SPARQLQueryBuilder // - .forItems(kb) // - .withLabelStartingWith("Achilles")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Achilles")); - } - - @Tag("slow") - @Test - public void testWithLabelStartingWith_onlyDescendants_HUCIT_noFTS() throws Exception - { - assertIsReachable(hucit); - - kb.setType(REMOTE); - kb.setFullTextSearchIri(null); - - List results = asHandles(hucit, SPARQLQueryBuilder // - .forInstances(kb) // - .descendantsOf("http://erlangen-crm.org/efrbroo/F1_Work") // - .withLabelStartingWith("Achilles")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.startsWith("Achilles")); - } - - static void testWithLabelStartingWith_OLIA(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - aKB.setLabelIri("http://purl.org/olia/system.owl#hasTag"); - - importDataFromFile(aRepository, aKB, "src/test/resources/data/penn.owl"); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forInstances(aKB) // - .withLabelStartingWith("N")); - - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).isNotEmpty(); - assertThat(results).extracting(KBHandle::getUiLabel).containsExactlyInAnyOrder("NN", "NNP", - "NNPS", "NNS"); - } - - @Tag("slow") - @Test - public void thatChildrenOfExplicitRootCanBeRetrieved_DBPedia() - { - assertIsReachable(dbpedia); - - kb.setType(REMOTE); - - assertThatChildrenOfExplicitRootCanBeRetrieved(kb, dbpedia, - "http://www.w3.org/2002/07/owl#Thing", 0); - } - - @Disabled("YAGO seems to have problem atm 29-04-2023") - @Tag("slow") - @Test - public void thatChildrenOfExplicitRootCanBeRetrieved_YAGO() - { - assertIsReachable(yago); - - kb.setType(REMOTE); - - // YAGO has the habit of timing out on some requests. Unfortunately, there is no clear - // pattern when this happens - might be due to server load on the YAGO side. Thus, to - // keep the load lower, we only validate 5 children. - assertThatChildrenOfExplicitRootCanBeRetrieved(kb, yago, "http://schema.org/Thing", 5); - } - - @Tag("slow") - @Test - public void thatParentsCanBeRetrieved_Wikidata() - { - assertIsReachable(wikidata); - - kb.setType(REMOTE); - initWikidataMapping(); - - List results = asHandles(wikidata, SPARQLQueryBuilder // - .forClasses(kb) // - .ancestorsOf("http://www.wikidata.org/entity/Q5") // - .retrieveLabel()); - - assertThat(results).isNotEmpty(); - assertThat(results) // - .as("Root concept http://www.wikidata.org/entity/Q35120 should be included") // - .extracting(KBHandle::getIdentifier) // - .contains("http://www.wikidata.org/entity/Q35120"); - } - - @Tag("slow") - @Test - public void thatRootsCanBeRetrieved_DBPedia() - { - assertIsReachable(dbpedia); - - kb.setType(REMOTE); - - List results = asHandles(dbpedia, - SPARQLQueryBuilder.forClasses(kb).roots().retrieveLabel()); - - assertThat(results).isNotEmpty(); - - assertThat(results) // - .extracting(KBHandle::getUiLabel) // - .contains("Thing"); - } - - @Tag("slow") - @Test - public void thatParentsCanBeRetrieved_DBPedia() - { - assertIsReachable(dbpedia); - - kb.setType(REMOTE); - - List results = asHandles(dbpedia, SPARQLQueryBuilder // - .forClasses(kb) // - .ancestorsOf("http://dbpedia.org/ontology/Organisation") // - .retrieveLabel()); - - assertThat(results).isNotEmpty(); - - assertThat(results) // - .extracting(KBHandle::getName) // - .contains("agent", "Thing"); - } - - static void testWithLabelContainingAnyOf_pets_ttl_noFTS(Repository aRepository, - KnowledgeBase aKB) - throws Exception - { - aKB.setFullTextSearchIri(null); - - importDataFromFile(aRepository, aKB, "src/test/resources/data/pets.ttl"); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forItems(aKB) // - .withLabelContainingAnyOf("Socke")); - - assertThat(results).extracting(KBHandle::getUiLabel) - .allMatch(label -> label.contains("Socke")); - assertThat(results).extracting(KBHandle::getIdentifier).doesNotHaveDuplicates(); - assertThat(results).usingRecursiveFieldByFieldElementComparatorOnFields("identifier", - "name", "language") - .containsExactlyInAnyOrder(new KBHandle("http://mbugert.de/pets#socke", "Socke")); - } - - static void thatRootsCanBeRetrieved_ontolex(Repository aRepository, KnowledgeBase aKB) - throws Exception - { - importDataFromFile(aRepository, aKB, - "src/test/resources/data/wordnet-ontolex-ontology.owl"); - - initOwlMapping(aKB); - - List results = asHandles(aRepository, SPARQLQueryBuilder // - .forClasses(aKB) // - .roots() // - .retrieveLabel()); - - assertThat(results).isNotEmpty(); - - assertThat(results).extracting(KBHandle::getUiLabel).contains("Adjective position", - "Lexical domain", "Part of speech", "Phrase type", "Synset"); - } - @Test public void thatLineBreaksAreSanitized() throws Exception { assertThat(sanitizeQueryString_FTS("Green\n\rGoblin")).isEqualTo("Green Goblin"); } - - static void importDataFromFile(Repository aRepository, KnowledgeBase aKB, String aFilename) - throws IOException - { - // Detect the file format - RDFFormat format = Rio.getParserFormatForFileName(aFilename).orElse(RDFXML); - - System.out.printf("Loading %s data from %s%n", format, aFilename); - - // Load files into the repository - try (InputStream is = new FileInputStream(aFilename)) { - importData(aRepository, aKB, format, is); - } - } - - static void importDataFromString(Repository aRepository, KnowledgeBase aKB, RDFFormat aFormat, - String... aRdfData) - throws IOException - { - String data = String.join("\n", aRdfData); - - // Load files into the repository - try (InputStream is = IOUtils.toInputStream(data, UTF_8)) { - importData(aRepository, aKB, aFormat, is); - } - } - - static void importData(Repository aRepository, KnowledgeBase aKB, RDFFormat aFormat, - InputStream aIS) - throws IOException - { - try (RepositoryConnection conn = aRepository.getConnection()) { - // If the RDF file contains relative URLs, then they probably start with a hash. - // To avoid having two hashes here, we drop the hash from the base prefix configured - // by the user. - String prefix = StringUtils.removeEnd(aKB.getBasePrefix(), "#"); - if (aKB.getDefaultDatasetIri() != null) { - var ctx = SimpleValueFactory.getInstance().createIRI(aKB.getDefaultDatasetIri()); - conn.add(aIS, prefix, aFormat, ctx); - } - else { - conn.add(aIS, prefix, aFormat); - } - } - } - - static void initRdfsMapping(KnowledgeBase aKB) - { - aKB.setClassIri(RDFS.CLASS.stringValue()); - aKB.setSubclassIri(RDFS.SUBCLASSOF.stringValue()); - aKB.setTypeIri(RDF.TYPE.stringValue()); - aKB.setLabelIri(RDFS.LABEL.stringValue()); - aKB.setPropertyTypeIri(RDF.PROPERTY.stringValue()); - aKB.setDescriptionIri(RDFS.COMMENT.stringValue()); - // We are intentionally not using RDFS.LABEL here to ensure we can test the label - // and property label separately - aKB.setPropertyLabelIri(SKOS.PREF_LABEL.stringValue()); - // We are intentionally not using RDFS.COMMENT here to ensure we can test the description - // and property description separately - aKB.setPropertyDescriptionIri("http://schema.org/description"); - aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - } - - static void initOwlMapping(KnowledgeBase aKB) - { - aKB.setClassIri(OWL.CLASS.stringValue()); - aKB.setSubclassIri(RDFS.SUBCLASSOF.stringValue()); - aKB.setTypeIri(RDF.TYPE.stringValue()); - aKB.setLabelIri(RDFS.LABEL.stringValue()); - aKB.setPropertyTypeIri(RDF.PROPERTY.stringValue()); - aKB.setDescriptionIri(RDFS.COMMENT.stringValue()); - aKB.setPropertyLabelIri(RDF.PROPERTY.stringValue()); - aKB.setPropertyDescriptionIri(RDFS.COMMENT.stringValue()); - aKB.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); - } - - private void initWikidataMapping() - { - kb.setClassIri("http://www.wikidata.org/entity/Q35120"); - kb.setSubclassIri("http://www.wikidata.org/prop/direct/P279"); - kb.setTypeIri("http://www.wikidata.org/prop/direct/P31"); - kb.setLabelIri("http://www.w3.org/2000/01/rdf-schema#label"); - kb.setPropertyTypeIri("http://www.wikidata.org/entity/Q18616576"); - kb.setDescriptionIri("http://schema.org/description"); - kb.setPropertyLabelIri("http://www.w3.org/2000/01/rdf-schema#label"); - kb.setPropertyDescriptionIri("http://www.w3.org/2000/01/rdf-schema#comment"); - kb.setSubPropertyIri("http://www.wikidata.org/prop/direct/P1647"); - } - - public static void assertIsReachable(Repository aRepository) - { - if (!(aRepository instanceof SPARQLRepository)) { - return; - } - - SPARQLRepository sparqlRepository = (SPARQLRepository) aRepository; - - assumeTrue(isReachable(sparqlRepository.toString()), - "Remote repository at [" + sparqlRepository + "] is not reachable"); - } } diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogRepositoryTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogRepositoryTest.java index bd1a94d7dfe..b42f07970b9 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogRepositoryTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/StardogRepositoryTest.java @@ -20,13 +20,13 @@ import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_STARDOG; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; +import static java.time.Duration.ofMinutes; import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryConnection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -34,22 +34,36 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import de.tudarmstadt.ukp.inception.kb.RepositoryType; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; -import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.Scenario; +import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.Scenario; // The repository has to exist and Stardog has to be running for this test to run. // To create the repository, use the following command: // stardog-admin db create -n test -o search.enabled=true -- -@Disabled("Requires manually setting up a test server") +@Disabled("Requires manually setting up a test server AND license key!") +@Testcontainers(disabledWithoutDocker = true) public class StardogRepositoryTest { + private static final int STARDOG_PORT = 8890; + + @Container + private static final GenericContainer STARDOG = new GenericContainer<>( + "stardog/stardog:latest") // + .withExposedPorts(STARDOG_PORT) // + .waitingFor(Wait.forHttp("/").forPort(STARDOG_PORT) + .withStartupTimeout(ofMinutes(2))); + private Repository repository; private KnowledgeBase kb; @BeforeEach - public void setUp(TestInfo aTestInfo) + public void setUp(TestInfo aTestInfo) throws Exception { String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); System.out.printf("\n=== %s === %s =====================\n", methodName, @@ -57,19 +71,24 @@ public void setUp(TestInfo aTestInfo) suspendSslVerification(); + STARDOG.execInContainer("stardog-admin", "db", "create", "-n", "test", "-o", + "search.enabled=true", "--"); + kb = new KnowledgeBase(); kb.setDefaultLanguage("en"); kb.setType(RepositoryType.REMOTE); kb.setFullTextSearchIri(FTS_STARDOG.stringValue()); kb.setMaxResults(100); - SPARQLQueryBuilderTest.initRdfsMapping(kb); + SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping(kb); - repository = SPARQLQueryBuilderTest.buildSparqlRepository( - "http://admin:admin@localhost:5820/test/query", - "http://admin:admin@localhost:5820/test/update"); + repository = SPARQLQueryBuilderLocalTestScenarios.buildSparqlRepository( + "http://admin:admin@" + STARDOG.getHost() + ":" + + STARDOG.getMappedPort(STARDOG_PORT) + "/test/query", + "http://admin:admin@" + STARDOG.getHost() + ":" + + STARDOG.getMappedPort(STARDOG_PORT) + "/test/update"); - try (RepositoryConnection conn = repository.getConnection()) { + try (var conn = repository.getConnection()) { conn.clear(); } } @@ -82,7 +101,7 @@ public void tearDown() private static List tests() throws Exception { - return SPARQLQueryBuilderTest.tests().stream() // + return SPARQLQueryBuilderLocalTestScenarios.tests().stream() // .map(scenario -> Arguments.of(scenario.name, scenario)) .collect(Collectors.toList()); } diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/VirtuosoRepositoryTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/VirtuosoRepositoryTest.java index 3b76b136093..78a9cb9b686 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/VirtuosoRepositoryTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/querybuilder/VirtuosoRepositoryTest.java @@ -20,15 +20,17 @@ import static de.tudarmstadt.ukp.inception.kb.IriConstants.FTS_VIRTUOSO; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.restoreSslVerification; import static de.tudarmstadt.ukp.inception.kb.http.PerThreadSslCheckingHttpClientUtils.suspendSslVerification; +import static java.time.Duration.ofMinutes; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.List; -import java.util.stream.Collectors; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryConnection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -36,10 +38,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import de.tudarmstadt.ukp.inception.kb.RepositoryType; import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; -import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderTest.Scenario; +import de.tudarmstadt.ukp.inception.kb.querybuilder.SPARQLQueryBuilderLocalTestScenarios.Scenario; // The repository has to exist and Virtuoso has to be running for this test to run. // To create the repository, use the following steps: @@ -48,38 +56,72 @@ // Go to http://localhost:8890/conductor -> System Admin -> User Accounts -> SPARQL and add account role SPARQL_UPDATE // Check if FTS is enabled: `SELECT * from DB.DBA.RDF_OBJ_FT_RULES;` // Enable FTS: `DB.DBA.RDF_OBJ_FT_RULE_ADD (null, null, 'ALL');` -@Disabled("Requires manually setting up a test server") +@Disabled("It seems the FTS is not properly flushing its data and tests may fail kind of randomly") +@Testcontainers(disabledWithoutDocker = true) public class VirtuosoRepositoryTest { + private static final String GRAPH_IRI = "http://testgraph"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final int VIRTUOSO_PORT = 8890; + + @Container + private static final GenericContainer VIRTUOSO = new GenericContainer<>( + "openlink/virtuoso-opensource-7:latest") // + .withEnv("DBA_PASSWORD", "secret") // + .withExposedPorts(VIRTUOSO_PORT) // + .waitingFor(Wait.forHttp("/sparql").forPort(VIRTUOSO_PORT) + .withStartupTimeout(ofMinutes(2))); + private Repository repository; + private KnowledgeBase kb; @BeforeEach - public void setUp(TestInfo aTestInfo) + public void setUp(TestInfo aTestInfo) throws Exception { String methodName = aTestInfo.getTestMethod().map(Method::getName).orElse(""); System.out.printf("\n=== %s === %s =====================\n", methodName, aTestInfo.getDisplayName()); + assertThat(VIRTUOSO.isRunning()).isTrue(); + + virtuosoSqlCommand("DB.DBA.RDF_OBJ_FT_RULE_ADD (null, null, 'ALL');"); + virtuosoSqlCommand("DB.DBA.VT_BATCH_UPDATE ('DB.DBA.RDF_OBJ', 'OFF', null);"); + suspendSslVerification(); kb = new KnowledgeBase(); kb.setDefaultLanguage("en"); - kb.setDefaultDatasetIri("http://testgraph"); + kb.setDefaultDatasetIri(GRAPH_IRI); kb.setType(RepositoryType.REMOTE); kb.setFullTextSearchIri(FTS_VIRTUOSO.stringValue()); kb.setMaxResults(100); - SPARQLQueryBuilderTest.initRdfsMapping(kb); + SPARQLQueryBuilderLocalTestScenarios.initRdfsMapping(kb); - repository = SPARQLQueryBuilderTest.buildSparqlRepository("http://localhost:8890/sparql/"); + repository = SPARQLQueryBuilderLocalTestScenarios + .buildSparqlRepository("http://dba:secret@" + VIRTUOSO.getHost() + ":" + + VIRTUOSO.getMappedPort(VIRTUOSO_PORT) + "/sparql-auth/"); - try (RepositoryConnection conn = repository.getConnection()) { + try (var conn = repository.getConnection()) { var ctx = SimpleValueFactory.getInstance().createIRI(kb.getDefaultDatasetIri()); conn.clear(ctx); } } + static void virtuosoSqlCommand(String command) throws IOException, InterruptedException + { + command = command.replace("\"", "\\\""); + LOG.info("Running virtuoso command: {}", command); + var result = VIRTUOSO.execInContainer("sh", "-c", + "echo \"" + command + "\" | isql -U dba -P secret"); + LOG.info("Command exit code: {}", result.getExitCode()); + LOG.info("Command stdout: {}", result.getStdout()); + LOG.info("Command stderr: {}", result.getStderr()); + } + @AfterEach public void tearDown() { @@ -100,10 +142,10 @@ private static List tests() throws Exception // Virtuoso does not like to import the test data for this one "testWithLabelStartingWith_OLIA"); - return SPARQLQueryBuilderTest.tests().stream() // - .filter(scenario -> !exclusions.contains(scenario.name)) - .map(scenario -> Arguments.of(scenario.name, scenario)) - .collect(Collectors.toList()); + return SPARQLQueryBuilderLocalTestScenarios.tests().stream() // + .filter(scenario -> !exclusions.contains(scenario.name)) // + .map(scenario -> Arguments.of(scenario.name, scenario)) // + .toList(); } @ParameterizedTest(name = "{index}: test {0}") diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/NoReificationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/NoReificationTest.java index 40447d7f350..91276f80e15 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/NoReificationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/NoReificationTest.java @@ -26,11 +26,11 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rdf4j.model.vocabulary.OWL; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.model.vocabulary.SKOS; import org.eclipse.rdf4j.repository.Repository; -import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.rio.RDFFormat; import org.eclipse.rdf4j.sail.lucene.LuceneSail; @@ -105,7 +105,7 @@ public void thatItemCanBeObtainedAsStatements() throws Exception importDataFromString(RDFFormat.TURTLE, TURTLE_PREFIX, DATA_LABELS_AND_DESCRIPTIONS_WITH_LANGUAGE); - List result = listStatements(rdf4jLocalRepo, + var result = listStatements(rdf4jLocalRepo, new KBHandle("http://example.org/#green-goblin")); assertThat(result) // @@ -116,10 +116,10 @@ public void thatItemCanBeObtainedAsStatements() throws Exception public List listStatements(Repository aRepo, KBHandle aItem) throws Exception { - try (RepositoryConnection conn = aRepo.getConnection()) { - long startTime = System.currentTimeMillis(); + try (var conn = aRepo.getConnection()) { + var startTime = System.currentTimeMillis(); - List results = sut.listStatements(conn, kb, aItem, true); + var results = sut.listStatements(conn, kb, aItem, true); System.out.printf("Results : %d in %dms%n", results.size(), System.currentTimeMillis() - startTime); @@ -131,21 +131,21 @@ public List listStatements(Repository aRepo, KBHandle aItem) throws private void importDataFromString(RDFFormat aFormat, String... aRdfData) throws IOException { - String data = String.join("\n", aRdfData); + var data = String.join("\n", aRdfData); // Load files into the repository - try (InputStream is = IOUtils.toInputStream(data, UTF_8)) { + try (var is = IOUtils.toInputStream(data, UTF_8)) { importData(aFormat, is); } } private void importData(RDFFormat aFormat, InputStream aIS) throws IOException { - try (RepositoryConnection conn = rdf4jLocalRepo.getConnection()) { + try (var conn = rdf4jLocalRepo.getConnection()) { // If the RDF file contains relative URLs, then they probably start with a hash. // To avoid having two hashes here, we drop the hash from the base prefix configured // by the user. - String prefix = StringUtils.removeEnd(kb.getBasePrefix(), "#"); + var prefix = StringUtils.removeEnd(kb.getBasePrefix(), "#"); conn.add(aIS, prefix, aFormat); } } @@ -164,6 +164,7 @@ private void initRdfsMapping() // We are intentionally not using RDFS.COMMENT here to ensure we can test the description // and property description separately kb.setPropertyDescriptionIri("http://schema.org/description"); + kb.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); kb.setSubPropertyIri(RDFS.SUBPROPERTYOF.stringValue()); } } diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/WikiDataReificationTest.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/WikiDataReificationTest.java index aebed335c64..a58084fa4f0 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/WikiDataReificationTest.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/reification/WikiDataReificationTest.java @@ -29,6 +29,7 @@ import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.OWL; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; @@ -96,6 +97,7 @@ public void setUp() kb.setDescriptionIri("http://schema.org/description"); kb.setPropertyLabelIri("http://www.w3.org/2000/01/rdf-schema#label"); kb.setPropertyDescriptionIri("http://www.w3.org/2000/01/rdf-schema#comment"); + kb.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); kb.setSubPropertyIri("http://www.wikidata.org/prop/direct/P1647"); // Local in-memory store - this should be used for most tests because we can diff --git a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java index 58b1f636500..f45d5429dd2 100644 --- a/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java +++ b/inception/inception-kb/src/test/java/de/tudarmstadt/ukp/inception/kb/util/TestFixtures.java @@ -42,6 +42,7 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.OWL; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.RDFS; import org.eclipse.rdf4j.model.vocabulary.SKOS; @@ -107,6 +108,7 @@ public KnowledgeBase buildKnowledgeBase(Project project, String name, Reificatio // querying for properties with the class label. kb.setPropertyLabelIri(SKOS.PREF_LABEL.stringValue()); kb.setPropertyDescriptionIri(SKOS.DEFINITION.stringValue()); + kb.setDeprecationPropertyIri(OWL.DEPRECATED.stringValue()); kb.setRootConcepts(new ArrayList<>()); kb.setReification(reification); kb.setMaxResults(1000); diff --git a/inception/inception-kb/src/test/resources/data/README.txt b/inception/inception-kb/src/test/resources/data/README.txt index 8e5ba9ba9a8..76407652240 100644 --- a/inception/inception-kb/src/test/resources/data/README.txt +++ b/inception/inception-kb/src/test/resources/data/README.txt @@ -21,3 +21,9 @@ License: https://www.w3.org/Consortium/Legal/2015/copyright-software-and-doc Source: http://wordnet-rdf.princeton.edu/ontology License: http://wordnet-rdf.princeton.edu/license + + +== example1.obo + +Source: https://raw.githubusercontent.com/owlcs/owlapi/version5/oboformat/src/test/resources/example1.obo +License: Apache License 2.0 (https://github.com/owlcs/owlapi/blob/version5/README.md) diff --git a/inception/inception-kb/src/test/resources/data/example1.obo b/inception/inception-kb/src/test/resources/data/example1.obo new file mode 100644 index 00000000000..12ed94b68eb --- /dev/null +++ b/inception/inception-kb/src/test/resources/data/example1.obo @@ -0,0 +1,190 @@ +format-version: 1.2 +data-version: releases/2018-09-25 +saved-by: cooperl +subsetdef: Angiosperm "Term for angiosperms" +subsetdef: Arabidopsis "Term used for Arabidopsis" +subsetdef: Bryophytes "Term used for mosses, liverworts, and/or hornworts" +subsetdef: Citrus "Term used for citrus" +subsetdef: CL "plant cells" +subsetdef: Gymnosperms "Term used for gymnosperms" +subsetdef: Maize "Term used for maize" +subsetdef: Musa "Terms used for banana" +subsetdef: Poaceae "Term used for grasses" +subsetdef: Potato "Term used for potato" +subsetdef: Pteridophytes "Term used for ferns and allies" +subsetdef: reference "reference plant structure term" +subsetdef: Rice "Term used for rice" +subsetdef: Tomato "Term used for tomato" +subsetdef: TraitNet "Plant Functional Traits" +synonymtypedef: German "German synonym (exact)" EXACT +synonymtypedef: Japanese "Japanese synonym (exact)" EXACT +synonymtypedef: Plural "Plural" +synonymtypedef: Spanish "Spanish synonym (exact)" EXACT +default-namespace: plant_ontology +treat-xrefs-as-genus-differentia: CL part_of NCBITaxon:33090 +treat-xrefs-as-is_a: CARO +! We want the file to load without network access, so the import is commented out +!import: http://purl.obolibrary.org/obo/po/imports/ncbitaxon_import.owl +ontology: po +property_value: dc-contributor https://orcid.org/0000-0001-5889-4463 +property_value: dc-description "This is a sample ontology." xsd:string +property_value: dc-publisher http://po.org +property_value: dc-source "aggregates AAO from 13:04:2012" xsd:string +property_value: dc-source http://dbpedia.org +property_value: has_ontology_root_term UBERON:0000104 + +[Term] +id: PO:0000001 +name: plant embryo proper +namespace: plant_anatomy +def: "An embryonic plant structure (PO:0025099) that is the body of a developing plant embryo (PO:0009009) attached to the maternal tissue in an plant ovule (PO:0020003) by a suspensor (PO:0020108)." [POC:Laurel_Cooper, POC:Ramona_Walls] +comment: The embryo proper is the entire embryo exclusive of the suspensor. +synonym: "embrióforo (Spanish, exact)" EXACT Spanish [POC:Maria_Alejandra_Gandolfo] +synonym: "胚本体 (Japanese, exact)" EXACT Japanese [NIG:Yukiko_Yamazaki] +xref: PO_GIT:246 +customTag: custom value +property_value: dc-source http://purl.obolibrary.org/obo/aao.owl +property_value: dc-source "ISBN:0030229073 Invertebrate Zoology, Barnes" xsd:string +property_value: depicted_by http://upload.wikimedia.org/wikipedia/commons/4/41/Parts_of_feather_modified.jpg +property_value: has_ontology_root_term UBERON:0000104 +property_value: seeAlso http://xkcd.com/1104/ +property_value: structure_notes "Some notes" xsd:string + +[Term] +id: PO:0000002 +name: anther wall +namespace: plant_anatomy +alt_id: PO:0006445 +alt_id: PO:0006477 +def: "A microsporangium wall (PO:0025307) that is part of an anther (PO:0009066)." [ISBN:0471244554, ISBN:9780003686647] +comment: Has an outer epidermis (exothecium) and an endothecium and may have additional layers. If you are annotating to this structure for Zea mays or other grasses, please also add an annotation to the corresponding floret type. Choose the most specific term possible from: spikelet floret (PO:0009082), tassel floret (PO:0006310), lower floret of pedicellate spikelet of tassel (PO:0006313), lower floret of sessile spikelet of tassel (PO:0006315), upper floret of pedicellate spikelet of tassel (PO:0006314), upper floret of sessile spikelet of tassel (PO:0006316). +subset: Angiosperm +subset: reference +synonym: "pared de la antera (Spanish, exact)" EXACT Spanish [POC:Maria_Alejandra_Gandolfo] +synonym: "Poaceae anther wall (narrow)" NARROW [] +synonym: "pollen sac wall (exact)" EXACT [] +synonym: "Zea anther wall (narrow)" NARROW [] +synonym: "葯壁 (Japanese, exact)" EXACT Japanese [NIG:Yukiko_Yamazaki] +xref: PO_GIT:149 +xref: PO_GIT:298 +is_a: PO:0000001 ! plant embryo proper + +[Term] +id: PO:0000003 +name: whole plant +namespace: plant_anatomy +def: "A plant structure (PO:0005679) which is a whole organism." [POC:curators] +comment: Examples include plant embryo (PO:0009009), megagametophyte (PO:0025279) and microgametophyte (PO:0025280). +subset: reference +subset: TraitNet +synonym: "bush (narrow)" NARROW [] +synonym: "clonal colony (related)" RELATED [] +synonym: "colony (related)" RELATED [] +synonym: "frutex (narrow)" NARROW [FNA:e4dde193-57f7-4ab9-9d25-96b4ca0088ba] +synonym: "frutices (narrow)" NARROW Plural [FNA:ec8c2064-2a67-43d7-8e14-aecfef5cf33b] +synonym: "gametophyte (narrow)" NARROW [] +synonym: "genet (broad)" BROAD [] +synonym: "herb (narrow)" NARROW [] +synonym: "liana (narrow)" NARROW [] +synonym: "planta entera (Spanish, exact)" EXACT Spanish [POC:Maria_Alejandra_Gandolfo] +synonym: "prothalli (narrow)" NARROW Plural [FNA:4b610104-1bb0-4c6b-9bb9-e3cc61d11ac0] +synonym: "prothallium (narrow)" NARROW [] +synonym: "prothallus (narrow)" NARROW [FNA:f8f31520-e4bc-4430-9274-8dd3cee7ffd8] +synonym: "ramet (broad)" BROAD [] +synonym: "seedling (narrow)" NARROW [] +synonym: "shrub (narrow)" NARROW [] +synonym: "sporophyte (narrow)" NARROW [] +synonym: "suffrutex (narrow)" NARROW [FNA:99508f62-7116-4e2b-90c0-19ff55ebd967] +synonym: "suffrutices (narrow)" NARROW Plural [FNA:ba1b1bd5-75bd-4195-b11c-3aba08da08c2] +synonym: "tree (narrow)" NARROW [] +synonym: "vine (narrow)" NARROW [] +synonym: "woody clump (narrow)" NARROW [FNA:c1ccca7d-2a98-4a9d-8603-c34b551935e0] +synonym: "植物体全体 (Japanese, exact)" EXACT Japanese [NIG:Yukiko_Yamazaki] +xref: PO_GIT:538 +xref: PO_GIT:69 +is_a: PO:0000001 ! plant embryo proper + +[Typedef] +id: adjacent_to +name: adjacent_to +xref: RO:0002220 + +[Typedef] +id: derives_by_manipulation_from +name: derives_by_manipulation_from +def: "A derives_by_manipulation_from B means: A is a type of in vitro plant structure, and every instance of A exists at a point in time later than some instance of B from which it was created through human manipulation, and every instance of A inherited a biologically significant portion of its matter from the instance of B from which it was derived." [POC:curators] +comment: The derives_by_manipulation_from relation, is a special case of the RO relation derives_from. Derives_from implies an instance level derivation that holds between two distinct material continuants when one succeeds the other across a temporal divide in such a way that at least a biologically significant portion of the matter of the earlier continuant is inherited by the later. Likewise, derives_by_manipulation_from joins two plant structures before and after some manipulation. Specifically, derives_by_manipulation_from is used to describe the relation between an in vitro plant structure and the in vivo plant structure that is its temporal precursor. +created_by: rwalls +creation_date: 2010-07-01T02:45:12Z + +[Typedef] +id: developmentally_preceded_by +name: developmentally_preceded_by +def: "X developmentally related to y if and only if there exists some developmental process (GO:0032502) p such that x and y both participates in p, and x is the output of p and y is the input of p." [RO:0002258] +xref: RO:0002258 +is_obsolete: true +created_by: Laurel_Cooper +creation_date: 2013-06-27T13:16:56Z + +[Typedef] +id: develops_from +name: develops_from +xref: RO:0002202 +is_transitive: true + +[Typedef] +id: has_part +name: has_part +xref: BFO:0000051 +is_transitive: true + +[Typedef] +id: has_participant +name: has_participant +xref: BFO:0000057 + +[Typedef] +id: isolated_from_germplasm +name: isolated from germplasm + +[Typedef] +id: located_in +name: located_in +xref: RO:0001025 +created_by: rwalls +creation_date: 2012-05-23T09:52:02Z + +[Typedef] +id: only_in_taxon +name: only_in_taxon +xref: RO:0002160 + +[Typedef] +id: part_of +name: part_of +xref: BFO:0000050 +is_transitive: true + +[Typedef] +id: participates_in +name: participates_in +xref: BFO:0000056 +holds_over_chain: part_of participates_in +inverse_of: has_participant ! has_participant + +[Typedef] +id: preceded_by +name: preceded_by +def: "The assertion P preceded_by P1 tells us something about Ps in general: that is, it tells us something about what happened earlier, given what we know about what happened later. Thus it does not provide information pointing in the opposite direction, concerning instances of P1 in general; that is, that each is such as to be succeeded by some instance of P." [POC:Laurel_Cooper] +comment: Note that an assertion to the effect that P preceded_by P1 is rather weak; it tells us little about the relations between the underlying instances in virtue of which the preceded_by relation obtains. Typically we will be interested in stronger relations, for example in the relation immediately_preceded_by, or in relations which combine preceded_by with a condition to the effect that the corresponding instances of P and P1 share participants, or that their participants are connected by relations of derivation, or (as a first step along the road to a treatment of causality) that the one process in some way affects (for example, initiates or regulates) the other. +xref: BFO:0000062 +is_transitive: true +created_by: Laurel_Cooper +creation_date: 2013-07-09T14:29:15Z + +[Typedef] +id: precedes +name: precedes +xref: BFO:0000063 +created_by: Laurel_Cooper +creation_date: 2015-06-04T17:18:40Z diff --git a/inception/inception-kb/src/test/resources/data/example1.obo.gz b/inception/inception-kb/src/test/resources/data/example1.obo.gz new file mode 100644 index 00000000000..6f3de66de9c Binary files /dev/null and b/inception/inception-kb/src/test/resources/data/example1.obo.gz differ diff --git a/inception/inception-kb/src/test/resources/kb_test_profiles.yaml b/inception/inception-kb/src/test/resources/kb_test_profiles.yaml index 985d863acc1..e4d752594d1 100644 --- a/inception/inception-kb/src/test/resources/kb_test_profiles.yaml +++ b/inception/inception-kb/src/test/resources/kb_test_profiles.yaml @@ -17,6 +17,7 @@ test_profile: property-label: http://www.w3.org/2000/01/rdf-schema#label property-description: http://www.w3.org/2000/01/rdf-schema#comment subproperty-of: http://www.w3.org/2000/01/rdf-schema#subPropertyOf + deprecation-property: http://www.w3.org/2002/07/owl#deprecated info: description: This is a knowledge base for testing the kb profiles host-institution-name: a host diff --git a/inception/inception-layer-docmetadata/pom.xml b/inception/inception-layer-docmetadata/pom.xml index c26ace1f5e3..8aa2a661f7e 100644 --- a/inception/inception-layer-docmetadata/pom.xml +++ b/inception/inception-layer-docmetadata/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-layer-docmetadata INCEpTION - Document Metadata Support diff --git a/inception/inception-log/pom.xml b/inception/inception-log/pom.xml index 6bac936eacf..345c69d4ba3 100644 --- a/inception/inception-log/pom.xml +++ b/inception/inception-log/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-log INCEpTION - Log diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventLoggingListener.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventLoggingListener.java index 9c016be3cd4..53ef44335e1 100644 --- a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventLoggingListener.java +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventLoggingListener.java @@ -20,11 +20,15 @@ import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; @@ -55,6 +59,8 @@ public class EventLoggingListener private final EventLoggingProperties properties; private final EventLoggingAdapterRegistry adapterRegistry; + private Map eventCache = new HashMap<>(); + private volatile boolean flushing = false; @Autowired @@ -71,6 +77,32 @@ public EventLoggingListener(EventRepository aRepo, EventLoggingProperties aPrope scheduler.scheduleAtFixedRate(() -> flush(), 1, 1, TimeUnit.SECONDS); } + boolean shouldLogEvent(String aEventName) + { + if (eventCache.containsKey(aEventName)) { + return eventCache.get(aEventName); + } + + boolean shouldLog; + + if (CollectionUtils.isEmpty(properties.getIncludePatterns())) { + shouldLog = true; + } + else { + shouldLog = properties.getIncludePatterns().stream() + .anyMatch(pattern -> Pattern.matches(pattern, aEventName)); + } + + if (shouldLog && !CollectionUtils.isEmpty(properties.getExcludePatterns())) { + shouldLog = properties.getExcludePatterns().stream() + .noneMatch(pattern -> Pattern.matches(pattern, aEventName)); + } + + eventCache.put(aEventName, shouldLog); + + return shouldLog; + } + @EventListener public void onApplicationEvent(ApplicationEvent aEvent) { @@ -86,7 +118,7 @@ public void onApplicationEvent(ApplicationEvent aEvent) var adapter = maybeAdapter.get(); - if (properties.getExcludeEvents().contains(adapter.getEvent(aEvent))) { + if (!shouldLogEvent(adapter.getEvent(aEvent))) { return; } diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingProperties.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingProperties.java index 4588870bba2..09c77446c7a 100644 --- a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingProperties.java +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingProperties.java @@ -25,11 +25,25 @@ public interface EventLoggingProperties boolean isEnabled(); - Set getExcludeEvents(); + /** + * @return Set of regex include patterns + */ + Set getIncludePatterns(); + + /** + * @param includePatterns + * Set of regex include patterns + */ + void setIncludePatterns(Set includePatterns); + + /** + * @return Set of regex exclude patterns + */ + Set getExcludePatterns(); /** - * @param aExcludeEvents - * events never to be written to the event log. + * @param excludePatterns + * Set of regex exclude patterns */ - void setExcludeEvents(Set aExcludeEvents); + void setExcludePatterns(Set excludePatterns); } diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingPropertiesImpl.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingPropertiesImpl.java index 739b7348f35..85cd86ed141 100644 --- a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingPropertiesImpl.java +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/config/EventLoggingPropertiesImpl.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.log.config; +import java.util.Collections; import java.util.Set; import org.springframework.boot.availability.AvailabilityChangeEvent; @@ -32,8 +33,9 @@ public class EventLoggingPropertiesImpl { private boolean enabled; - private Set excludeEvents = Set.of( // - // Do not log this by default - hardly any information value + private Set includePatterns = Collections.emptySet(); // Default include everything + + private Set excludePatterns = Set.of( // AfterCasWrittenEvent.class.getSimpleName(), // AvailabilityChangeEvent.class.getSimpleName(), // "RecommenderTaskNotificationEvent", // @@ -55,14 +57,26 @@ public void setEnabled(boolean aEnabled) } @Override - public Set getExcludeEvents() + public Set getIncludePatterns() + { + return includePatterns; + } + + @Override + public void setIncludePatterns(Set aIncludePatterns) + { + this.includePatterns = aIncludePatterns; + } + + @Override + public Set getExcludePatterns() { - return excludeEvents; + return excludePatterns; } @Override - public void setExcludeEvents(Set aExcludeEvents) + public void setExcludePatterns(Set aExcludePatterns) { - excludeEvents = aExcludeEvents; + this.excludePatterns = aExcludePatterns; } } diff --git a/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventLoggingListenerTest.java b/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventLoggingListenerTest.java new file mode 100644 index 00000000000..fbb0f3951e7 --- /dev/null +++ b/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventLoggingListenerTest.java @@ -0,0 +1,91 @@ +/* + * 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.log; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.availability.AvailabilityChangeEvent; + +import de.tudarmstadt.ukp.inception.annotation.events.BeforeDocumentOpenedEvent; +import de.tudarmstadt.ukp.inception.annotation.events.PreparingToOpenDocumentEvent; +import de.tudarmstadt.ukp.inception.documents.event.AfterCasWrittenEvent; +import de.tudarmstadt.ukp.inception.log.config.EventLoggingPropertiesImpl; + +class EventLoggingListenerTest +{ + + private EventLoggingListener listener; + private EventLoggingPropertiesImpl properties; + + @BeforeEach + void setUp() throws Exception + { + properties = new EventLoggingPropertiesImpl(); + listener = new EventLoggingListener(null, properties, null); + } + + @Test + public void shouldLogEvent_defaultExcludeInternalList_ReturnsFalse() + { + assertThat(listener.shouldLogEvent(AfterCasWrittenEvent.class.getSimpleName())).isFalse(); + assertThat(listener.shouldLogEvent(AvailabilityChangeEvent.class.getSimpleName())) + .isFalse(); + assertThat(listener.shouldLogEvent("RecommenderTaskNotificationEvent")).isFalse(); + assertThat(listener.shouldLogEvent(BeforeDocumentOpenedEvent.class.getSimpleName())) + .isFalse(); + assertThat(listener.shouldLogEvent(PreparingToOpenDocumentEvent.class.getSimpleName())) + .isFalse(); + assertThat(listener.shouldLogEvent("BrokerAvailabilityEvent")).isFalse(); + assertThat(listener.shouldLogEvent("ShutdownDialogAvailableEvent")).isFalse(); + } + + @Test + public void shouldLogEvent_EventNotInExcludeLists_ReturnsTrue() + { + var eventAfterDocumentOpened = "AfterDocumentOpenedEvent"; + + assertThat(listener.shouldLogEvent(eventAfterDocumentOpened)).isTrue(); + assertThat(properties.getExcludePatterns()).doesNotContain(eventAfterDocumentOpened); + } + + @Test + public void shouldLogEvent_setExcludeWorksAndEventsGetExcludedTrue() + { + var excludedEvent = "ExcludedEvent"; + + properties.setExcludePatterns(Set.of(excludedEvent)); + + assertThat(listener.shouldLogEvent(excludedEvent)).isFalse(); + } + + @Test + public void shouldLogEvent_setIncludeWorksAndOnlyEventsSetIncludedWork() + { + var includedEvent = "IncludedEvent"; + var notIncludedEvent = "NotIncludedEvent"; + + properties.setIncludePatterns(Set.of(includedEvent)); + + assertThat(listener.shouldLogEvent(includedEvent)).isTrue(); + assertThat(listener.shouldLogEvent(notIncludedEvent)).isFalse(); + } +} diff --git a/inception/inception-model-export/pom.xml b/inception/inception-model-export/pom.xml index 2ef916bd30a..ec303627b34 100644 --- a/inception/inception-model-export/pom.xml +++ b/inception/inception-model-export/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-model-export INCEpTION - Core - Export Model diff --git a/inception/inception-model-vdoc/pom.xml b/inception/inception-model-vdoc/pom.xml index 91bdbfdd7f7..50abe0b8121 100644 --- a/inception/inception-model-vdoc/pom.xml +++ b/inception/inception-model-vdoc/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-model-vdoc INCEpTION - Core - VDoc Model diff --git a/inception/inception-model/pom.xml b/inception/inception-model/pom.xml index 6c673e1c9c0..32bb97084f8 100644 --- a/inception/inception-model/pom.xml +++ b/inception/inception-model/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-model INCEpTION - Core - Model diff --git a/inception/inception-pdf-editor/pom.xml b/inception/inception-pdf-editor/pom.xml index 23c6e6a719d..e36abb5a70e 100644 --- a/inception/inception-pdf-editor/pom.xml +++ b/inception/inception-pdf-editor/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT INCEpTION - Editor - PDF (pdfbox ${pdfbox.version}) (deprecated) inception-pdf-editor diff --git a/inception/inception-pdf-editor/src/main/ts_template/package-lock.json b/inception/inception-pdf-editor/src/main/ts_template/package-lock.json index b8fad4d431b..7474c232e41 100644 --- a/inception/inception-pdf-editor/src/main/ts_template/package-lock.json +++ b/inception/inception-pdf-editor/src/main/ts_template/package-lock.json @@ -74,7 +74,7 @@ "dependencies": { "@stomp/stompjs": "^6.1.2", "@types/stompjs": "^2.3.5", - "bootstrap": "5.3.2" + "bootstrap": "5.3.3" }, "devDependencies": { "@types/chai": "^4.3.1", @@ -113,7 +113,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", + "version": "0.19.12", "cpu": [ "arm64" ], @@ -171,8 +171,28 @@ "url": "https://opencollective.com/eslint" } }, + "../../../../inception-js-api/src/main/ts/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/@eslint/js": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "engines": { @@ -180,18 +200,38 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", + "version": "0.11.14", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -205,12 +245,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "BSD-3-Clause" }, "../../../../inception-js-api/src/main/ts/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -223,7 +263,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", + "version": "0.3.22", "dev": true, "license": "MIT", "dependencies": { @@ -297,7 +337,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@types/node": { - "version": "20.10.2", + "version": "20.11.20", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -309,7 +349,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@types/semver": { - "version": "7.5.6", + "version": "7.5.7", "dev": true, "license": "MIT" }, @@ -321,15 +361,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -355,14 +395,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/parser": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -382,12 +422,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -398,12 +438,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -424,7 +464,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/types": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "engines": { @@ -436,15 +476,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -462,16 +503,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -486,11 +527,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -507,7 +548,7 @@ "license": "ISC" }, "../../../../inception-js-api/src/main/ts/node_modules/acorn": { - "version": "8.11.2", + "version": "8.11.3", "dev": true, "license": "MIT", "bin": { @@ -588,12 +629,15 @@ "license": "Python-2.0" }, "../../../../inception-js-api/src/main/ts/node_modules/array-buffer-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -625,16 +669,34 @@ "node": ">=8" } }, - "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.findlastindex": { - "version": "1.2.3", + "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.filter": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -678,16 +740,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -714,9 +777,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/available-typed-arrays": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -738,7 +804,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/bootstrap": { - "version": "5.3.2", + "version": "5.3.3", "funding": [ { "type": "github", @@ -755,12 +821,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/brace-expansion": { - "version": "1.1.11", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "../../../../inception-js-api/src/main/ts/node_modules/braces": { @@ -801,13 +866,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/call-bind": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -833,7 +903,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/chai": { - "version": "4.3.10", + "version": "4.4.1", "dev": true, "license": "MIT", "dependencies": { @@ -1019,16 +1089,19 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/define-data-property": { - "version": "1.1.1", + "version": "1.1.4", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/define-properties": { @@ -1091,49 +1164,51 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/es-abstract": { - "version": "1.22.3", + "version": "1.22.4", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1142,14 +1217,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "../../../../inception-js-api/src/main/ts/node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "../../../../inception-js-api/src/main/ts/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/es-set-tostringtag": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1185,7 +1284,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild": { - "version": "0.19.8", + "version": "0.19.12", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1196,28 +1295,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild-runner-plugins": { @@ -1268,7 +1368,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild-sass-plugin": { - "version": "2.16.0", + "version": "2.16.1", "dev": true, "license": "MIT", "dependencies": { @@ -1295,7 +1395,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/escalade": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -1314,14 +1414,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1438,7 +1538,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", + "version": "0.7.4", "dev": true, "license": "MIT", "engines": { @@ -1489,7 +1589,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import": { - "version": "2.29.0", + "version": "2.29.1", "dev": true, "license": "MIT", "dependencies": { @@ -1509,7 +1609,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -1518,6 +1618,15 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "dev": true, @@ -1537,6 +1646,17 @@ "node": ">=0.10.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "dev": true, @@ -1546,7 +1666,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -1584,6 +1704,26 @@ "eslint": ">=7.0.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-promise": { "version": "6.1.1", "dev": true, @@ -1646,6 +1786,26 @@ "url": "https://opencollective.com/eslint" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/espree": { "version": "9.6.1", "dev": true, @@ -1742,7 +1902,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/fastq": { - "version": "1.15.0", + "version": "1.17.1", "dev": true, "license": "ISC", "dependencies": { @@ -1808,7 +1968,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/flatted": { - "version": "3.2.9", + "version": "3.3.1", "dev": true, "license": "ISC" }, @@ -1900,26 +2060,31 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/get-intrinsic": { - "version": "1.2.2", + "version": "1.2.4", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/get-symbol-description": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -1929,19 +2094,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/glob": { - "version": "7.2.0", + "version": "8.1.0", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1958,8 +2122,19 @@ "node": ">=10.13.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/globals": { - "version": "13.23.0", + "version": "13.24.0", "dev": true, "license": "MIT", "dependencies": { @@ -2043,18 +2218,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/has-property-descriptors": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/has-proto": { - "version": "1.0.1", + "version": "1.0.3", "dev": true, "license": "MIT", "engines": { @@ -2076,11 +2251,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/has-tostringtag": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -2090,7 +2265,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { @@ -2109,7 +2284,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "dev": true, "license": "MIT", "engines": { @@ -2117,7 +2292,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/immutable": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "license": "MIT" }, @@ -2159,11 +2334,11 @@ "license": "ISC" }, "../../../../inception-js-api/src/main/ts/node_modules/internal-slot": { - "version": "1.0.6", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -2172,13 +2347,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-array-buffer": { - "version": "3.0.2", + "version": "3.0.4", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2290,7 +2467,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-negative-zero": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "engines": { @@ -2354,11 +2531,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2404,11 +2584,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-typed-array": { - "version": "1.1.12", + "version": "1.1.13", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -2571,11 +2751,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/magic-string": { - "version": "0.27.0", + "version": "0.30.7", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2647,14 +2827,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/minimatch": { - "version": "3.1.2", + "version": "9.0.3", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "../../../../inception-js-api/src/main/ts/node_modules/minimist": { @@ -2680,7 +2863,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -2691,13 +2874,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -2712,10 +2894,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "../../../../inception-js-api/src/main/ts/node_modules/mocha-junit-reporter": { @@ -2733,14 +2911,6 @@ "mocha": ">=2.2.5" } }, - "../../../../inception-js-api/src/main/ts/node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "../../../../inception-js-api/src/main/ts/node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "dev": true, @@ -2803,19 +2973,8 @@ "dev": true, "license": "MIT" }, - "../../../../inception-js-api/src/main/ts/node_modules/nanoid": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "../../../../inception-js-api/src/main/ts/node_modules/natural-compare": { - "version": "1.4.0", + "../../../../inception-js-api/src/main/ts/node_modules/natural-compare": { + "version": "1.4.0", "dev": true, "license": "MIT" }, @@ -2877,14 +3036,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/object.groupby": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "../../../../inception-js-api/src/main/ts/node_modules/object.values": { @@ -3022,6 +3182,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "../../../../inception-js-api/src/main/ts/node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -3082,13 +3250,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/regexp.prototype.flags": { - "version": "1.5.1", + "version": "1.5.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -3163,6 +3332,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -3186,12 +3394,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/safe-array-concat": { - "version": "1.0.1", + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -3222,14 +3430,17 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/safe-regex-test": { - "version": "1.0.0", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3245,6 +3456,45 @@ "rimraf": "^2.5.2" } }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/mkdirp": { "version": "0.5.6", "dev": true, @@ -3268,7 +3518,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/sass": { - "version": "1.69.5", + "version": "1.71.1", "dev": true, "license": "MIT", "dependencies": { @@ -3284,7 +3534,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "license": "ISC", "dependencies": { @@ -3306,27 +3556,30 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/set-function-length": { - "version": "1.1.1", + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "../../../../inception-js-api/src/main/ts/node_modules/set-function-name": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3352,13 +3605,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3538,26 +3795,27 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/svelte-preprocess": { - "version": "5.1.1", + "version": "5.1.3", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -3640,11 +3898,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/ts-api-utils": { - "version": "1.0.3", + "version": "1.2.1", "dev": true, "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -3711,7 +3969,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", "dev": true, "license": "MIT", "dependencies": { @@ -3757,27 +4015,28 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-buffer": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -3787,15 +4046,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-byte-offset": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -3805,20 +4065,26 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-length": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/typescript": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "license": "Apache-2.0", "bin": { @@ -3901,15 +4167,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/which-typed-array": { - "version": "1.1.13", + "version": "1.1.14", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4037,7 +4303,7 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", + "version": "0.19.12", "cpu": [ "arm64" ], @@ -4095,8 +4361,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "engines": { @@ -4104,18 +4390,38 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", + "version": "0.11.14", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -4129,7 +4435,7 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "BSD-3-Clause" }, @@ -4185,7 +4491,7 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.6", + "version": "7.5.7", "dev": true, "license": "MIT" }, @@ -4195,15 +4501,15 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4229,14 +4535,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -4256,12 +4562,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4272,12 +4578,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -4298,7 +4604,7 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "engines": { @@ -4310,15 +4616,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -4336,16 +4643,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -4360,11 +4667,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4381,7 +4688,7 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.11.2", + "version": "8.11.3", "dev": true, "license": "MIT", "bin": { @@ -4442,12 +4749,15 @@ "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4479,16 +4789,34 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", + "node_modules/array.prototype.filter": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4532,16 +4860,17 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -4552,9 +4881,12 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4568,12 +4900,11 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -4596,13 +4927,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4700,16 +5036,19 @@ "license": "MIT" }, "node_modules/define-data-property": { - "version": "1.1.1", + "version": "1.1.4", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -4760,49 +5099,51 @@ "license": "MIT" }, "node_modules/es-abstract": { - "version": "1.22.3", + "version": "1.22.4", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -4811,14 +5152,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -4849,7 +5214,7 @@ } }, "node_modules/esbuild": { - "version": "0.19.8", + "version": "0.19.12", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4860,32 +5225,33 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/escalade": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -4904,14 +5270,14 @@ } }, "node_modules/eslint": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5068,7 +5434,7 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", + "version": "2.29.1", "dev": true, "license": "MIT", "dependencies": { @@ -5088,7 +5454,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -5097,6 +5463,15 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "dev": true, @@ -5116,6 +5491,17 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "dev": true, @@ -5148,6 +5534,26 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "dev": true, @@ -5210,6 +5616,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "dev": true, @@ -5313,7 +5739,7 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.15.0", + "version": "1.17.1", "dev": true, "license": "ISC", "dependencies": { @@ -5371,7 +5797,7 @@ } }, "node_modules/flatted": { - "version": "3.2.9", + "version": "3.3.1", "dev": true, "license": "ISC" }, @@ -5443,26 +5869,31 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", + "version": "1.2.4", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -5501,8 +5932,28 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { - "version": "13.23.0", + "version": "13.24.0", "dev": true, "license": "MIT", "dependencies": { @@ -5586,18 +6037,18 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", + "version": "1.0.3", "dev": true, "license": "MIT", "engines": { @@ -5619,11 +6070,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5633,7 +6084,7 @@ } }, "node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { @@ -5644,7 +6095,7 @@ } }, "node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "dev": true, "license": "MIT", "engines": { @@ -5689,11 +6140,11 @@ "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.6", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -5702,13 +6153,15 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", + "version": "3.0.4", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5804,7 +6257,7 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "engines": { @@ -5860,11 +6313,14 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5899,11 +6355,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", + "version": "1.1.13", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -6052,14 +6508,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "9.0.3", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6130,14 +6589,15 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -6283,6 +6743,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -6319,13 +6787,14 @@ "license": "MIT" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", + "version": "1.5.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -6423,12 +6892,12 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -6440,20 +6909,23 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.0", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "license": "ISC", "dependencies": { @@ -6467,27 +6939,30 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6513,13 +6988,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6662,18 +7141,18 @@ "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "1.0.3", + "version": "1.2.1", "dev": true, "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", "dev": true, "license": "MIT", "dependencies": { @@ -6706,27 +7185,28 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -6736,15 +7216,16 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -6754,20 +7235,26 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "license": "Apache-2.0", "bin": { @@ -6813,7 +7300,7 @@ "license": "MIT" }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", + "version": "3.3.3", "license": "MIT", "engines": { "node": ">= 8" @@ -6849,15 +7336,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", + "version": "1.1.14", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" diff --git a/inception/inception-pdf-editor2/pom.xml b/inception/inception-pdf-editor2/pom.xml index 0f9356411fd..0956eea3f25 100644 --- a/inception/inception-pdf-editor2/pom.xml +++ b/inception/inception-pdf-editor2/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT INCEpTION - Editor - PDF (pdfbox ${pdfbox.version}; pdfjs ${pdfjs.version}) inception-pdf-editor2 diff --git a/inception/inception-pdf-editor2/src/main/ts/src/PdfAnnotationEditor.ts b/inception/inception-pdf-editor2/src/main/ts/src/PdfAnnotationEditor.ts index 06c0637f26c..50723ad934b 100644 --- a/inception/inception-pdf-editor2/src/main/ts/src/PdfAnnotationEditor.ts +++ b/inception/inception-pdf-editor2/src/main/ts/src/PdfAnnotationEditor.ts @@ -28,10 +28,16 @@ export class PdfAnnotationEditor implements AnnotationEditor { this.ajax = ajax this.root = element - element.addEventListener('annotationSelected', ev => this.onAnnotationSelected(ev)) - element.addEventListener('createSpanAnnotation', ev => this.onCreateSpanAnnotation(ev)) - element.addEventListener('createRelationAnnotation', ev => this.onCreateRelationAnnotation(ev)) - element.addEventListener('doubleClickAnnotation', ev => this.onDoubleClickAnnotation(ev)) + // Prevent right-click from triggering a selection event + this.root.addEventListener('mousedown', e => this.cancelRightClick(e), { capture: true }) + this.root.addEventListener('mouseup', e => this.cancelRightClick(e), { capture: true }) + this.root.addEventListener('mouseclick', e => this.cancelRightClick(e), { capture: true }) + + this.root.addEventListener('annotationSelected', ev => this.onAnnotationSelected(ev)) + this.root.addEventListener('createSpanAnnotation', ev => this.onCreateSpanAnnotation(ev)) + this.root.addEventListener('createRelationAnnotation', ev => this.onCreateRelationAnnotation(ev)) + this.root.addEventListener('doubleClickAnnotation', ev => this.onDoubleClickAnnotation(ev)) + this.root.addEventListener('openContextMenu', ev => this.onOpenContextMenu(ev)) } async init (): Promise { @@ -47,6 +53,27 @@ export class PdfAnnotationEditor implements AnnotationEditor { scrollTo(args) } + private cancelRightClick (e: Event): void { + if (e instanceof MouseEvent) { + if (e.button === 2) { + console.log("cancelled") + e.preventDefault() + e.stopPropagation() + } + } + } + + onOpenContextMenu (ev: Event) { + if (ev instanceof CustomEvent) { + const ann = ev.detail.ann as AbstractAnnotation + const oev = ev.detail.originalEvent + + if (!(oev instanceof MouseEvent) || !(oev.target instanceof Node)) return + + if (ann.vid) this.ajax.openContextMenu(ann.vid, oev) + } + } + onAnnotationSelected (ev: Event) { if (ev instanceof CustomEvent) { const ann = ev.detail as AbstractAnnotation diff --git a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/AbstractAnnotation.ts b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/AbstractAnnotation.ts index 277ebfcde7c..7b2f44b3a1f 100644 --- a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/AbstractAnnotation.ts +++ b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/AbstractAnnotation.ts @@ -100,7 +100,7 @@ export default abstract class AbstractAnnotation extends EventEmitter { } /** - * Handle a click event. + * Handle a left-click event. */ handleSingleClickEvent (e: Event) { if (this.element) { @@ -110,6 +110,23 @@ export default abstract class AbstractAnnotation extends EventEmitter { } } + /** + * Handle a right-click event. + */ + handleRightClickEvent (e: Event) { + if (this.element && e instanceof MouseEvent) { + // If the user shift-right-clicks, open the normal browser context menu. This is useful + // e.g. during debugging / developing + if (e.shiftKey) return + + e.preventDefault() + + const event = document.createEvent('CustomEvent') + event.initCustomEvent('openContextMenu', true, true, { ann: this, originalEvent: e}) + this.element.dispatchEvent(event) + } + } + /** * Handle a hoverIn event. */ diff --git a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/RelationAnnotation.ts b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/RelationAnnotation.ts index d12940586cd..b666071d23c 100644 --- a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/RelationAnnotation.ts +++ b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/RelationAnnotation.ts @@ -31,6 +31,7 @@ export default class RelationAnnotation extends AbstractAnnotation { // Need to bind these event handler methods this.handleSingleClickEvent = this.handleSingleClickEvent.bind(this) + this.handleRightClickEvent = this.handleRightClickEvent.bind(this) this.handleHoverInEvent = this.handleHoverInEvent.bind(this) this.handleHoverOutEvent = this.handleHoverOutEvent.bind(this) @@ -220,9 +221,10 @@ export default class RelationAnnotation extends AbstractAnnotation { super.enableViewMode() if (!this.readOnly) { - this.element?.querySelectorAll('path').forEach(e => - e.addEventListener('click', this.handleSingleClickEvent)) - } + this.element?.querySelectorAll('path').forEach(e => { + e.addEventListener('click', this.handleSingleClickEvent) + e.addEventListener('contextmenu', this.handleRightClickEvent) + })} } /** @@ -230,8 +232,10 @@ export default class RelationAnnotation extends AbstractAnnotation { */ disableViewMode () { super.disableViewMode() - this.element?.querySelectorAll('path').forEach(e => - e.removeEventListener('click', this.handleSingleClickEvent)) + this.element?.querySelectorAll('path').forEach(e => { + e.removeEventListener('click', this.handleSingleClickEvent) + e.removeEventListener('contextmenu', this.handleRightClickEvent) + }) } /** diff --git a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/SpanAnnotation.ts b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/SpanAnnotation.ts index 7ab1b698440..3c6bdd7a719 100644 --- a/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/SpanAnnotation.ts +++ b/inception/inception-pdf-editor2/src/main/ts/src/pdfanno/core/src/model/SpanAnnotation.ts @@ -30,6 +30,7 @@ export default class SpanAnnotation extends AbstractAnnotation { // Need to bind these event handler methods this.handleClickEvent = this.handleClickEvent.bind(this) + this.handleRightClickEvent = this.handleRightClickEvent.bind(this) this.handleHoverInEvent = this.handleHoverInEvent.bind(this) this.handleHoverOutEvent = this.handleHoverOutEvent.bind(this) @@ -117,6 +118,7 @@ export default class SpanAnnotation extends AbstractAnnotation { this.element?.querySelectorAll('.anno-knob').forEach(e => { e.addEventListener('mousedown', this.preventFocusChange) e.addEventListener('click', this.handleClickEvent) + e.addEventListener('contextmenu', this.handleRightClickEvent) }) } @@ -162,6 +164,7 @@ export default class SpanAnnotation extends AbstractAnnotation { super.disableViewMode() this.element?.querySelectorAll('.anno-knob').forEach(e => { e.removeEventListener('click', this.handleClickEvent) + e.removeEventListener('contextmenu', this.handleRightClickEvent) e.removeEventListener('mousedown', this.preventFocusChange) }) } diff --git a/inception/inception-pdf-editor2/src/main/ts_template/package-lock.json b/inception/inception-pdf-editor2/src/main/ts_template/package-lock.json index d0ee2fd8f5f..3cae41acfee 100644 --- a/inception/inception-pdf-editor2/src/main/ts_template/package-lock.json +++ b/inception/inception-pdf-editor2/src/main/ts_template/package-lock.json @@ -88,7 +88,7 @@ "dependencies": { "@stomp/stompjs": "^6.1.2", "@types/stompjs": "^2.3.5", - "bootstrap": "5.3.2" + "bootstrap": "5.3.3" }, "devDependencies": { "@types/chai": "^4.3.1", @@ -127,7 +127,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", + "version": "0.19.12", "cpu": [ "arm64" ], @@ -185,8 +185,28 @@ "url": "https://opencollective.com/eslint" } }, + "../../../../inception-js-api/src/main/ts/node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/@eslint/js": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "engines": { @@ -194,18 +214,38 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", + "version": "0.11.14", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -219,12 +259,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "BSD-3-Clause" }, "../../../../inception-js-api/src/main/ts/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -237,7 +277,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", + "version": "0.3.22", "dev": true, "license": "MIT", "dependencies": { @@ -311,7 +351,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@types/node": { - "version": "20.10.2", + "version": "20.11.20", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -323,7 +363,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/@types/semver": { - "version": "7.5.6", + "version": "7.5.7", "dev": true, "license": "MIT" }, @@ -335,15 +375,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -369,14 +409,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/parser": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -396,12 +436,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -412,12 +452,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -438,7 +478,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/types": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "engines": { @@ -450,15 +490,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -476,16 +517,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -500,11 +541,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -521,7 +562,7 @@ "license": "ISC" }, "../../../../inception-js-api/src/main/ts/node_modules/acorn": { - "version": "8.11.2", + "version": "8.11.3", "dev": true, "license": "MIT", "bin": { @@ -602,12 +643,15 @@ "license": "Python-2.0" }, "../../../../inception-js-api/src/main/ts/node_modules/array-buffer-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -639,16 +683,34 @@ "node": ">=8" } }, - "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.findlastindex": { - "version": "1.2.3", + "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.filter": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -692,16 +754,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -728,9 +791,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/available-typed-arrays": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -752,7 +818,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/bootstrap": { - "version": "5.3.2", + "version": "5.3.3", "funding": [ { "type": "github", @@ -769,12 +835,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/brace-expansion": { - "version": "1.1.11", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "../../../../inception-js-api/src/main/ts/node_modules/braces": { @@ -815,13 +880,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/call-bind": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -847,7 +917,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/chai": { - "version": "4.3.10", + "version": "4.4.1", "dev": true, "license": "MIT", "dependencies": { @@ -1033,16 +1103,19 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/define-data-property": { - "version": "1.1.1", + "version": "1.1.4", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/define-properties": { @@ -1105,49 +1178,51 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/es-abstract": { - "version": "1.22.3", + "version": "1.22.4", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1156,14 +1231,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "../../../../inception-js-api/src/main/ts/node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "../../../../inception-js-api/src/main/ts/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/es-set-tostringtag": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1199,7 +1298,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild": { - "version": "0.19.8", + "version": "0.19.12", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1210,28 +1309,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild-runner-plugins": { @@ -1282,7 +1382,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/esbuild-sass-plugin": { - "version": "2.16.0", + "version": "2.16.1", "dev": true, "license": "MIT", "dependencies": { @@ -1309,7 +1409,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/escalade": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -1328,14 +1428,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1452,7 +1552,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", + "version": "0.7.4", "dev": true, "license": "MIT", "engines": { @@ -1503,7 +1603,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import": { - "version": "2.29.0", + "version": "2.29.1", "dev": true, "license": "MIT", "dependencies": { @@ -1523,7 +1623,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -1532,6 +1632,15 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "dev": true, @@ -1551,6 +1660,17 @@ "node": ">=0.10.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "dev": true, @@ -1560,7 +1680,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -1598,6 +1718,26 @@ "eslint": ">=7.0.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/eslint-plugin-promise": { "version": "6.1.1", "dev": true, @@ -1660,6 +1800,26 @@ "url": "https://opencollective.com/eslint" } }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/espree": { "version": "9.6.1", "dev": true, @@ -1756,7 +1916,7 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/fastq": { - "version": "1.15.0", + "version": "1.17.1", "dev": true, "license": "ISC", "dependencies": { @@ -1822,7 +1982,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/flatted": { - "version": "3.2.9", + "version": "3.3.1", "dev": true, "license": "ISC" }, @@ -1914,26 +2074,31 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/get-intrinsic": { - "version": "1.2.2", + "version": "1.2.4", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/get-symbol-description": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -1943,19 +2108,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/glob": { - "version": "7.2.0", + "version": "8.1.0", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1972,8 +2136,19 @@ "node": ">=10.13.0" } }, + "../../../../inception-js-api/src/main/ts/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/globals": { - "version": "13.23.0", + "version": "13.24.0", "dev": true, "license": "MIT", "dependencies": { @@ -2057,18 +2232,18 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/has-property-descriptors": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/has-proto": { - "version": "1.0.1", + "version": "1.0.3", "dev": true, "license": "MIT", "engines": { @@ -2090,11 +2265,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/has-tostringtag": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -2104,7 +2279,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { @@ -2123,7 +2298,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "dev": true, "license": "MIT", "engines": { @@ -2131,7 +2306,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/immutable": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "license": "MIT" }, @@ -2173,11 +2348,11 @@ "license": "ISC" }, "../../../../inception-js-api/src/main/ts/node_modules/internal-slot": { - "version": "1.0.6", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -2186,13 +2361,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-array-buffer": { - "version": "3.0.2", + "version": "3.0.4", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2304,7 +2481,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-negative-zero": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "engines": { @@ -2368,11 +2545,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2418,11 +2598,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/is-typed-array": { - "version": "1.1.12", + "version": "1.1.13", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -2585,11 +2765,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/magic-string": { - "version": "0.27.0", + "version": "0.30.7", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -2661,14 +2841,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/minimatch": { - "version": "3.1.2", + "version": "9.0.3", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "../../../../inception-js-api/src/main/ts/node_modules/minimist": { @@ -2694,7 +2877,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -2705,13 +2888,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -2726,10 +2908,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "../../../../inception-js-api/src/main/ts/node_modules/mocha-junit-reporter": { @@ -2747,14 +2925,6 @@ "mocha": ">=2.2.5" } }, - "../../../../inception-js-api/src/main/ts/node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "../../../../inception-js-api/src/main/ts/node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "dev": true, @@ -2817,19 +2987,8 @@ "dev": true, "license": "MIT" }, - "../../../../inception-js-api/src/main/ts/node_modules/nanoid": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "../../../../inception-js-api/src/main/ts/node_modules/natural-compare": { - "version": "1.4.0", + "../../../../inception-js-api/src/main/ts/node_modules/natural-compare": { + "version": "1.4.0", "dev": true, "license": "MIT" }, @@ -2891,14 +3050,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/object.groupby": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "../../../../inception-js-api/src/main/ts/node_modules/object.values": { @@ -3036,6 +3196,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "../../../../inception-js-api/src/main/ts/node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -3096,13 +3264,14 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/regexp.prototype.flags": { - "version": "1.5.1", + "version": "1.5.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -3177,6 +3346,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -3200,12 +3408,12 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/safe-array-concat": { - "version": "1.0.1", + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -3236,14 +3444,17 @@ "license": "MIT" }, "../../../../inception-js-api/src/main/ts/node_modules/safe-regex-test": { - "version": "1.0.0", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3259,6 +3470,45 @@ "rimraf": "^2.5.2" } }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "../../../../inception-js-api/src/main/ts/node_modules/sander/node_modules/mkdirp": { "version": "0.5.6", "dev": true, @@ -3282,7 +3532,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/sass": { - "version": "1.69.5", + "version": "1.71.1", "dev": true, "license": "MIT", "dependencies": { @@ -3298,7 +3548,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "license": "ISC", "dependencies": { @@ -3320,27 +3570,30 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/set-function-length": { - "version": "1.1.1", + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "../../../../inception-js-api/src/main/ts/node_modules/set-function-name": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3366,13 +3619,17 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3552,26 +3809,27 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/svelte-preprocess": { - "version": "5.1.1", + "version": "5.1.3", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -3654,11 +3912,11 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/ts-api-utils": { - "version": "1.0.3", + "version": "1.2.1", "dev": true, "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -3725,7 +3983,7 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", "dev": true, "license": "MIT", "dependencies": { @@ -3771,27 +4029,28 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-buffer": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -3801,15 +4060,16 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-byte-offset": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -3819,20 +4079,26 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/typed-array-length": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "../../../../inception-js-api/src/main/ts/node_modules/typescript": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "license": "Apache-2.0", "bin": { @@ -3915,15 +4181,15 @@ } }, "../../../../inception-js-api/src/main/ts/node_modules/which-typed-array": { - "version": "1.1.13", + "version": "1.1.14", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4051,7 +4317,7 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", + "version": "0.19.12", "cpu": [ "arm64" ], @@ -4109,8 +4375,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "engines": { @@ -4118,18 +4404,38 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", + "version": "0.11.14", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "dev": true, @@ -4143,7 +4449,7 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "BSD-3-Clause" }, @@ -4152,7 +4458,7 @@ "link": true }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -4165,7 +4471,7 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", + "version": "0.3.22", "dev": true, "license": "MIT", "dependencies": { @@ -4244,7 +4550,7 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.6", + "version": "7.5.7", "dev": true, "license": "MIT" }, @@ -4259,15 +4565,15 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4293,14 +4599,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -4320,12 +4626,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -4336,12 +4642,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -4362,7 +4668,7 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "engines": { @@ -4374,15 +4680,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -4400,16 +4707,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -4424,11 +4731,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", + "version": "6.21.0", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4445,7 +4752,7 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.11.2", + "version": "8.11.3", "dev": true, "license": "MIT", "bin": { @@ -4526,12 +4833,15 @@ "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4563,16 +4873,34 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", + "node_modules/array.prototype.filter": { + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4616,16 +4944,17 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -4644,9 +4973,12 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4668,12 +5000,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -4714,13 +5045,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4746,7 +5082,7 @@ } }, "node_modules/chai": { - "version": "4.3.10", + "version": "4.4.1", "dev": true, "license": "MIT", "dependencies": { @@ -4916,16 +5252,19 @@ "license": "MIT" }, "node_modules/define-data-property": { - "version": "1.1.1", + "version": "1.1.4", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -4992,49 +5331,51 @@ "license": "MIT" }, "node_modules/es-abstract": { - "version": "1.22.3", + "version": "1.22.4", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -5043,14 +5384,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -5086,7 +5451,7 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.19.8", + "version": "0.19.12", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5097,28 +5462,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-runner-plugins": { @@ -5169,7 +5535,7 @@ } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", + "version": "2.16.1", "dev": true, "license": "MIT", "dependencies": { @@ -5196,7 +5562,7 @@ } }, "node_modules/escalade": { - "version": "3.1.1", + "version": "3.1.2", "dev": true, "license": "MIT", "engines": { @@ -5215,14 +5581,14 @@ } }, "node_modules/eslint": { - "version": "8.55.0", + "version": "8.56.0", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5339,7 +5705,7 @@ } }, "node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", + "version": "0.7.4", "dev": true, "license": "MIT", "engines": { @@ -5390,7 +5756,7 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", + "version": "2.29.1", "dev": true, "license": "MIT", "dependencies": { @@ -5410,7 +5776,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -5419,6 +5785,15 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "dev": true, @@ -5430,12 +5805,23 @@ "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", "dev": true, - "license": "Apache-2.0", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", "dependencies": { - "esutils": "^2.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, "node_modules/eslint-plugin-import/node_modules/semver": { @@ -5447,7 +5833,7 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -5485,6 +5871,26 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "dev": true, @@ -5547,6 +5953,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "dev": true, @@ -5650,7 +6076,7 @@ "license": "MIT" }, "node_modules/fastq": { - "version": "1.15.0", + "version": "1.17.1", "dev": true, "license": "ISC", "dependencies": { @@ -5716,7 +6142,7 @@ } }, "node_modules/flatted": { - "version": "3.2.9", + "version": "3.3.1", "dev": true, "license": "ISC" }, @@ -5808,26 +6234,31 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", + "version": "1.2.4", "dev": true, "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -5837,19 +6268,18 @@ } }, "node_modules/glob": { - "version": "7.2.0", + "version": "8.1.0", "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5866,8 +6296,19 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { - "version": "13.23.0", + "version": "13.24.0", "dev": true, "license": "MIT", "dependencies": { @@ -5951,18 +6392,18 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", + "version": "1.0.3", "dev": true, "license": "MIT", "engines": { @@ -5984,11 +6425,11 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5998,7 +6439,7 @@ } }, "node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { @@ -6017,7 +6458,7 @@ } }, "node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "dev": true, "license": "MIT", "engines": { @@ -6025,7 +6466,7 @@ } }, "node_modules/immutable": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "license": "MIT" }, @@ -6067,11 +6508,11 @@ "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.6", + "version": "1.0.7", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -6080,13 +6521,15 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", + "version": "3.0.4", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6193,7 +6636,7 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", + "version": "2.0.3", "dev": true, "license": "MIT", "engines": { @@ -6257,11 +6700,14 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6307,11 +6753,11 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", + "version": "1.1.13", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -6474,11 +6920,11 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", + "version": "0.30.7", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -6535,14 +6981,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", + "version": "9.0.3", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6565,7 +7014,7 @@ } }, "node_modules/mocha": { - "version": "10.2.0", + "version": "10.3.0", "dev": true, "license": "MIT", "dependencies": { @@ -6576,13 +7025,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -6597,18 +7045,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" } }, "node_modules/mocha/node_modules/cliui": { @@ -6673,17 +7109,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.3", - "dev": true, - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -6747,14 +7172,15 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -6908,6 +7334,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -6968,13 +7402,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", + "version": "1.5.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -7049,6 +7484,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -7072,12 +7546,12 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", + "version": "1.1.0", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7108,14 +7582,17 @@ "license": "MIT" }, "node_modules/safe-regex-test": { - "version": "1.0.0", + "version": "1.0.3", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7131,6 +7608,45 @@ "rimraf": "^2.5.2" } }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/sander/node_modules/rimraf": { "version": "2.7.1", "dev": true, @@ -7159,7 +7675,7 @@ } }, "node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "license": "ISC", "dependencies": { @@ -7181,27 +7697,30 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", + "version": "1.2.1", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", + "version": "2.0.2", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7227,13 +7746,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7413,26 +7936,27 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.1.1", + "version": "5.1.3", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -7523,18 +8047,18 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", + "version": "1.2.1", "dev": true, "license": "MIT", "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", "dev": true, "license": "MIT", "dependencies": { @@ -7580,27 +8104,28 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", + "version": "1.0.1", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -7610,15 +8135,16 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -7628,20 +8154,26 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", + "version": "1.0.5", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "license": "Apache-2.0", "bin": { @@ -7699,7 +8231,7 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", + "version": "3.3.3", "license": "MIT", "engines": { "node": ">= 8" @@ -7735,15 +8267,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", + "version": "1.1.14", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" diff --git a/inception/inception-plugin-api/pom.xml b/inception/inception-plugin-api/pom.xml index 0786ab2ee5a..d9cd784bc5c 100644 --- a/inception/inception-plugin-api/pom.xml +++ b/inception/inception-plugin-api/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-plugin-api INCEpTION - Plugin API diff --git a/inception/inception-plugin-manager/pom.xml b/inception/inception-plugin-manager/pom.xml index f83646acfaa..ee8cdc4adb5 100644 --- a/inception/inception-plugin-manager/pom.xml +++ b/inception/inception-plugin-manager/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-plugin-manager INCEpTION - Plugin manager diff --git a/inception/inception-plugin-parent/pom.xml b/inception/inception-plugin-parent/pom.xml index 9831c2ddbcb..dced09b2866 100644 --- a/inception/inception-plugin-parent/pom.xml +++ b/inception/inception-plugin-parent/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-plugin-parent INCEpTION - Plugin Parent POM diff --git a/inception/inception-preferences/pom.xml b/inception/inception-preferences/pom.xml index 3f45f17a9c9..b9eec985aac 100644 --- a/inception/inception-preferences/pom.xml +++ b/inception/inception-preferences/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-preferences INCEpTION - Preferences diff --git a/inception/inception-project-api/pom.xml b/inception/inception-project-api/pom.xml index 0c346057c67..e86306afe7e 100644 --- a/inception/inception-project-api/pom.xml +++ b/inception/inception-project-api/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-api INCEpTION - Project - API diff --git a/inception/inception-project-export/pom.xml b/inception/inception-project-export/pom.xml index 7dd6a5c6dd1..2fd641b1038 100644 --- a/inception/inception-project-export/pom.xml +++ b/inception/inception-project-export/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-export INCEpTION - Core - Project export diff --git a/inception/inception-project-export/src/main/java/de/tudarmstadt/ukp/inception/project/export/settings/FormatDropdownChoice.java b/inception/inception-project-export/src/main/java/de/tudarmstadt/ukp/inception/project/export/settings/FormatDropdownChoice.java index 78441cd7851..d7e06011ea1 100644 --- a/inception/inception-project-export/src/main/java/de/tudarmstadt/ukp/inception/project/export/settings/FormatDropdownChoice.java +++ b/inception/inception-project-export/src/main/java/de/tudarmstadt/ukp/inception/project/export/settings/FormatDropdownChoice.java @@ -17,11 +17,10 @@ */ package de.tudarmstadt.ukp.inception.project.export.settings; +import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toCollection; import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; @@ -72,8 +71,8 @@ public Object getDisplayValue(String aObject) }); setChoices(LoadableDetachableModel.of(() -> { - List formats = importExportService.getWritableFormats().stream() // - .sorted(Comparator.comparing(FormatSupport::getName)) // + var formats = importExportService.getWritableFormats().stream() // + .sorted(comparing(FormatSupport::getName, String.CASE_INSENSITIVE_ORDER)) // .map(FormatSupport::getId) // .collect(toCollection(ArrayList::new)); formats.add(0, FullProjectExportRequest.FORMAT_AUTO); diff --git a/inception/inception-project-export/src/main/ts_template/package-lock.json b/inception/inception-project-export/src/main/ts_template/package-lock.json index 8511eb9c33d..d674362146c 100644 --- a/inception/inception-project-export/src/main/ts_template/package-lock.json +++ b/inception/inception-project-export/src/main/ts_template/package-lock.json @@ -232,9 +232,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -243,10 +243,26 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", - "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -260,9 +276,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", - "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -276,9 +292,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", - "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -292,9 +308,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", - "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -308,9 +324,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", - "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -324,9 +340,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", - "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -340,9 +356,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", - "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -356,9 +372,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", - "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -372,9 +388,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", - "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -388,9 +404,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", - "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -404,9 +420,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", - "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -420,9 +436,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", - "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -436,9 +452,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", - "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -452,9 +468,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", - "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -468,9 +484,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", - "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -484,9 +500,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", - "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -500,9 +516,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", - "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -516,9 +532,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", - "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -532,9 +548,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", - "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -548,9 +564,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", - "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -564,9 +580,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", - "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -580,9 +596,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", - "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -642,29 +658,73 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -679,15 +739,15 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -700,9 +760,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -835,22 +895,22 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -876,15 +936,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -904,13 +964,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -921,13 +981,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -948,9 +1008,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -961,16 +1021,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -988,17 +1049,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -1013,12 +1074,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1043,9 +1104,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1074,9 +1135,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1172,13 +1233,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1212,17 +1276,36 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1268,17 +1351,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1304,10 +1388,13 @@ "dev": true }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -1331,13 +1418,12 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1383,14 +1469,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1418,9 +1509,9 @@ } }, "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -1720,17 +1811,20 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -1839,50 +1933,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1891,6 +1987,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -1912,14 +2035,14 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1958,9 +2081,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", - "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1970,28 +2093,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-runner-plugins": { @@ -2047,9 +2171,9 @@ } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz", - "integrity": "sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.1.tgz", + "integrity": "sha512-mBB2aEF0xk7yo+Q9pSUh8xYED/1O2wbAM6IauGkDrqy6pl9SbJNakLeLGXiNpNujWIudu8TJTZCv2L5AQYRXtA==", "dev": true, "dependencies": { "resolve": "^1.22.6", @@ -2076,9 +2200,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -2118,15 +2242,15 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2260,9 +2384,9 @@ } }, "node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.2.tgz", - "integrity": "sha512-LOIfGx5sZZ5FwM1shr2GlYAWV9Omdi+1/3byuVagvQNoGUuU0iHhp7AfjA1uR+4dJ4Isfb4+FwBJgQajIw9iAg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.4.tgz", + "integrity": "sha512-PGPjJ8diYgX1mjLxGJqRop2rrGwZRKImoEOwUOgoIhg0p80MkTaqvmFLe5TF7/iagZHggasvIfQlUyHIhK/PYg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2315,9 +2439,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -2336,7 +2460,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -2345,6 +2469,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2366,6 +2500,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2376,9 +2522,9 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", - "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz", + "integrity": "sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", @@ -2416,6 +2562,28 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -2518,6 +2686,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2637,9 +2827,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2709,9 +2899,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -2826,28 +3016,33 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -2857,20 +3052,19 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2888,10 +3082,22 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2981,21 +3187,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -3017,12 +3223,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -3032,9 +3238,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -3104,18 +3310,18 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -3160,12 +3366,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -3190,14 +3396,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3328,9 +3536,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -3413,12 +3621,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3467,12 +3678,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3758,12 +3969,12 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -3857,15 +4068,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -3893,9 +4107,9 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -3905,13 +4119,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -3926,10 +4139,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha-junit-reporter": { @@ -3948,15 +4157,6 @@ "mocha": ">=2.2.5" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4026,10 +4226,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -4128,15 +4334,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -4305,10 +4512,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -4405,9 +4621,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4417,24 +4633,6 @@ "node": ">=4" } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4545,20 +4743,21 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -4645,6 +4844,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4669,13 +4910,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -4707,15 +4948,18 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4738,6 +4982,48 @@ "rimraf": "^2.5.2" } }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/sander/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4792,9 +5078,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4816,29 +5102,32 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4866,14 +5155,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5109,27 +5402,28 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.1.tgz", - "integrity": "sha512-p/Dp4hmrBW5mrCCq29lEMFpIJT2FZsRlouxEc5qpbOmXRbaFs7clLs8oKPwD3xCFyZfv1bIhvOzpQkhMEVQdMw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz", + "integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -5267,21 +5561,21 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -5330,29 +5624,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5362,16 +5657,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5381,23 +5677,29 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5571,16 +5873,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5619,9 +5921,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/AnnotationDocumentsExporterTest.java b/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/AnnotationDocumentsExporterTest.java index 85245f332cc..07d5d86f791 100644 --- a/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/AnnotationDocumentsExporterTest.java +++ b/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/AnnotationDocumentsExporterTest.java @@ -51,9 +51,8 @@ import de.tudarmstadt.ukp.inception.annotation.storage.config.CasStoragePropertiesImpl; import de.tudarmstadt.ukp.inception.annotation.storage.driver.filesystem.FileSystemCasStorageDriver; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; -import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.export.DocumentImportExportServiceImpl; -import de.tudarmstadt.ukp.inception.export.config.DocumentImportExportServiceProperties; import de.tudarmstadt.ukp.inception.export.config.DocumentImportExportServicePropertiesImpl; import de.tudarmstadt.ukp.inception.io.xmi.XmiFormatSupport; import de.tudarmstadt.ukp.inception.io.xmi.config.UimaFormatsPropertiesImpl.XmiFormatProperties; @@ -65,7 +64,7 @@ public class AnnotationDocumentsExporterTest { public @TempDir File tempFolder; - private RepositoryProperties repositoryProperties; + private RepositoryPropertiesImpl repositoryProperties; private DocumentImportExportService importExportSerivce; private FileSystemCasStorageDriver driver; private CasStorageServiceImpl casStorageService; @@ -89,9 +88,9 @@ public void setUp() throws Exception project.setId(1l); project.setName("Test Project"); - DocumentImportExportServiceProperties properties = new DocumentImportExportServicePropertiesImpl(); + var properties = new DocumentImportExportServicePropertiesImpl(); - repositoryProperties = new RepositoryProperties(); + repositoryProperties = new RepositoryPropertiesImpl(); repositoryProperties.setPath(workFolder); driver = new FileSystemCasStorageDriver(repositoryProperties, @@ -101,9 +100,9 @@ public void setUp() throws Exception null, schemaService); var xmiFormatSupport = new XmiFormatSupport(new XmiFormatProperties()); - importExportSerivce = new DocumentImportExportServiceImpl(repositoryProperties, - asList(xmiFormatSupport), casStorageService, schemaService, properties, - checksRegistry, repairsRegistry, xmiFormatSupport); + importExportSerivce = new DocumentImportExportServiceImpl(asList(xmiFormatSupport), + casStorageService, schemaService, properties, checksRegistry, repairsRegistry, + xmiFormatSupport); sut = new AnnotationDocumentExporter(documentService, null, importExportSerivce, repositoryProperties); diff --git a/inception/inception-project-export/src/test/resources/log4j2-test.xml b/inception/inception-project-export/src/test/resources/log4j2-test.xml index 818a4e5a5d5..589b76065e8 100644 --- a/inception/inception-project-export/src/test/resources/log4j2-test.xml +++ b/inception/inception-project-export/src/test/resources/log4j2-test.xml @@ -6,7 +6,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/inception/inception-project-initializers-basic/pom.xml b/inception/inception-project-initializers-basic/pom.xml index 5b7ff6b20a1..c5e2e02c68d 100644 --- a/inception/inception-project-initializers-basic/pom.xml +++ b/inception/inception-project-initializers-basic/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers-basic INCEpTION - Core - Project initializers - Basic Span / Relation diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.java b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.java index 02cefbcf718..ed4e68ed30a 100644 --- a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.java +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.java @@ -44,7 +44,7 @@ public class BasicProjectInitializer implements QuickProjectInitializer { private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( - MethodHandles.lookup().lookupClass(), "thumbnail.svg"); + MethodHandles.lookup().lookupClass(), "BasicProjectInitializer.svg"); private final ApplicationContext context; diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/thumbnail.svg b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.svg similarity index 100% rename from inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/thumbnail.svg rename to inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicProjectInitializer.svg diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.java b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.java index 0f32d7a5656..11dfe489175 100644 --- a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.java +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.java @@ -26,9 +26,14 @@ import static org.apache.uima.cas.CAS.TYPE_NAME_STRING; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; @@ -38,6 +43,7 @@ import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.basic.config.InceptionBasicProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -45,9 +51,13 @@ * {@link InceptionBasicProjectInitializersAutoConfiguration#basicRelationLayerInitializer}. *

*/ +@Order(20) public class BasicRelationLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "BasicRelationLayerInitializer.svg"); + public static final String BASIC_RELATION_LAYER_NAME = "custom.Relation"; public static final String BASIC_RELATION_LABEL_FEATURE_NAME = "label"; @@ -62,7 +72,19 @@ public BasicRelationLayerInitializer(AnnotationSchemaService aAnnotationSchemaSe @Override public String getName() { - return "Basic relation annotation"; + return "Generic relation annotation"; + } + + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("basic-relation-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); } @Override diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.svg b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.svg new file mode 100644 index 00000000000..a719b4f0461 --- /dev/null +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationLayerInitializer.svg @@ -0,0 +1,999 @@ + + + + + +RELATIONPERLOC diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationRecommenderInitializer.java b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationRecommenderInitializer.java index 24551acebce..eb65c5c247f 100644 --- a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationRecommenderInitializer.java +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicRelationRecommenderInitializer.java @@ -62,7 +62,7 @@ public BasicRelationRecommenderInitializer(RecommendationService aRecommenderSer @Override public String getName() { - return "Basic relation recommender"; + return "Generic relation recommender"; } @Override diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.java b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.java index 05d18f36c1c..84caa99de36 100644 --- a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.java +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.java @@ -23,10 +23,15 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; @@ -37,6 +42,7 @@ import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.basic.config.InceptionBasicProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -44,9 +50,13 @@ * {@link InceptionBasicProjectInitializersAutoConfiguration#basicSpanLayerInitializer}. *

*/ +@Order(10) public class BasicSpanLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "BasicSpanLayerInitializer.svg"); + public static final String BASIC_SPAN_LAYER_NAME = "custom.Span"; public static final String BASIC_SPAN_LABEL_FEATURE_NAME = "label"; @@ -61,7 +71,19 @@ public BasicSpanLayerInitializer(AnnotationSchemaService aAnnotationSchemaServic @Override public String getName() { - return "Basic span annotation"; + return "Generic span annotation"; + } + + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("basic-span-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); } @Override diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.svg b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.svg new file mode 100644 index 00000000000..27edc3a8175 --- /dev/null +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanLayerInitializer.svg @@ -0,0 +1,938 @@ + + + + + + diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanRecommenderInitializer.java b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanRecommenderInitializer.java index d8475c954a5..a5a2f0309bd 100644 --- a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanRecommenderInitializer.java +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/BasicSpanRecommenderInitializer.java @@ -59,7 +59,7 @@ public BasicSpanRecommenderInitializer(RecommendationService aRecommenderService @Override public String getName() { - return "Basic span recommender"; + return "Generic span recommender"; } @Override diff --git a/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/wicket-package.properties b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/wicket-package.properties new file mode 100644 index 00000000000..564eaa7a418 --- /dev/null +++ b/inception/inception-project-initializers-basic/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/basic/wicket-package.properties @@ -0,0 +1,20 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +basic-span-layer.description=Identify spans and classify them using labels. This is a good starting point for any span \ + annotation task. You can define new span labels as you go. +basic-relation-layer.description=Identify relations between basic span annotations and classify them using labels. This \ + is a good starting point for any annotation task involving relations. You can define new relation labels as you go. \ + Adding this layer will also add the basic span layer if it is not present in the project yet. diff --git a/inception/inception-project-initializers-doclabeling/pom.xml b/inception/inception-project-initializers-doclabeling/pom.xml index f12f017ad68..91760088efd 100644 --- a/inception/inception-project-initializers-doclabeling/pom.xml +++ b/inception/inception-project-initializers-doclabeling/pom.xml @@ -20,11 +20,15 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers-doclabeling INCEpTION - Core - Project initializers - Document Labeling + + de.tudarmstadt.ukp.inception.app + inception-support + de.tudarmstadt.ukp.inception.app inception-model diff --git a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.java b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.java index 788f2db3bda..7294eaadd36 100644 --- a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.java +++ b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.java @@ -22,10 +22,15 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; @@ -35,6 +40,7 @@ import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.doclabeling.config.InceptionDocumentLabelingProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; import de.tudarmstadt.ukp.inception.ui.core.docanno.layer.DocumentMetadataLayerSupport; /** @@ -43,9 +49,13 @@ * {@link InceptionDocumentLabelingProjectInitializersAutoConfiguration#basicDocumentTagLayerInitializer}. *

*/ +@Order(40) public class BasicDocumentLabelLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "BasicDocumentLabelLayerInitializer.svg"); + public static final String BASIC_DOCUMENT_LABEL_LAYER_NAME = "custom.DocumentLabel"; public static final String BASIC_DOCUMENT_LABEL_LABEL_FEATURE_NAME = "label"; @@ -63,7 +73,19 @@ public BasicDocumentLabelLayerInitializer(AnnotationSchemaService aAnnotationSch @Override public String getName() { - return "Basic document tag"; + return "Generic document classification"; + } + + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("document-labeling-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); } @Override diff --git a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.svg b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.svg new file mode 100644 index 00000000000..5a3c1e416b7 --- /dev/null +++ b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelLayerInitializer.svg @@ -0,0 +1,1071 @@ + + + + + + diff --git a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.java b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.java index c3d74e46d43..71dd6d16fb0 100644 --- a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.java +++ b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.java @@ -36,6 +36,7 @@ import de.tudarmstadt.ukp.inception.preferences.PreferencesService; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.doclabeling.config.InceptionDocumentLabelingProjectInitializersAutoConfiguration; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; import de.tudarmstadt.ukp.inception.ui.core.docanno.sidebar.DocumentMetadataSidebarFactory; import de.tudarmstadt.ukp.inception.workload.matrix.MatrixWorkloadExtension; import de.tudarmstadt.ukp.inception.workload.matrix.trait.MatrixWorkloadTraits; @@ -53,7 +54,7 @@ public class BasicDocumentLabelingProjectInitializer implements QuickProjectInitializer { private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( - MethodHandles.lookup().lookupClass(), "thumbnail.svg"); + MethodHandles.lookup().lookupClass(), "BasicDocumentLabelingProjectInitializer.svg"); private final PreferencesService prefService; private final DocumentMetadataSidebarFactory docMetaSidebar; @@ -77,6 +78,12 @@ public String getName() return "Document classification"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("document-labeling-project.description")); + } + @Override public Optional getThumbnail() { @@ -130,10 +137,4 @@ public void configure(Project aProject) throws IOException matrixWorkloadExtension.writeTraits(traits, aProject); workloadManagementService.saveConfiguration(manager); } - - @Override - public Optional getDescription() - { - return Optional.of("Create document-classification annotations."); - } } diff --git a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/thumbnail.svg b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.svg similarity index 100% rename from inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/thumbnail.svg rename to inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/BasicDocumentLabelingProjectInitializer.svg diff --git a/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/wicket-package.properties b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/wicket-package.properties new file mode 100644 index 00000000000..b2eadf0e59d --- /dev/null +++ b/inception/inception-project-initializers-doclabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/doclabeling/wicket-package.properties @@ -0,0 +1,19 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +document-labeling-project.description=Create document-classification annotations. +document-labeling-layer.description=Assign classification labels to your documents. This is a good starting point for \ + any document labeling annotation tasks. Remember to define your document labels in the tagset panel of the project \ + settings before you go to the annotation page. diff --git a/inception/inception-project-initializers-sentencelabeling/pom.xml b/inception/inception-project-initializers-sentencelabeling/pom.xml index d955a728abd..ef91e6cacd2 100644 --- a/inception/inception-project-initializers-sentencelabeling/pom.xml +++ b/inception/inception-project-initializers-sentencelabeling/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers-sentencelabeling INCEpTION - Core - Project initializers - Sentence Labeling diff --git a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.java b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.java index 6f21cbfd0a5..cec195b01ec 100644 --- a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.java +++ b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.java @@ -23,10 +23,15 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; @@ -36,6 +41,7 @@ import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.sentencelabeling.config.InceptionSentenceLabelingProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -43,6 +49,7 @@ * {@link InceptionSentenceLabelingProjectInitializersAutoConfiguration#sentenceTagLayerInitializer}. *

*/ +@Order(30) public class SentenceLabelLayerInitializer implements LayerInitializer { @@ -51,6 +58,9 @@ public class SentenceLabelLayerInitializer private final AnnotationSchemaService annotationSchemaService; + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "SentenceLabelLayerInitializer.svg"); + @Autowired public SentenceLabelLayerInitializer(AnnotationSchemaService aAnnotationSchemaService) { @@ -60,7 +70,19 @@ public SentenceLabelLayerInitializer(AnnotationSchemaService aAnnotationSchemaSe @Override public String getName() { - return "Basic sentence tag"; + return "Generic sentence classification"; + } + + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("sentence-labeling-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); } @Override diff --git a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.svg b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.svg new file mode 100644 index 00000000000..d62ba5011be --- /dev/null +++ b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelLayerInitializer.svg @@ -0,0 +1,964 @@ + + + + + + diff --git a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.java b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.java index 341f95f31d4..97c70ab1e74 100644 --- a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.java +++ b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.java @@ -36,6 +36,7 @@ import de.tudarmstadt.ukp.clarin.webanno.project.initializers.TokenLayerInitializer; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.sentencelabeling.config.InceptionSentenceLabelingProjectInitializersAutoConfiguration; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -48,7 +49,7 @@ public class SentenceLabelingProjectInitializer implements QuickProjectInitializer { private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( - MethodHandles.lookup().lookupClass(), "thumbnail.svg"); + MethodHandles.lookup().lookupClass(), "SentenceLabelingProjectInitializer.svg"); @Override public String getName() @@ -56,6 +57,12 @@ public String getName() return "Sentence classification"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("sentence-labeling-project.description")); + } + @Override public Optional getThumbnail() { @@ -87,10 +94,4 @@ public void configure(Project aProject) throws IOException // project overview "\n" + IOUtils.toString(descriptionUrl, UTF_8)); } - - @Override - public Optional getDescription() - { - return Optional.of("Annotate documents at the sentence level."); - } } diff --git a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/thumbnail.svg b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.svg similarity index 100% rename from inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/thumbnail.svg rename to inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/SentenceLabelingProjectInitializer.svg diff --git a/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/wicket-package.properties b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/wicket-package.properties new file mode 100644 index 00000000000..75138f07c3f --- /dev/null +++ b/inception/inception-project-initializers-sentencelabeling/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/sentencelabeling/wicket-package.properties @@ -0,0 +1,18 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +sentence-labeling-project.description=Annotate documents at the sentence level. +sentence-labeling-layer.description=Assign generic labels at the sentence level. This is a good starting point for any \ + sentence classification tasks. You can define new labels as you go along. diff --git a/inception/inception-project-initializers-ud/pom.xml b/inception/inception-project-initializers-ud/pom.xml index 11ddad0ff6d..8b4c1d5a815 100644 --- a/inception/inception-project-initializers-ud/pom.xml +++ b/inception/inception-project-initializers-ud/pom.xml @@ -15,18 +15,20 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers-ud INCEpTION - Core - Project initializers - Universal Dependencies + + de.tudarmstadt.ukp.inception.app + inception-support + de.tudarmstadt.ukp.inception.app inception-model diff --git a/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.java b/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.java index e503c6258e9..d4193f9058d 100644 --- a/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.java +++ b/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.java @@ -38,6 +38,7 @@ import de.tudarmstadt.ukp.clarin.webanno.project.initializers.TokenLayerInitializer; import de.tudarmstadt.ukp.clarin.webanno.project.initializers.config.ProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -50,7 +51,7 @@ public class UniversalDependenciesProjectInitializer implements QuickProjectInitializer { private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( - MethodHandles.lookup().lookupClass(), "thumbnail.svg"); + MethodHandles.lookup().lookupClass(), "UniversalDependenciesProjectInitializer.svg"); @Override public String getName() @@ -58,6 +59,12 @@ public String getName() return "Universal Dependencies"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("ud-project.description")); + } + @Override public Optional getThumbnail() { @@ -88,15 +95,4 @@ public void configure(Project aProject) throws IOException { // Nothing to do - all initialization is already done by the dependencies } - - @Override - public Optional getDescription() - { - return Optional - .of(""" - Comes pre-configured for linguistic annotation tasks according to the Universal Dependencies - guidelines. These include part-of-speech tagging, dependency parsing, morphological features, - lemmas, and surface forms. Importing and exporting these layers in the CoNLL-U format is - possible."""); - } } diff --git a/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/thumbnail.svg b/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.svg similarity index 100% rename from inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/thumbnail.svg rename to inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/UniversalDependenciesProjectInitializer.svg diff --git a/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/wicket-package.properties b/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/wicket-package.properties new file mode 100644 index 00000000000..377af700c66 --- /dev/null +++ b/inception/inception-project-initializers-ud/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/ud/wicket-package.properties @@ -0,0 +1,18 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +ud-project.description=Comes pre-configured for linguistic annotation tasks according to the Universal \ + Dependencies guidelines. These include part-of-speech tagging, dependency parsing, morphological features, \ + lemmas, and surface forms. Importing and exporting these layers in the CoNLL-U format is possible. diff --git a/inception/inception-project-initializers-wikidatalinking/pom.xml b/inception/inception-project-initializers-wikidatalinking/pom.xml index 44529fb22ac..5295034ae2b 100644 --- a/inception/inception-project-initializers-wikidatalinking/pom.xml +++ b/inception/inception-project-initializers-wikidatalinking/pom.xml @@ -20,11 +20,15 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers-wikidatalinking INCEpTION - Core - Project initializers - Entity Linking (Wikidata) + + de.tudarmstadt.ukp.inception.app + inception-support + de.tudarmstadt.ukp.inception.app inception-model diff --git a/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.java b/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.java index 3ebcca6019c..ed11ca995a2 100644 --- a/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.java +++ b/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.java @@ -37,6 +37,7 @@ import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.project.initializers.wikidatalinking.config.WikiDataLinkingProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; import de.tudarmstadt.ukp.inception.ui.kb.initializers.NamedEntityIdentifierFeatureInitializer; /** @@ -50,7 +51,7 @@ public class EntityLinkingProjectInitializer implements QuickProjectInitializer { private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( - MethodHandles.lookup().lookupClass(), "thumbnail.svg"); + MethodHandles.lookup().lookupClass(), "EntityLinkingProjectInitializer.svg"); private final AnnotationSchemaService annotationService; private final ApplicationContext context; @@ -68,6 +69,12 @@ public String getName() return "Entity linking (Wikidata)"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("entity-linking-project.description")); + } + @Override public Optional getThumbnail() { @@ -105,10 +112,4 @@ public void configure(Project aProject) throws IOException valueFeature.setEnabled(false); annotationService.createFeature(valueFeature); } - - @Override - public Optional getDescription() - { - return Optional.of("Link entity mentions to WikiData."); - } } diff --git a/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/thumbnail.svg b/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.svg similarity index 100% rename from inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/thumbnail.svg rename to inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/EntityLinkingProjectInitializer.svg diff --git a/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/wicket-package.properties b/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/wicket-package.properties new file mode 100644 index 00000000000..68a66a82171 --- /dev/null +++ b/inception/inception-project-initializers-wikidatalinking/src/main/java/de/tudarmstadt/ukp/inception/project/initializers/wikidatalinking/wicket-package.properties @@ -0,0 +1,16 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +entity-linking-project.description=Link entity mentions to WikiData. diff --git a/inception/inception-project-initializers/pom.xml b/inception/inception-project-initializers/pom.xml index f0ccbd3673e..e97d7adafbf 100644 --- a/inception/inception-project-initializers/pom.xml +++ b/inception/inception-project-initializers/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project-initializers INCEpTION - Core - Project initializers diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.java index fa10c7d7e59..e85b9998103 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.java @@ -21,9 +21,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; @@ -35,6 +39,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.chunk.Chunk; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -45,6 +50,9 @@ public class ChunkLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "ChunkLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -53,6 +61,18 @@ public ChunkLayerInitializer(AnnotationSchemaService aAnnotationSchemaService) annotationSchemaService = aAnnotationSchemaService; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("chunk-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public String getName() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.svg new file mode 100644 index 00000000000..88a3cd887d5 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/ChunkLayerInitializer.svg @@ -0,0 +1,991 @@ + + + + + +NPOBJVERB diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.java index 959766ff946..20ae36f5509 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.java @@ -21,9 +21,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; @@ -35,6 +39,7 @@ import de.tudarmstadt.ukp.clarin.webanno.project.initializers.config.ProjectInitializersAutoConfiguration; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -45,9 +50,12 @@ public class CoreferenceLayerInitializer implements LayerInitializer { - private final AnnotationSchemaService annotationSchemaService; + private static final String COREFERENCE_LAYER_NAME = "de.tudarmstadt.ukp.dkpro.core.api.coref.type.Coreference"; + + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "CoreferenceLayerInitializer.svg"); - private final String COREFERENCE_LAYER_NAME = "de.tudarmstadt.ukp.dkpro.core.api.coref.type.Coreference"; + private final AnnotationSchemaService annotationSchemaService; @Autowired public CoreferenceLayerInitializer(AnnotationSchemaService aAnnotationSchemaService) @@ -61,6 +69,18 @@ public String getName() return "Co-reference annotation"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("coreference-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.svg new file mode 100644 index 00000000000..b0bdb8df18b --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/CoreferenceLayerInitializer.svg @@ -0,0 +1,1115 @@ + + + + + +PERLOC diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.java index 4f25afc0dbc..373462531e0 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.java @@ -23,9 +23,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -37,6 +41,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.dependency.Dependency; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -49,6 +54,9 @@ public class DependencyLayerInitializer { private final AnnotationSchemaService annotationSchemaService; + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "DependencyLayerInitializer.svg"); + @Autowired public DependencyLayerInitializer(AnnotationSchemaService aAnnotationSchemaService) { @@ -61,6 +69,18 @@ public String getName() return "Dependency parsing"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("dependency-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.svg new file mode 100644 index 00000000000..4e53bd7654e --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/DependencyLayerInitializer.svg @@ -0,0 +1,1034 @@ + + + + + +SUBJOBJ diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LayerInitializer.java index 97983123525..cf778b6d540 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LayerInitializer.java @@ -17,10 +17,22 @@ */ package de.tudarmstadt.ukp.clarin.webanno.project.initializers; +import java.util.Optional; + +import org.apache.wicket.request.resource.ResourceReference; + import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; public interface LayerInitializer extends ProjectInitializer { - // No additions + default Optional getDescription() + { + return Optional.empty(); + } + + default Optional getThumbnail() + { + return Optional.empty(); + } } diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.java index bf9da41366b..00ae9ae2f5e 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.java @@ -23,9 +23,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -36,6 +40,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -46,6 +51,9 @@ public class LemmaLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "LemmaLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -60,6 +68,18 @@ public String getName() return "Lemmatization"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("lemma-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.svg new file mode 100644 index 00000000000..b64b2ba5d71 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/LemmaLayerInitializer.svg @@ -0,0 +1,1100 @@ + + + + + +runningrun diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.java index 8024a6dc513..94d24cd84f7 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.java @@ -23,9 +23,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -36,6 +40,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -46,6 +51,9 @@ public class MorphologicalFeaturesLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "MorphologicalFeaturesLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -60,6 +68,18 @@ public String getName() return "Morphological analysis"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("morpholological-features-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.svg new file mode 100644 index 00000000000..d37ee662727 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/MorphologicalFeaturesLayerInitializer.svg @@ -0,0 +1,1175 @@ + + + + + +TENSECASEPOLARITYNUMBER diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.java index 8a576ccefc5..a9f7464db3a 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.java @@ -21,9 +21,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; @@ -36,6 +40,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.ner.type.NamedEntity; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -46,6 +51,9 @@ public class NamedEntityLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "NamedEntityLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -60,6 +68,18 @@ public String getName() return "Named entity tagging"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("named-entity-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.svg new file mode 100644 index 00000000000..e26ef8a6264 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/NamedEntityLayerInitializer.svg @@ -0,0 +1,1328 @@ + + + + + +PERORGLOCOTHGPEDATEMONEYNUM diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.java index f0a6430c840..2f224493677 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.java @@ -21,9 +21,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; @@ -36,6 +40,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.transform.type.SofaChangeAnnotation; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -46,6 +51,9 @@ public class OrthographyLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "OrthographyLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -60,6 +68,18 @@ public String getName() return "Spelling correction"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("orthography-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.svg new file mode 100644 index 00000000000..94775efe3a2 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/OrthographyLayerInitializer.svg @@ -0,0 +1,1097 @@ + + + + + +SpelingSpelling diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.java index ec5c369f334..dbe021eb911 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.java @@ -23,9 +23,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -37,6 +41,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -47,6 +52,9 @@ public class PartOfSpeechLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "PartOfSpeechLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -61,6 +69,18 @@ public String getName() return "Part-of-speech tagging"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("pos-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.svg new file mode 100644 index 00000000000..e3e3f4706f1 --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/PartOfSpeechLayerInitializer.svg @@ -0,0 +1,1350 @@ + + + + + +NOUNADVVERBPRONADJAUXDETINTJ diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.java index 5447dc5cb5c..2db59f283a4 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.java @@ -21,9 +21,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; @@ -39,6 +43,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.semantics.type.SemPred; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -49,6 +54,9 @@ public class SemPredArgLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "SemPredArgLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -63,6 +71,18 @@ public String getName() return "Predicate argument structure"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("predicate-argument-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.svg new file mode 100644 index 00000000000..e3c5043b63b --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SemPredArgLayerInitializer.svg @@ -0,0 +1,1120 @@ + + + + + +PROCESSAGENTTHEME diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.java b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.java index 4aa5ba04f59..c61857a56d4 100644 --- a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.java +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.java @@ -23,9 +23,13 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; import org.apache.uima.cas.CAS; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -35,6 +39,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.SurfaceForm; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; /** *

@@ -45,6 +50,9 @@ public class SurfaceFormLayerInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "SurfaceFormLayerInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -59,6 +67,18 @@ public String getName() return "Text normalization"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("surface-form-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.svg b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.svg new file mode 100644 index 00000000000..3615ca2940f --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/SurfaceFormLayerInitializer.svg @@ -0,0 +1,1098 @@ + + + + + +It is great.It's diff --git a/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/wicket-package.properties b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/wicket-package.properties new file mode 100644 index 00000000000..1ccf3fd059b --- /dev/null +++ b/inception/inception-project-initializers/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/initializers/wicket-package.properties @@ -0,0 +1,44 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +pos-layer.description=Part-of-speech annotations assign grammatical categories (such as nouns, verbs, etc.) to each \ + word in a text. The layer comes with the [Universal POS Tags](https://universaldependencies.org/u/pos/) \ + pre-configured. +lemma-layer.description=Lemma annotations assign the base or dictionary form to a word. +named-entity-layer.description=Named Entity Tagging, also known as Named Entity Recognition (NER) identifies and \ + classifies named entities in text into predefined categories. Named entities are specific objects, individuals, \ + locations, dates, numerical values, and other proper nouns. +chunk-layer.description=Chunking identifies and labels contiguous sequences of words, called chunks, into meaningful \ + syntactic units, such as noun phrases or verb phrases. +surface-form-layer.description=Surface form annotations identify parts of a text that have been normalized and label \ + them with the original non-normalized text. For example, if you have normalized `it's` into `it is`, then you can \ + use a surface form annotation to encode the orignal text. +dependency-layer.description=Dependency annotations represent the syntactic relationships between words in a \ + sentence. The layer comes with the [Universal Dependency Relations](https://universaldependencies.org/u/dep/) \ + pre-configured. +coreference-layer.description=Coreference annotations identifying and linking expressions in a text that refer to the \ + same entity. Coreference occurs when a word or phrase in a sentence refers back to a previously mentioned entity, \ + typically using pronouns or other referencing expressions. +morpholological-features-layer.description=Morphological features identify aspects of a word's structure and form, \ + specifically focusing on its internal components and variations. These features include elements such as prefixes, \ + suffixes, roots, and inflections. +orthography-layer.description=Orthography annotations identify mis-spelled words and label them with the correct \ + spelling. +predicate-argument-layer.description=Predicate-argument annotation identifies the relationships between predicates \ + (verbs or actions) and their associated arguments (noun phrases, pronouns, or other entities) within a sentence. \ + It can also be used to represent semantic frames. A frame is a conceptual scenario or situation, often associated \ + with a particular verb or predicate, and it includes various roles or participants that play specific semantic roles \ + in that scenario. + \ No newline at end of file diff --git a/inception/inception-project/pom.xml b/inception/inception-project/pom.xml index 70de49e3073..d048162789b 100644 --- a/inception/inception-project/pom.xml +++ b/inception/inception-project/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-project INCEpTION - Core - Project diff --git a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccess.java b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccess.java index 23c2587aaaa..f58171daeeb 100644 --- a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccess.java +++ b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccess.java @@ -22,6 +22,8 @@ public interface ProjectAccess extends AccessCheckingBean { + boolean canCreateProjects(); + boolean canManageProject(String aProjectId); boolean canAccessProject(String aProjectId); diff --git a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccessImpl.java b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccessImpl.java index ab0341fc6b8..010f1f086a0 100644 --- a/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccessImpl.java +++ b/inception/inception-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/project/ProjectAccessImpl.java @@ -50,6 +50,23 @@ public ProjectAccessImpl(UserDao aUserService, ProjectService aProjectService) projectService = aProjectService; } + @Override + public boolean canCreateProjects() + { + var sessionOwner = userService.getCurrentUser(); + log.trace("Permission check: canCreateProjects [user: {}]", sessionOwner); + + if (userService.isProjectCreator(sessionOwner)) { + return true; + } + + if (userService.isAdministrator(sessionOwner)) { + return true; + } + + return false; + } + @Override public boolean canAccessProject(String aProjectId) { diff --git a/inception/inception-recommendation-api/pom.xml b/inception/inception-recommendation-api/pom.xml index ef9797a444d..3c286fad457 100644 --- a/inception/inception-recommendation-api/pom.xml +++ b/inception/inception-recommendation-api/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-recommendation-api INCEpTION - Recommendation - API diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommenderTypeSystemUtils.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommenderTypeSystemUtils.java index 1cd8a92d23d..792580c376d 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommenderTypeSystemUtils.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/RecommenderTypeSystemUtils.java @@ -22,7 +22,6 @@ import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_SCORE_EXPLANATION_SUFFIX; import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_SCORE_SUFFIX; import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; import static org.apache.uima.cas.CAS.TYPE_NAME_BOOLEAN; import static org.apache.uima.cas.CAS.TYPE_NAME_DOUBLE; import static org.apache.uima.cas.CAS.TYPE_NAME_STRING; @@ -34,13 +33,13 @@ import org.apache.uima.fit.factory.CasFactory; import org.apache.uima.resource.ResourceInitializationException; import org.apache.uima.resource.metadata.TypeSystemDescription; +import org.apache.uima.util.CasCopier; import org.apache.uima.util.TypeSystemUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.inception.support.WebAnnoConst; -import de.tudarmstadt.ukp.inception.support.uima.SegmentationUtils; public class RecommenderTypeSystemUtils { @@ -53,8 +52,7 @@ public static CAS makePredictionCas(CAS aOriginalCas, AnnotationFeature... aFeat RecommenderTypeSystemUtils.addPredictionFeaturesToTypeSystem(tsd, asList(aFeatures)); var predictionCas = CasFactory.createCas(tsd); predictionCas.setDocumentText(aOriginalCas.getDocumentText()); - SegmentationUtils.splitSentences(predictionCas); - SegmentationUtils.tokenize(predictionCas); + CasCopier.copyCas(aOriginalCas, predictionCas, false); return predictionCas; } @@ -83,8 +81,7 @@ public static void addPredictionFeaturesToTypeSystem(TypeSystemDescription tsd, td.addFeature(modeFeatureName, "Suggestion mode", TYPE_NAME_STRING); } - var layers = features.stream().map(AnnotationFeature::getLayer).distinct() - .collect(toList()); + var layers = features.stream().map(AnnotationFeature::getLayer).distinct().toList(); for (var layer : layers) { var td = tsd.getType(layer.getName()); if (td == null) { diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport.java index f6b4bbe6661..01e772952ba 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport.java @@ -63,13 +63,30 @@ void calculateSuggestionVisibility(String aSess Collection> aRecommendations, int aWindowBegin, int aWindowEnd); /** - * Accept the given suggestion. + * Uses the given annotation suggestion to create a new annotation or to update a feature in an + * existing annotation. * + * @param aSessionOwner + * the user currently logged in + * @param aDocument + * the source document to which the annotations belong + * @param aDataOwner + * the annotator user to whom the annotations belong + * @param aCas + * the CAS containing the annotations + * @param aAdapter + * an adapter for the layer to upsert + * @param aFeature + * the feature on the layer that should be upserted * @param aSuggestion - * the suggestion to accept. - * @return the annotation created from the suggestion. + * the suggestion + * @param aLocation + * the location from where the change was triggered + * @param aAction + * whether the annotation was accepted or corrected + * @return the created/updated annotation. * @throws AnnotationException - * if there was a problem creating the annotation. + * if there was an annotation-level problem */ AnnotationBaseFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, String aDataOwner, CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature, diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport_ImplBase.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport_ImplBase.java index fb067d7be21..b7c15fe3183 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport_ImplBase.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/SuggestionSupport_ImplBase.java @@ -80,13 +80,13 @@ public String getId() return id; } - protected void commitLabel(SourceDocument aDocument, String aDataOwner, CAS aCas, + protected final void commitLabel(SourceDocument aDocument, String aDataOwner, CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature, String aValue, AnnotationBaseFS annotation) throws AnnotationException { // Update the feature value - aAdapter.setFeatureValue(aDocument, aDataOwner, aCas, ICasUtil.getAddr(annotation), + aAdapter.pushFeatureValue(aDocument, aDataOwner, aCas, ICasUtil.getAddr(annotation), aFeature, aValue); } diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcPosition_ImplBase.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcPosition_ImplBase.java new file mode 100644 index 00000000000..44f6e0583aa --- /dev/null +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcPosition_ImplBase.java @@ -0,0 +1,118 @@ +/* + * 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.recommendation.api.model; + +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.apache.uima.cas.text.AnnotationFS; + +public abstract class ArcPosition_ImplBase> + implements Serializable, Position, Comparable +{ + private static final long serialVersionUID = 6630083774056904670L; + + protected final int sourceBegin; + protected final int sourceEnd; + protected final int targetBegin; + protected final int targetEnd; + + public ArcPosition_ImplBase(AnnotationFS aSource, AnnotationFS aTarget) + { + sourceBegin = aSource.getBegin(); + sourceEnd = aSource.getEnd(); + targetBegin = aTarget.getBegin(); + targetEnd = aTarget.getEnd(); + } + + public ArcPosition_ImplBase(int aSourceBegin, int aSourceEnd, int aTargetBegin, int aTargetEnd) + { + sourceBegin = aSourceBegin; + sourceEnd = aSourceEnd; + targetBegin = aTargetBegin; + targetEnd = aTargetEnd; + } + + public ArcPosition_ImplBase(T aOther) + { + sourceBegin = aOther.sourceBegin; + sourceEnd = aOther.sourceEnd; + targetBegin = aOther.targetBegin; + targetEnd = aOther.targetEnd; + } + + @Override + public String toString() + { + + return String.format("RelationPosition{(%d, %d) -> (%d, %d)}", sourceBegin, sourceEnd, + targetBegin, targetEnd); + } + + public int getSourceBegin() + { + return sourceBegin; + } + + public int getSourceEnd() + { + return sourceEnd; + } + + public int getTargetBegin() + { + return targetBegin; + } + + public int getTargetEnd() + { + return targetEnd; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ArcPosition_ImplBase that = (ArcPosition_ImplBase) o; + return sourceBegin == that.sourceBegin && sourceEnd == that.sourceEnd + && targetBegin == that.targetBegin && targetEnd == that.targetEnd; + } + + @Override + public int hashCode() + { + return Objects.hash(sourceBegin, sourceEnd, targetBegin, targetEnd); + } + + @Override + public int compareTo(T o) + { + return new CompareToBuilder() // + .append(getSourceBegin(), o.getSourceBegin()) // + .append(getSourceEnd(), o.getSourceEnd()) // + .append(getTargetBegin(), o.getTargetBegin()) // + .append(getTargetEnd(), o.getTargetEnd()).toComparison(); + } + +} diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcSuggestion_ImplBase.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcSuggestion_ImplBase.java new file mode 100644 index 00000000000..c07a22be4ee --- /dev/null +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/ArcSuggestion_ImplBase.java @@ -0,0 +1,234 @@ +/* + * 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.recommendation.api.model; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; + +public abstract class ArcSuggestion_ImplBase

> + extends AnnotationSuggestion + implements Serializable +{ + private static final long serialVersionUID = -4873732473868120957L; + + protected final P position; + + protected ArcSuggestion_ImplBase(Builder builder) + { + super(builder.id, builder.generation, builder.age, builder.recommenderId, + builder.recommenderName, builder.layerId, builder.feature, builder.documentName, + builder.label, builder.uiLabel, builder.score, builder.scoreExplanation, + builder.autoAcceptMode, builder.hidingFlags); + + this.position = builder.position; + } + + // Getter and setter + + @Override + public P getPosition() + { + return position; + } + + // The begin of the window is min(source.begin, target.begin) + // The end of the window is max(source.end, target.end) + // This is mostly used to optimize the viewport when rendering + + @Override + public int getWindowBegin() + { + return Math.min(position.getSourceBegin(), position.getTargetBegin()); + } + + @Override + public int getWindowEnd() + { + return Math.max(position.getSourceEnd(), position.getTargetEnd()); + } + + @Override + public String toString() + { + return new ToStringBuilder(this) // + .append("id", id) // + .append("generation", generation) // + .append("age", getAge()) // + .append("recommenderId", recommenderId) // + .append("recommenderName", recommenderName) // + .append("layerId", layerId) // + .append("feature", feature) // + .append("documentName", documentName) // + .append("position", position) // + .append("windowBegin", getWindowBegin()) // + .append("windowEnd", getWindowEnd()) // + .append("label", label) // + .append("uiLabel", uiLabel) // + .append("score", score) // + .append("confindenceExplanation", scoreExplanation) // + .append("visible", isVisible()) // + .append("reasonForHiding", getReasonForHiding()) // + .toString(); + } + + @Override + public ArcSuggestion_ImplBase assignId(int aId) + { + return toBuilder().withId(aId).build(); + } + + public abstract Builder toBuilder(); + + public static abstract class Builder, P extends ArcPosition_ImplBase> + { + protected int generation; + protected int age; + protected int id; + protected long recommenderId; + protected String recommenderName; + protected long layerId; + protected String feature; + protected String documentName; + protected String label; + protected String uiLabel; + protected double score; + protected String scoreExplanation; + protected P position; + protected AutoAcceptMode autoAcceptMode; + protected int hidingFlags; + + protected Builder() + { + } + + public T withId(int aId) + { + this.id = aId; + return (T) this; + } + + public T withGeneration(int aGeneration) + { + this.generation = aGeneration; + return (T) this; + } + + public T withAge(int aAge) + { + this.age = aAge; + return (T) this; + } + + public T withRecommender(Recommender aRecommender) + { + this.recommenderId = aRecommender.getId(); + this.recommenderName = aRecommender.getName(); + this.feature = aRecommender.getFeature().getName(); + this.layerId = aRecommender.getLayer().getId(); + return (T) this; + } + + @Deprecated + T withRecommenderId(long aRecommenderId) + { + this.recommenderId = aRecommenderId; + return (T) this; + } + + @Deprecated + T withRecommenderName(String aRecommenderName) + { + this.recommenderName = aRecommenderName; + return (T) this; + } + + @Deprecated + T withLayerId(long aLayerId) + { + this.layerId = aLayerId; + return (T) this; + } + + @Deprecated + T withFeature(String aFeature) + { + this.feature = aFeature; + return (T) this; + } + + public T withDocument(SourceDocument aDocument) + { + this.documentName = aDocument.getName(); + return (T) this; + } + + @Deprecated + public T withDocumentName(String aDocumentName) + { + this.documentName = aDocumentName; + return (T) this; + } + + public T withLabel(String aLabel) + { + this.label = aLabel; + return (T) this; + } + + public T withUiLabel(String aUiLabel) + { + this.uiLabel = aUiLabel; + return (T) this; + } + + public T withScore(double aScore) + { + this.score = aScore; + return (T) this; + } + + public T withScoreExplanation(String aScoreExplanation) + { + this.scoreExplanation = aScoreExplanation; + return (T) this; + } + + public T withPosition(P aPosition) + { + this.position = aPosition; + return (T) this; + } + + public T withAutoAcceptMode(AutoAcceptMode aAutoAcceptMode) + { + this.autoAcceptMode = aAutoAcceptMode; + return (T) this; + } + + public T withHidingFlags(int aFlags) + { + this.hidingFlags = aFlags; + return (T) this; + } + + public abstract ArcSuggestion_ImplBase build(); + } +} diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkPosition.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkPosition.java new file mode 100644 index 00000000000..9c3f43520d8 --- /dev/null +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkPosition.java @@ -0,0 +1,48 @@ +/* + * 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.recommendation.api.model; + +import org.apache.uima.cas.text.AnnotationFS; + +public class LinkPosition + extends ArcPosition_ImplBase +{ + private static final long serialVersionUID = 4899546086036031468L; + + private final String feature; + + public LinkPosition(String aFeature, AnnotationFS aSource, AnnotationFS aTarget) + { + super(aSource, aTarget); + feature = aFeature; + } + + public LinkPosition(String aFeature, int aSourceBegin, int aSourceEnd, int aTargetBegin, + int aTargetEnd) + { + super(aSourceBegin, aSourceEnd, aTargetBegin, aTargetEnd); + feature = aFeature; + } + + public LinkPosition(LinkPosition aOther) + { + super(aOther); + feature = aOther.feature; + } + +} diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkSuggestion.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkSuggestion.java new file mode 100644 index 00000000000..5e5facc367e --- /dev/null +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/LinkSuggestion.java @@ -0,0 +1,68 @@ +/* + * 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.recommendation.api.model; + +import java.io.Serializable; + +public class LinkSuggestion + extends ArcSuggestion_ImplBase + implements Serializable +{ + private static final long serialVersionUID = -2724884621704905935L; + + public LinkSuggestion(Builder> aBuilder) + { + super(aBuilder); + } + + @Override + public Builder toBuilder() + { + return builder() // + .withId(id) // + .withGeneration(generation) // + .withAge(getAge()) // + .withRecommenderId(recommenderId) // + .withRecommenderName(recommenderName) // + .withLayerId(layerId) // + .withFeature(feature) // + .withDocumentName(documentName) // + .withLabel(label) // + .withUiLabel(uiLabel) // + .withScore(score) // + .withScoreExplanation(scoreExplanation) // + .withPosition(position) // + .withAutoAcceptMode(getAutoAcceptMode()) // + .withHidingFlags(getHidingFlags()); + } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends ArcSuggestion_ImplBase.Builder + { + @Override + public LinkSuggestion build() + { + return new LinkSuggestion(this); + } + } +} diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationPosition.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationPosition.java index a53b7baebca..bdd997026a5 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationPosition.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationPosition.java @@ -17,119 +17,25 @@ */ package de.tudarmstadt.ukp.inception.recommendation.api.model; -import java.io.Serializable; -import java.util.Objects; - -import org.apache.commons.lang3.builder.CompareToBuilder; import org.apache.uima.cas.text.AnnotationFS; public class RelationPosition - implements Serializable, Position, Comparable + extends ArcPosition_ImplBase { private static final long serialVersionUID = -3084534351646334021L; - private final int sourceBegin; - private final int sourceEnd; - private final int targetBegin; - private final int targetEnd; - public RelationPosition(AnnotationFS aSource, AnnotationFS aTarget) { - sourceBegin = aSource.getBegin(); - sourceEnd = aSource.getEnd(); - targetBegin = aTarget.getBegin(); - targetEnd = aTarget.getEnd(); + super(aSource, aTarget); } public RelationPosition(int aSourceBegin, int aSourceEnd, int aTargetBegin, int aTargetEnd) { - sourceBegin = aSourceBegin; - sourceEnd = aSourceEnd; - targetBegin = aTargetBegin; - targetEnd = aTargetEnd; + super(aSourceBegin, aSourceEnd, aTargetBegin, aTargetEnd); } public RelationPosition(RelationPosition aOther) { - sourceBegin = aOther.sourceBegin; - sourceEnd = aOther.sourceEnd; - targetBegin = aOther.targetBegin; - targetEnd = aOther.targetEnd; - } - - @Override - public String toString() - { - - return String.format("RelationPosition{(%d, %d) -> (%d, %d)}", sourceBegin, sourceEnd, - targetBegin, targetEnd); - } - - public int getSourceBegin() - { - return sourceBegin; - } - - public int getSourceEnd() - { - return sourceEnd; - } - - public int getTargetBegin() - { - return targetBegin; - } - - public int getTargetEnd() - { - return targetEnd; - } - - public boolean overlaps(final RelationPosition i) - { - throw new UnsupportedOperationException("Not implemented yet"); - // // Cases: - // // - // // start end - // // | | - // // 1 ####### | - // // 2 | ####### - // // 3 #################################### - // // 4 | ####### | - // // | | - // - // return (((i.getStart() <= getStart()) && (getStart() < i.getEnd())) || // Case 1-3 - // ((i.getStart() < getEnd()) && (getEnd() <= i.getEnd())) || // Case 1-3 - // ((getStart() <= i.getStart()) && (i.getEnd() <= getEnd()))); // Case 4 - } - - @Override - public boolean equals(Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RelationPosition that = (RelationPosition) o; - return sourceBegin == that.sourceBegin && sourceEnd == that.sourceEnd - && targetBegin == that.targetBegin && targetEnd == that.targetEnd; - } - - @Override - public int hashCode() - { - return Objects.hash(sourceBegin, sourceEnd, targetBegin, targetEnd); - } - - @Override - public int compareTo(RelationPosition o) - { - return new CompareToBuilder() // - .append(getSourceBegin(), o.getSourceBegin()) // - .append(getSourceEnd(), o.getSourceEnd()) // - .append(getTargetBegin(), o.getTargetBegin()) // - .append(getTargetEnd(), o.getTargetEnd()).toComparison(); + super(aOther); } } diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationSuggestion.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationSuggestion.java index bf4602f917d..70e5c715678 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationSuggestion.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/model/RelationSuggestion.java @@ -19,87 +19,18 @@ import java.io.Serializable; -import org.apache.commons.lang3.builder.ToStringBuilder; - -import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; - public class RelationSuggestion - extends AnnotationSuggestion + extends ArcSuggestion_ImplBase implements Serializable { private static final long serialVersionUID = -1904645143661843249L; - private final RelationPosition position; - - private RelationSuggestion(Builder builder) - { - super(builder.id, builder.generation, builder.age, builder.recommenderId, - builder.recommenderName, builder.layerId, builder.feature, builder.documentName, - builder.label, builder.uiLabel, builder.score, builder.scoreExplanation, - builder.autoAcceptMode, builder.hidingFlags); - - this.position = builder.position; - } - - // Getter and setter - - @Override - public RelationPosition getPosition() - { - return position; - } - - // The begin of the window is min(source.begin, target.begin) - // The end of the window is max(source.end, target.end) - // This is mostly used to optimize the viewport when rendering - - @Override - public int getWindowBegin() - { - return Math.min(position.getSourceBegin(), position.getTargetBegin()); - } - - @Override - public int getWindowEnd() - { - return Math.max(position.getSourceEnd(), position.getTargetEnd()); - } - - @Override - public String toString() + public RelationSuggestion(Builder> aBuilder) { - return new ToStringBuilder(this) // - .append("id", id) // - .append("generation", generation) // - .append("age", getAge()) // - .append("recommenderId", recommenderId) // - .append("recommenderName", recommenderName) // - .append("layerId", layerId) // - .append("feature", feature) // - .append("documentName", documentName) // - .append("position", position) // - .append("windowBegin", getWindowBegin()) // - .append("windowEnd", getWindowEnd()) // - .append("label", label) // - .append("uiLabel", uiLabel) // - .append("score", score) // - .append("confindenceExplanation", scoreExplanation) // - .append("visible", isVisible()) // - .append("reasonForHiding", getReasonForHiding()) // - .toString(); + super(aBuilder); } @Override - public AnnotationSuggestion assignId(int aId) - { - return toBuilder().withId(aId).build(); - } - - public static Builder builder() - { - return new Builder(); - } - public Builder toBuilder() { return builder() // @@ -120,138 +51,15 @@ public Builder toBuilder() .withHidingFlags(getHidingFlags()); } - public static final class Builder + public static Builder> builder() { - private int generation; - private int age; - private int id; - private long recommenderId; - private String recommenderName; - private long layerId; - private String feature; - private String documentName; - private String label; - private String uiLabel; - private double score; - private String scoreExplanation; - private RelationPosition position; - private AutoAcceptMode autoAcceptMode; - private int hidingFlags; - - private Builder() - { - } - - public Builder withId(int aId) - { - this.id = aId; - return this; - } - - public Builder withGeneration(int aGeneration) - { - this.generation = aGeneration; - return this; - } - - public Builder withAge(int aAge) - { - this.age = aAge; - return this; - } - - public Builder withRecommender(Recommender aRecommender) - { - this.recommenderId = aRecommender.getId(); - this.recommenderName = aRecommender.getName(); - this.feature = aRecommender.getFeature().getName(); - this.layerId = aRecommender.getLayer().getId(); - return this; - } - - @Deprecated - Builder withRecommenderId(long aRecommenderId) - { - this.recommenderId = aRecommenderId; - return this; - } - - @Deprecated - Builder withRecommenderName(String aRecommenderName) - { - this.recommenderName = aRecommenderName; - return this; - } - - @Deprecated - Builder withLayerId(long aLayerId) - { - this.layerId = aLayerId; - return this; - } - - @Deprecated - Builder withFeature(String aFeature) - { - this.feature = aFeature; - return this; - } - - public Builder withDocument(SourceDocument aDocument) - { - this.documentName = aDocument.getName(); - return this; - } - - @Deprecated - public Builder withDocumentName(String aDocumentName) - { - this.documentName = aDocumentName; - return this; - } - - public Builder withLabel(String aLabel) - { - this.label = aLabel; - return this; - } - - public Builder withUiLabel(String aUiLabel) - { - this.uiLabel = aUiLabel; - return this; - } - - public Builder withScore(double aScore) - { - this.score = aScore; - return this; - } - - public Builder withScoreExplanation(String aScoreExplanation) - { - this.scoreExplanation = aScoreExplanation; - return this; - } - - public Builder withPosition(RelationPosition aPosition) - { - this.position = aPosition; - return this; - } - - public Builder withAutoAcceptMode(AutoAcceptMode aAutoAcceptMode) - { - this.autoAcceptMode = aAutoAcceptMode; - return this; - } - - public Builder withHidingFlags(int aFlags) - { - this.hidingFlags = aFlags; - return this; - } + return new Builder<>(); + } + public static class Builder> + extends ArcSuggestion_ImplBase.Builder + { + @Override public RelationSuggestion build() { return new RelationSuggestion(this); diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/util/OverlapIterator.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/util/OverlapIterator.java index 41acee962f2..f1f883df731 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/util/OverlapIterator.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/util/OverlapIterator.java @@ -91,7 +91,7 @@ else if (ib.hasNext()) { // Step to the next B from the source list LOG.trace("Stepping B from source Bs"); b = ib.next(); - if (b.getBegin() > a.getEnd()) { + if (a != null && b.getBegin() > a.getEnd()) { // All remaining b's are beyond the end of the a's and won't overlap a = null; } diff --git a/inception/inception-recommendation/pom.xml b/inception/inception-recommendation/pom.xml index 0dcf7910dc6..94a0fee7ba0 100644 --- a/inception/inception-recommendation/pom.xml +++ b/inception/inception-recommendation/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-recommendation INCEpTION - Recommendation - Core diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java index 961c06a3a5a..d97c5ed22e9 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/RecommendationEditorExtension.java @@ -46,12 +46,12 @@ import de.tudarmstadt.ukp.clarin.webanno.brat.message.DoActionResponse; import de.tudarmstadt.ukp.clarin.webanno.brat.message.RejectActionResponse; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPage; -import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationAdapter; -import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter; import de.tudarmstadt.ukp.inception.diam.editor.actions.ScrollToHandler; import de.tudarmstadt.ukp.inception.diam.editor.actions.SelectAnnotationHandler; import de.tudarmstadt.ukp.inception.editor.AnnotationEditorExtension; @@ -173,32 +173,6 @@ else if (ScrollToHandler.COMMAND.equals(aAction)) { } } - private void actionAcceptPrediction(AnnotationActionHandler aActionHandler, - AnnotatorState aState, AjaxRequestTarget aTarget, CAS aCas, VID aVID, - Optional prediction, SourceDocument document) - throws AnnotationException, IOException - { - if (prediction.map(p -> p instanceof SpanSuggestion).get()) { - actionAcceptSpanRecommendation(aTarget, (SpanSuggestion) prediction.get(), document, - aActionHandler, aState, aCas, aVID); - } - - if (prediction.map(p -> p instanceof RelationSuggestion).get()) { - actionAcceptRelationRecommendation(aTarget, (RelationSuggestion) prediction.get(), - document, aActionHandler, aState, aCas, aVID); - } - } - - private Optional getPrediction(AnnotatorState aState, VID aRecVid) - { - Predictions predictions = recommendationService.getPredictions(aState.getUser(), - aState.getProject()); - SourceDocument document = aState.getDocument(); - Optional prediction = predictions // - .getPredictionByVID(document, aRecVid); - return prediction; - } - /** * Accept a suggestion. * @@ -209,52 +183,38 @@ private Optional getPrediction(AnnotatorState aState, VID *

  • Sends events to the UI and application informing other components about the action.
  • * */ - private void actionAcceptSpanRecommendation(AjaxRequestTarget aTarget, - SpanSuggestion aSuggestion, SourceDocument aSocument, - AnnotationActionHandler aActionHandler, AnnotatorState aState, CAS aCas, - VID aSuggestionVid) + private void actionAcceptPrediction(AnnotationActionHandler aActionHandler, + AnnotatorState aState, AjaxRequestTarget aTarget, CAS aCas, VID aVID, + Optional aSuggestion, SourceDocument document) throws AnnotationException, IOException { + var suggestion = aSuggestion.get(); var page = (AnnotationPage) aTarget.getPage(); var dataOwner = aState.getUser().getUsername(); var sessionOwner = userService.getCurrentUsername(); - var layer = annotationService.getLayer(aSuggestion.getLayerId()); - var adapter = (SpanAdapter) annotationService.getAdapter(layer); + var layer = annotationService.getLayer(suggestion.getLayerId()); + var adapter = annotationService.getAdapter(layer); - var span = (Annotation) recommendationService.acceptSuggestion(sessionOwner, aSocument, - dataOwner, aCas, aSuggestion, MAIN_EDITOR); + var annotation = (Annotation) recommendationService.acceptSuggestion(sessionOwner, document, + dataOwner, aCas, suggestion, MAIN_EDITOR); page.writeEditorCas(aCas); // Set selection to the accepted annotation and select it and load it into the detail editor - aState.getSelection().set(adapter.select(VID.of(span), span)); + aState.getSelection().set(adapter.select(VID.of(annotation), annotation)); // Send a UI event that the suggestion has been accepted - page.send(page, BREADTH, - new AjaxRecommendationAcceptedEvent(aTarget, aState, aSuggestionVid)); + page.send(page, BREADTH, new AjaxRecommendationAcceptedEvent(aTarget, aState, aVID)); } - private void actionAcceptRelationRecommendation(AjaxRequestTarget aTarget, - RelationSuggestion aSuggestion, SourceDocument aDocument, - AnnotationActionHandler aActionHandler, AnnotatorState aState, CAS aCas, VID aVID) - throws AnnotationException, IOException + private Optional getPrediction(AnnotatorState aState, VID aRecVid) { - var page = (AnnotationPage) aTarget.getPage(); - var dataOwner = aState.getUser().getUsername(); - var sessionOwner = userService.getCurrentUsername(); - var layer = annotationService.getLayer(aSuggestion.getLayerId()); - var adapter = (RelationAdapter) annotationService.getAdapter(layer); - - var relation = (Annotation) recommendationService.acceptSuggestion(sessionOwner, aDocument, - dataOwner, aCas, aSuggestion, MAIN_EDITOR); - - page.writeEditorCas(aCas); - - // Set selection to the accepted annotation and select it and load it into the detail editor - aState.getSelection().set(adapter.select(aVID, relation)); - - // Send a UI event that the suggestion has been accepted - page.send(page, BREADTH, new AjaxRecommendationAcceptedEvent(aTarget, aState, aVID)); + Predictions predictions = recommendationService.getPredictions(aState.getUser(), + aState.getProject()); + SourceDocument document = aState.getDocument(); + Optional prediction = predictions // + .getPredictionByVID(document, aRecVid); + return prediction; } /** @@ -352,8 +312,8 @@ public V getFeatureValue(SourceDocument aDocument, User aUser, CAS aCas, VID } @Override - public List lookupLazyDetails(SourceDocument aDocument, User aUser, VID aVid, - AnnotationFeature aFeature) + public List lookupLazyDetails(SourceDocument aDocument, User aUser, CAS aCas, + VID aVid, AnnotationLayer aLayer) { var predictions = recommendationService.getPredictions(aUser, aDocument.getProject()); @@ -361,46 +321,56 @@ public List lookupLazyDetails(SourceDocument aDocument, User a return emptyList(); } - var vid = VID.parse(aVid.getExtensionPayload()); - var representative = predictions.getPredictionByVID(aDocument, vid); - if (representative.isEmpty() - || !representative.get().getFeature().equals(aFeature.getName())) { - return emptyList(); - } - - var sao = representative.get(); - var group = predictions - .getGroupedPredictions(AnnotationSuggestion.class, aDocument.getName(), - aFeature.getLayer(), sao.getWindowBegin(), sao.getWindowEnd()) - .stream() // - .filter(g -> g.contains(representative.get())) // - .findFirst(); + var detailGroups = new ArrayList(); + for (var aFeature : annotationService.listAnnotationFeature(aLayer)) { + if (aFeature.getLinkMode() == LinkMode.WITH_ROLE) { + return emptyList(); + } - if (group.isEmpty()) { - return emptyList(); - } + var vid = VID.parse(aVid.getExtensionPayload()); + var representative = predictions.getPredictionByVID(aDocument, vid); + if (representative.isEmpty() + || !representative.get().getFeature().equals(aFeature.getName())) { + return emptyList(); + } - var pref = recommendationService.getPreferences(aUser, aDocument.getProject()); - var label = defaultIfBlank(sao.getLabel(), null); - var sortedByScore = group.get().bestSuggestionsByFeatureAndLabel(pref, aFeature.getName(), - label); + var sao = representative.get(); + var group = predictions + .getGroupedPredictions(AnnotationSuggestion.class, aDocument.getName(), + aFeature.getLayer(), sao.getWindowBegin(), sao.getWindowEnd()) + .stream() // + .filter(g -> g.contains(representative.get())) // + .findFirst(); - var detailGroups = new ArrayList(); - for (var ao : sortedByScore) { - var detailGroup = new VLazyDetailGroup(ao.getRecommenderName()); - // detailGroup.addDetail(new VLazyDetail("Age", String.valueOf(ao.getAge()))); - if (ao.getScore() > 0.0d) { - detailGroup - .addDetail(new VLazyDetail("Score", String.format("%.2f", ao.getScore()))); - } - if (ao.getScoreExplanation().isPresent()) { - detailGroup - .addDetail(new VLazyDetail("Explanation", ao.getScoreExplanation().get())); + if (group.isEmpty()) { + return emptyList(); } - if (pref.isShowAllPredictions() && !ao.isVisible()) { - detailGroup.addDetail(new VLazyDetail("Hidden", ao.getReasonForHiding())); + + var pref = recommendationService.getPreferences(aUser, aDocument.getProject()); + var label = defaultIfBlank(sao.getLabel(), null); + var sortedByScore = group.get().bestSuggestionsByFeatureAndLabel(pref, + aFeature.getName(), label); + + var value = getFeatureValue(aDocument, aUser, aCas, aVid, aFeature); + featureSupportRegistry.findExtension(aFeature).orElseThrow() + .lookupLazyDetails(aFeature, value).forEach(detailGroups::add); + + for (var ao : sortedByScore) { + var detailGroup = new VLazyDetailGroup(ao.getRecommenderName()); + // detailGroup.addDetail(new VLazyDetail("Age", String.valueOf(ao.getAge()))); + if (ao.getScore() > 0.0d) { + detailGroup.addDetail( + new VLazyDetail("Score", String.format("%.2f", ao.getScore()))); + } + if (ao.getScoreExplanation().isPresent()) { + detailGroup.addDetail( + new VLazyDetail("Explanation", ao.getScoreExplanation().get())); + } + if (pref.isShowAllPredictions() && !ao.isVisible()) { + detailGroup.addDetail(new VLazyDetail("Hidden", ao.getReasonForHiding())); + } + detailGroups.add(detailGroup); } - detailGroups.add(detailGroup); } return detailGroups; diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java index c59c9e6afee..10fbfc65baa 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/config/RecommenderServiceAutoConfiguration.java @@ -20,6 +20,7 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -44,6 +45,7 @@ import de.tudarmstadt.ukp.inception.recommendation.exporter.LearningRecordExporter; import de.tudarmstadt.ukp.inception.recommendation.exporter.RecommenderExporter; import de.tudarmstadt.ukp.inception.recommendation.footer.RecommendationEventFooterItem; +import de.tudarmstadt.ukp.inception.recommendation.link.LinkSuggestionSupport; import de.tudarmstadt.ukp.inception.recommendation.log.RecommendationAcceptedEventAdapter; import de.tudarmstadt.ukp.inception.recommendation.log.RecommendationRejectedEventAdapter; import de.tudarmstadt.ukp.inception.recommendation.log.RecommenderDeletedEventAdapter; @@ -204,8 +206,7 @@ public RecommenderActionBarExtension recommenderActionBarExtension( } @Bean - public SpanSuggestionSupport spanRecommendationSupport( - RecommendationService aRecommendationService, + public SpanSuggestionSupport spanSuggestionSupport(RecommendationService aRecommendationService, LearningRecordService aLearningRecordService, ApplicationEventPublisher aApplicationEventPublisher, AnnotationSchemaService aSchemaService, FeatureSupportRegistry aFeatureSupportRegistry, @@ -217,7 +218,7 @@ public SpanSuggestionSupport spanRecommendationSupport( } @Bean - public RelationSuggestionSupport relationRecommendationSupport( + public RelationSuggestionSupport relationSuggestionSupport( RecommendationService aRecommendationService, LearningRecordService aLearningRecordService, ApplicationEventPublisher aApplicationEventPublisher, @@ -227,6 +228,16 @@ public RelationSuggestionSupport relationRecommendationSupport( aApplicationEventPublisher, aSchemaService, aFeatureSupportRegistry); } + @Bean + public LinkSuggestionSupport linkSuggestionSupport(RecommendationService aRecommendationService, + LearningRecordService aLearningRecordService, + ApplicationEventPublisher aApplicationEventPublisher, + AnnotationSchemaService aSchemaService, FeatureSupportRegistry aFeatureSupportRegistry) + { + return new LinkSuggestionSupport(aRecommendationService, aLearningRecordService, + aApplicationEventPublisher, aSchemaService, aFeatureSupportRegistry); + } + @Bean public SuggestionSupportRegistry layerRecommendtionSupportRegistry( @Lazy @Autowired(required = false) List aExtensions) @@ -236,6 +247,7 @@ public SuggestionSupportRegistry layerRecommendtionSupportRegistry( @ConditionalOnWebApplication @Bean + @ConditionalOnExpression("${websocket.enabled:true} and ${bulk-processing.enabled:false}") public BulkProcessingPageMenuItem bulkProcessingPageMenuItem(UserDao aUserRepo, ProjectService aProjectService, ServletContext aServletContext) { diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionRenderer.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionRenderer.java new file mode 100644 index 00000000000..99643d1289a --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionRenderer.java @@ -0,0 +1,65 @@ +/* + * 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.recommendation.link; + +import java.util.Map; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.Type; +import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.fit.util.CasUtil; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.relation.ArcSuggestionRenderer_ImplBase; +import de.tudarmstadt.ukp.inception.rendering.vmodel.VArc; +import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; + +public class LinkSuggestionRenderer + extends ArcSuggestionRenderer_ImplBase +{ + public LinkSuggestionRenderer(RecommendationService aRecommendationService, + AnnotationSchemaService aAnnotationService, FeatureSupportRegistry aFsRegistry) + { + super(aRecommendationService, aAnnotationService, aFsRegistry); + } + + @Override + protected VArc renderArc(AnnotationLayer aLayer, LinkSuggestion suggestion, AnnotationFS source, + AnnotationFS target, Map featureAnnotation) + { + return new VArc(aLayer, suggestion.getVID(), VID.of(source), VID.of(target), + "\uD83E\uDD16 " + suggestion.getUiLabel(), featureAnnotation, COLOR); + } + + @Override + protected Type getSourceType(CAS aCas, AnnotationLayer aLayer, AnnotationFeature aFeature) + { + return CasUtil.getType(aCas, aLayer.getName()); + } + + @Override + protected Type getTargetType(CAS aCas, AnnotationLayer aLayer, AnnotationFeature aFeature) + { + return CasUtil.getType(aCas, aFeature.getType()); + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionSupport.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionSupport.java new file mode 100644 index 00000000000..438ae50b3ba --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionSupport.java @@ -0,0 +1,299 @@ +/* + * 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.recommendation.link; + +import static org.apache.uima.fit.util.CasUtil.select; + +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.uima.cas.CAS; +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.tcas.Annotation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.LinkMode; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanAdapter; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionRenderer; +import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkPosition; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Position; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; +import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext; +import de.tudarmstadt.ukp.inception.recommendation.relation.ArcSuggestionSupport_ImplBase; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationComparisonUtils; +import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; +import de.tudarmstadt.ukp.inception.schema.api.feature.LinkWithRoleModel; +import de.tudarmstadt.ukp.inception.support.uima.ICasUtil; + +public class LinkSuggestionSupport + extends ArcSuggestionSupport_ImplBase +{ + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static final String TYPE = "LINK"; + + private final FeatureSupportRegistry featureSupportRegistry; + protected final AnnotationSchemaService schemaService; + + public LinkSuggestionSupport(RecommendationService aRecommendationService, + LearningRecordService aLearningRecordService, + ApplicationEventPublisher aApplicationEventPublisher, + AnnotationSchemaService aSchemaService, FeatureSupportRegistry aFeatureSupportRegistry) + { + super(aRecommendationService, aLearningRecordService, aApplicationEventPublisher, + aSchemaService); + + schemaService = aSchemaService; + featureSupportRegistry = aFeatureSupportRegistry; + } + + @Override + public String getType() + { + return TYPE; + } + + @Override + public boolean accepts(Recommender aContext) + { + if (!SpanLayerSupport.TYPE.equals(aContext.getLayer().getType())) { + return false; + } + + var feature = aContext.getFeature(); + if (feature.getLinkMode() == LinkMode.WITH_ROLE) { + return true; + } + + return false; + } + + @Override + public AnnotationFS acceptSuggestion(String aSessionOwner, SourceDocument aDocument, + String aDataOwner, CAS aCas, TypeAdapter aAdapter, AnnotationFeature aFeature, + AnnotationSuggestion aSuggestion, LearningRecordChangeLocation aLocation, + LearningRecordUserAction aAction) + throws AnnotationException + { + var suggestion = (LinkSuggestion) aSuggestion; + var adapter = (SpanAdapter) aAdapter; + + var sourceBegin = suggestion.getPosition().getSourceBegin(); + var sourceEnd = suggestion.getPosition().getSourceEnd(); + var linkHostType = adapter.getAnnotationType(aCas); + + // Check if there is already a link host + var linkHostCandidates = aCas. select(linkHostType).at(sourceBegin, sourceEnd) + .limit(2).toList(); + Annotation linkHost = null; + if (linkHostCandidates.size() > 1) { + LOG.warn("Found multiple link host candidates, using first one..."); + linkHost = linkHostCandidates.get(0); + } + else if (!linkHostCandidates.isEmpty()) { + linkHost = linkHostCandidates.get(0); + } + + // Check if there are valid slot fillers + var targetBegin = suggestion.getPosition().getTargetBegin(); + var targetEnd = suggestion.getPosition().getTargetEnd(); + var slotFillerType = CasUtil.getType(aCas, aFeature.getType()); + var slotFillerCandidates = aCas. select(slotFillerType) + .at(targetBegin, targetEnd).limit(2).toList(); + Annotation slotFiller = null; + if (slotFillerCandidates.size() > 1) { + LOG.warn("Found multiple slot filler candidates, using first one..."); + slotFiller = slotFillerCandidates.get(0); + } + else if (!slotFillerCandidates.isEmpty()) { + slotFiller = slotFillerCandidates.get(0); + } + + try (var eventBatch = adapter.batchEvents()) { + if (linkHost == null || slotFiller == null) { + String msg = "Cannot find link host or slot filler to establish link between"; + LOG.error(msg); + throw new IllegalStateException(msg); + } + + var annotationCreated = false; + + var oldLinks = (List) adapter.getFeatureValue(aFeature, linkHost); + try { + var newLinks = new ArrayList<>(oldLinks); + var link = new LinkWithRoleModel(suggestion.getLabel(), suggestion.getLabel(), + slotFiller.getAddress()); + newLinks.add(link); + adapter.setFeatureValue(aDocument, aDataOwner, aCas, linkHost.getAddress(), + aFeature, newLinks); + + annotationCreated = true; + } + catch (Exception e) { + if (annotationCreated) { + adapter.setFeatureValue(aDocument, aDataOwner, aCas, linkHost.getAddress(), + aFeature, oldLinks); + } + throw e; + } + + hideSuggestion(aSuggestion, aAction); + recordAndPublishAcceptance(aSessionOwner, aDocument, aDataOwner, aAdapter, aFeature, + aSuggestion, linkHost, aLocation, aAction); + + eventBatch.commit(); + return linkHost; + } + } + + @Override + protected MultiValuedMap groupAnnotationsInWindow(CAS aCas, + TypeAdapter aAdapter, int aWindowBegin, int aWindowEnd) + { + var adapter = (SpanAdapter) aAdapter; + + var type = adapter.getAnnotationType(aCas); + + var annotationsInWindow = getAnnotationsInWindow(aCas, type, aWindowBegin, aWindowEnd); + + var linkFeatures = adapter.listFeatures().stream() // + .filter(f -> f.getLinkMode() == LinkMode.WITH_ROLE) // + .toList(); + + var groupedAnnotations = new ArrayListValuedHashMap(); + for (var source : annotationsInWindow) { + for (var linkFeature : linkFeatures) { + var links = (List) adapter.getFeatureValue(linkFeature, source); + + for (var link : links) { + var slotFiller = ICasUtil.selectAnnotationByAddr(aCas, link.targetAddr); + var linkPosition = new LinkPosition(linkFeature.getName(), source.getBegin(), + source.getEnd(), slotFiller.getBegin(), slotFiller.getEnd()); + + groupedAnnotations.put(linkPosition, source); + } + } + } + + return groupedAnnotations; + } + + @Override + public Optional getRenderer() + { + return Optional.of(new LinkSuggestionRenderer(recommendationService, schemaService, + featureSupportRegistry)); + } + + @Override + public List extractSuggestions(ExtractionContext ctx) + { + var adapter = schemaService.getAdapter(ctx.getLayer()); + + var result = new ArrayList(); + for (var predictedFS : ctx.getPredictionCas().select(ctx.getPredictedType())) { + if (!predictedFS.getBooleanValue(ctx.getPredictionFeature())) { + continue; + } + + var feature = ctx.getRecommender().getFeature(); + var links = (List) adapter.getFeatureValue(feature, predictedFS); + if (links.isEmpty()) { + continue; + } + + var source = (AnnotationFS) predictedFS; + var link = links.get(0); + var target = ICasUtil.selectAnnotationByAddr(ctx.getPredictionCas(), link.targetAddr); + + var originalSource = findEquivalentSpan(ctx.getOriginalCas(), source); + var originalTarget = findEquivalentSpan(ctx.getOriginalCas(), target); + if (originalSource.isEmpty() || originalTarget.isEmpty()) { + LOG.debug("Unable to find owner or slot filler of predicted link in original CAS"); + continue; + } + + var autoAcceptMode = getAutoAcceptMode(predictedFS, ctx.getModeFeature()); + var score = predictedFS.getDoubleValue(ctx.getScoreFeature()); + var scoreExplanation = predictedFS.getStringValue(ctx.getScoreExplanationFeature()); + var position = new LinkPosition(feature.getName(), originalSource.get(), + originalTarget.get()); + + var suggestion = LinkSuggestion.builder() // + .withId(LinkSuggestion.NEW_ID) // + .withGeneration(ctx.getGeneration()) // + .withRecommender(ctx.getRecommender()) // + .withDocument(ctx.getDocument()) // + .withPosition(position) // + .withLabel(link.role) // + .withUiLabel(link.role) // + .withScore(score) // + .withScoreExplanation(scoreExplanation) // + .withAutoAcceptMode(autoAcceptMode) // + .build(); + result.add(suggestion); + } + return result; + } + + /** + * Locates an annotation in the given CAS which is equivalent of the provided annotation. + * + * @param aOriginalCas + * the original CAS. + * @param aAnnotation + * an annotation in the prediction CAS. return the equivalent in the original CAS. + */ + private static Optional findEquivalentSpan(CAS aOriginalCas, + AnnotationFS aAnnotation) + { + return aOriginalCas. select(aAnnotation.getType()) // + .at(aAnnotation) // + .filter(candidate -> AnnotationComparisonUtils.isEquivalentSpanAnnotation(candidate, + aAnnotation, null)) + .findFirst(); + } + + private List getAnnotationsInWindow(CAS aCas, Type type, int aWindowBegin, + int aWindowEnd) + { + return select(aCas, type).stream() // + .filter(fs -> fs.coveredBy(aWindowBegin, aWindowEnd)) // + .toList(); + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.java index 22e865f5f54..11afd90e816 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPage.java @@ -18,14 +18,17 @@ package de.tudarmstadt.ukp.inception.recommendation.processor; import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.MANAGER; +import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.NS_PROJECT; +import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.PAGE_PARAM_PROJECT; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; +import org.wicketstuff.annotation.mount.MountPath; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase; +@MountPath(NS_PROJECT + "/${" + PAGE_PARAM_PROJECT + "}/process") public class BulkProcessingPage extends ProjectPageBase { @@ -37,7 +40,7 @@ public BulkProcessingPage(PageParameters aParameters) { super(aParameters); - User user = userRepository.getCurrentUser(); + var user = userRepository.getCurrentUser(); requireProjectRole(user, MANAGER); diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPanel.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPanel.java index 60a4986bfe2..b1b9b663fb2 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPanel.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/processor/BulkProcessingPanel.java @@ -74,14 +74,18 @@ public BulkProcessingPanel(String aId, IModel aModel) queue(new LambdaAjaxButton<>("startProcessing", this::actionStartProcessing)); queue(new TaskMonitorPanel("runningProcesses").setPopupMode(false) - .setKeepRemovedTasks(true)); + .setShowFinishedTasks(true)); } private void actionStartProcessing(AjaxRequestTarget aTarget, Form aForm) { var formData = aForm.getModelObject(); - schedulingService.enqueue(new BulkPredictionTask(userService.getCurrentUser(), - formData.recommender, "User request", formData.user.getUsername())); + schedulingService.enqueue(BulkPredictionTask.builder() // + .withSessionOwner(userService.getCurrentUser()) // + .withRecommender(formData.recommender) // + .withTrigger("User request") // + .withDataOwner(formData.user.getUsername()) // + .build()); } private List listUsers() diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionRenderer_ImplBase.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionRenderer_ImplBase.java new file mode 100644 index 00000000000..177e7f643b5 --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionRenderer_ImplBase.java @@ -0,0 +1,153 @@ +/* + * 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.recommendation.relation; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.apache.uima.fit.util.CasUtil.selectAt; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.Type; +import org.apache.uima.cas.text.AnnotationFS; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionRenderer; +import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.ArcSuggestion_ImplBase; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; +import de.tudarmstadt.ukp.inception.rendering.request.RenderRequest; +import de.tudarmstadt.ukp.inception.rendering.vmodel.VArc; +import de.tudarmstadt.ukp.inception.rendering.vmodel.VDocument; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupport; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; + +public abstract class ArcSuggestionRenderer_ImplBase> + implements SuggestionRenderer +{ + private final RecommendationService recommendationService; + private final AnnotationSchemaService annotationService; + private final FeatureSupportRegistry fsRegistry; + + public ArcSuggestionRenderer_ImplBase(RecommendationService aRecommendationService, + AnnotationSchemaService aAnnotationService, FeatureSupportRegistry aFsRegistry) + { + recommendationService = aRecommendationService; + annotationService = aAnnotationService; + fsRegistry = aFsRegistry; + } + + @Override + public void render(VDocument aVDoc, RenderRequest aRequest, + SuggestionDocumentGroup aSuggestions, + AnnotationLayer aLayer) + { + var cas = aRequest.getCas(); + + // TODO #176 use the document Id once it it available in the CAS + var groupedPredictions = (SuggestionDocumentGroup) aSuggestions; + + // No recommendations to render for this layer + if (groupedPredictions.isEmpty()) { + return; + } + + recommendationService.calculateSuggestionVisibility( + aRequest.getSessionOwner().getUsername(), aRequest.getSourceDocument(), cas, + aRequest.getAnnotationUser().getUsername(), aLayer, groupedPredictions, + aRequest.getWindowBeginOffset(), aRequest.getWindowEndOffset()); + + var pref = recommendationService.getPreferences(aRequest.getAnnotationUser(), + aLayer.getProject()); + + // Bulk-load all the features of this layer to avoid having to do repeated DB accesses later + var features = annotationService.listSupportedFeatures(aLayer).stream() + .collect(toMap(AnnotationFeature::getName, identity())); + + var rankerCache = new HashMap(); + + for (var group : groupedPredictions) { + for (var suggestion : group.bestSuggestions(pref)) { + // Skip rendering AnnotationObjects that should not be rendered + if (!pref.isShowAllPredictions() && !suggestion.isVisible()) { + continue; + } + + var position = suggestion.getPosition(); + int sourceBegin = position.getSourceBegin(); + int sourceEnd = position.getSourceEnd(); + int targetBegin = position.getTargetBegin(); + int targetEnd = position.getTargetEnd(); + + // Retrieve the UI display label for the given feature value + var feature = features.get(suggestion.getFeature()); + var sourceType = getSourceType(cas, aLayer, feature); + var targetType = getTargetType(cas, aLayer, feature); + + // FIXME: We get the first match for the (begin, end) span. With stacking, there can + // be more than one and we need to get the right one then which does not need to be + // the first. We wait for #2135 for a maybe fix. + var source = selectAt(cas, sourceType, sourceBegin, sourceEnd) // + .stream().findFirst().orElse(null); + + var target = selectAt(cas, targetType, targetBegin, targetEnd) // + .stream().findFirst().orElse(null); + + if (source == null || target == null) { + continue; + } + + FeatureSupport featureSupport = fsRegistry.findExtension(feature).orElseThrow(); + var annotation = featureSupport.renderFeatureValue(feature, suggestion.getLabel()); + + Map featureAnnotation = annotation != null + ? Map.of(suggestion.getFeature(), annotation) + : Map.of(); + + var isRanker = rankerCache.computeIfAbsent(suggestion.getRecommenderId(), id -> { + var recommender = recommendationService.getRecommender(id); + if (recommender != null) { + var factory = recommendationService.getRecommenderFactory(recommender); + return factory.map(f -> f.isRanker(recommender)).orElse(false); + } + return false; + }); + + var arc = renderArc(aLayer, suggestion, source, target, featureAnnotation); + arc.setScore(suggestion.getScore()); + arc.setHideScore(isRanker); + + aVDoc.add(arc); + } + } + } + + protected abstract VArc renderArc(AnnotationLayer aLayer, T suggestion, AnnotationFS source, + AnnotationFS target, Map featureAnnotation); + + protected abstract Type getSourceType(CAS aCas, AnnotationLayer aLayer, + AnnotationFeature aFeature); + + protected abstract Type getTargetType(CAS aCas, AnnotationLayer aLayer, + AnnotationFeature aFeature); +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionSupport_ImplBase.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionSupport_ImplBase.java new file mode 100644 index 00000000000..80b3184261a --- /dev/null +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/ArcSuggestionSupport_ImplBase.java @@ -0,0 +1,189 @@ +/* + * 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.recommendation.relation; + +import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_OVERLAP; + +import java.lang.invoke.MethodHandles; +import java.util.Collection; + +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.Type; +import org.apache.uima.cas.text.AnnotationFS; +import org.apache.uima.fit.util.CasUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.lang.Nullable; + +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.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupport_ImplBase; +import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.ArcSuggestion_ImplBase; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Position; +import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationPosition; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; + +public abstract class ArcSuggestionSupport_ImplBase + extends SuggestionSupport_ImplBase +{ + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public ArcSuggestionSupport_ImplBase(RecommendationService aRecommendationService, + LearningRecordService aLearningRecordService, + ApplicationEventPublisher aApplicationEventPublisher, + AnnotationSchemaService aSchemaService) + { + super(aRecommendationService, aLearningRecordService, aApplicationEventPublisher, + aSchemaService); + } + + @Nullable + protected Type getAnnotationType(CAS aCas, AnnotationLayer aLayer) + { + // NOTE: In order to avoid having to upgrade the "original CAS" in computePredictions,this + // method is implemented in such a way that it gracefully handles cases where the CAS and + // the project type system are not in sync - specifically the CAS where the project defines + // layers or features which do not exist in the CAS. + + try { + return CasUtil.getAnnotationType(aCas, aLayer.getName()); + } + catch (IllegalArgumentException e) { + // Type does not exist in the type system of the CAS. Probably it has not been upgraded + // to the latest version of the type system yet. If this is the case, we'll just skip. + return null; + } + } + + public abstract String getType(); + + @Override + public void calculateSuggestionVisibility(String aSessionOwner, + SourceDocument aDocument, CAS aCas, String aUser, AnnotationLayer aLayer, + Collection> aRecommendations, int aWindowBegin, int aWindowEnd) + { + var type = getAnnotationType(aCas, aLayer); + + if (type == null) { + // The type does not exist in the type system of the CAS. Probably it has not + // been upgraded to the latest version of the type system yet. If this is the case, + // we'll just skip. + return; + } + + var adapter = schemaService.getAdapter(aLayer); + + // Group annotations by relation position, that is (source, target) address + var groupedAnnotations = groupAnnotationsInWindow(aCas, adapter, aWindowBegin, aWindowEnd); + + // Collect all suggestions of the given layer + var groupedSuggestions = aRecommendations.stream() + .filter(group -> group.getLayerId() == aLayer.getId()) // + .map(group -> (SuggestionGroup>) group) // + .toList(); + + // Get previously rejected suggestions + var groupedRecordedAnnotations = new ArrayListValuedHashMap(); + for (var learningRecord : learningRecordService.listLearningRecords(aSessionOwner, aUser, + aLayer)) { + var relationPosition = new RelationPosition(learningRecord.getOffsetSourceBegin(), + learningRecord.getOffsetSourceEnd(), learningRecord.getOffsetTargetBegin(), + learningRecord.getOffsetTargetEnd()); + + groupedRecordedAnnotations.put(relationPosition, learningRecord); + } + + for (var feature : schemaService.listSupportedFeatures(aLayer)) { + var feat = type.getFeatureByBaseName(feature.getName()); + + if (feat == null) { + // The feature does not exist in the type system of the CAS. Probably it has not + // been upgraded to the latest version of the type system yet. If this is the case, + // we'll just skip. + return; + } + + for (var group : groupedSuggestions) { + if (!feature.getName().equals(group.getFeature())) { + continue; + } + + group.showAll(AnnotationSuggestion.FLAG_ALL); + + var position = group.getPosition(); + + // If any annotation at this position has a non-null label for this feature, + // then we hide the suggestion group + for (var annotationFS : groupedAnnotations.get(position)) { + if (annotationFS.getFeatureValueAsString(feat) != null) { + for (var suggestion : group) { + suggestion.hide(FLAG_OVERLAP); + } + } + } + + // Hide previously rejected suggestions + for (var learningRecord : groupedRecordedAnnotations.get(position)) { + for (var suggestion : group) { + if (suggestion.labelEquals(learningRecord.getAnnotation())) { + suggestion.hideSuggestion(learningRecord.getUserAction()); + } + } + } + } + } + } + + protected abstract MultiValuedMap groupAnnotationsInWindow(CAS aCas, + TypeAdapter aAdapter, int aWindowBegin, int aWindowEnd); + + @Override + public LearningRecord toLearningRecord(SourceDocument aDocument, String aDataOwner, + AnnotationSuggestion aSuggestion, AnnotationFeature aFeature, + LearningRecordUserAction aUserAction, LearningRecordChangeLocation aLocation) + { + var pos = ((ArcSuggestion_ImplBase) aSuggestion).getPosition(); + var record = new LearningRecord(); + record.setUser(aDataOwner); + record.setSourceDocument(aDocument); + record.setUserAction(aUserAction); + record.setOffsetBegin(pos.getSourceBegin()); + record.setOffsetEnd(pos.getSourceEnd()); + record.setOffsetBegin2(pos.getTargetBegin()); + record.setOffsetEnd2(pos.getTargetEnd()); + record.setTokenText(""); + record.setAnnotation(aSuggestion.getLabel()); + record.setLayer(aFeature.getLayer()); + record.setSuggestionType(getType()); + record.setChangeLocation(aLocation); + record.setAnnotationFeature(aFeature); + return record; + } +} diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionRenderer.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionRenderer.java index 9ac92151a12..d017bfb5c2d 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionRenderer.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionRenderer.java @@ -17,124 +17,48 @@ */ package de.tudarmstadt.ukp.inception.recommendation.relation; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static org.apache.uima.fit.util.CasUtil.selectAt; - -import java.util.HashMap; import java.util.Map; +import org.apache.uima.cas.CAS; +import org.apache.uima.cas.Type; +import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; -import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionRenderer; -import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; -import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; -import de.tudarmstadt.ukp.inception.rendering.request.RenderRequest; import de.tudarmstadt.ukp.inception.rendering.vmodel.VArc; -import de.tudarmstadt.ukp.inception.rendering.vmodel.VDocument; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; -import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupport; import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; public class RelationSuggestionRenderer - implements SuggestionRenderer + extends ArcSuggestionRenderer_ImplBase { - private final RecommendationService recommendationService; - private final AnnotationSchemaService annotationService; - private final FeatureSupportRegistry fsRegistry; - public RelationSuggestionRenderer(RecommendationService aRecommendationService, AnnotationSchemaService aAnnotationService, FeatureSupportRegistry aFsRegistry) { - recommendationService = aRecommendationService; - annotationService = aAnnotationService; - fsRegistry = aFsRegistry; + super(aRecommendationService, aAnnotationService, aFsRegistry); } @Override - public void render(VDocument aVDoc, RenderRequest aRequest, - SuggestionDocumentGroup aSuggestions, - AnnotationLayer aLayer) + protected VArc renderArc(AnnotationLayer aLayer, RelationSuggestion suggestion, + AnnotationFS source, AnnotationFS target, Map featureAnnotation) { - var cas = aRequest.getCas(); - - // TODO #176 use the document Id once it it available in the CAS - var groupedPredictions = (SuggestionDocumentGroup) aSuggestions; - - // No recommendations to render for this layer - if (groupedPredictions.isEmpty()) { - return; - } - - recommendationService.calculateSuggestionVisibility( - aRequest.getSessionOwner().getUsername(), aRequest.getSourceDocument(), cas, - aRequest.getAnnotationUser().getUsername(), aLayer, groupedPredictions, - aRequest.getWindowBeginOffset(), aRequest.getWindowEndOffset()); - - var pref = recommendationService.getPreferences(aRequest.getAnnotationUser(), - aLayer.getProject()); - - var attachType = CasUtil.getType(cas, aLayer.getAttachType().getName()); - - // Bulk-load all the features of this layer to avoid having to do repeated DB accesses later - var features = annotationService.listSupportedFeatures(aLayer).stream() - .collect(toMap(AnnotationFeature::getName, identity())); - - var rankerCache = new HashMap(); - - for (var group : groupedPredictions) { - for (var suggestion : group.bestSuggestions(pref)) { - // Skip rendering AnnotationObjects that should not be rendered - if (!pref.isShowAllPredictions() && !suggestion.isVisible()) { - continue; - } - - var position = suggestion.getPosition(); - int sourceBegin = position.getSourceBegin(); - int sourceEnd = position.getSourceEnd(); - int targetBegin = position.getTargetBegin(); - int targetEnd = position.getTargetEnd(); - - // FIXME: We get the first match for the (begin, end) span. With stacking, there can - // be more than one and we need to get the right one then which does not need to be - // the first. We wait for #2135 for a maybe fix. - var source = selectAt(cas, attachType, sourceBegin, sourceEnd) // - .stream().findFirst().orElse(null); - - var target = selectAt(cas, attachType, targetBegin, targetEnd) // - .stream().findFirst().orElse(null); - - // Retrieve the UI display label for the given feature value - var feature = features.get(suggestion.getFeature()); - - FeatureSupport featureSupport = fsRegistry.findExtension(feature).orElseThrow(); - var annotation = featureSupport.renderFeatureValue(feature, suggestion.getLabel()); - - Map featureAnnotation = annotation != null - ? Map.of(suggestion.getFeature(), annotation) - : Map.of(); - - var isRanker = rankerCache.computeIfAbsent(suggestion.getRecommenderId(), id -> { - var recommender = recommendationService.getRecommender(id); - if (recommender != null) { - var factory = recommendationService.getRecommenderFactory(recommender); - return factory.map(f -> f.isRanker(recommender)).orElse(false); - } - return false; - }); + return new VArc(aLayer, suggestion.getVID(), VID.of(source), VID.of(target), + "\uD83E\uDD16 " + suggestion.getUiLabel(), featureAnnotation, COLOR); + } - var arc = new VArc(aLayer, suggestion.getVID(), VID.of(source), VID.of(target), - "\uD83E\uDD16 " + suggestion.getUiLabel(), featureAnnotation, COLOR); - arc.setScore(suggestion.getScore()); - arc.setHideScore(isRanker); + @Override + protected Type getSourceType(CAS aCas, AnnotationLayer aLayer, AnnotationFeature aFeature) + { + return CasUtil.getType(aCas, aLayer.getAttachType().getName()); + } - aVDoc.add(arc); - } - } + @Override + protected Type getTargetType(CAS aCas, AnnotationLayer aLayer, AnnotationFeature aFeature) + { + return CasUtil.getType(aCas, aLayer.getAttachType().getName()); } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java index d3595ac6c25..0ffd5594603 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionSupport.java @@ -17,7 +17,6 @@ */ package de.tudarmstadt.ukp.inception.recommendation.relation; -import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_OVERLAP; import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_SOURCE; import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.FEAT_REL_TARGET; import static org.apache.uima.cas.text.AnnotationPredicates.colocated; @@ -26,11 +25,11 @@ import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; +import org.apache.commons.collections4.MultiMapUtils; +import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.uima.cas.CAS; import org.apache.uima.cas.Type; @@ -40,26 +39,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.lang.Nullable; 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.inception.annotation.layer.relation.RelationAdapter; import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationLayerSupport; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionRenderer; -import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupport_ImplBase; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; -import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord; import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation; import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction; import de.tudarmstadt.ukp.inception.recommendation.api.model.Position; import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationPosition; import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; -import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup; import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext; import de.tudarmstadt.ukp.inception.rendering.vmodel.VID; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -69,7 +63,7 @@ import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; public class RelationSuggestionSupport - extends SuggestionSupport_ImplBase + extends ArcSuggestionSupport_ImplBase { private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -84,9 +78,16 @@ public RelationSuggestionSupport(RecommendationService aRecommendationService, { super(aRecommendationService, aLearningRecordService, aApplicationEventPublisher, aSchemaService); + featureSupportRegistry = aFeatureSupportRegistry; } + @Override + public String getType() + { + return TYPE; + } + @Override public boolean accepts(Recommender aContext) { @@ -95,7 +96,9 @@ public boolean accepts(Recommender aContext) } var feature = aContext.getFeature(); - if (CAS.TYPE_NAME_STRING.equals(feature.getType()) || feature.isVirtualFeature()) { + if (CAS.TYPE_NAME_STRING.equals(feature.getType()) + || CAS.TYPE_NAME_STRING_ARRAY.equals(feature.getType()) + || feature.isVirtualFeature()) { return true; } @@ -222,30 +225,21 @@ else if (candidates.size() > 1) { } @Override - public void calculateSuggestionVisibility(String aSessionOwner, - SourceDocument aDocument, CAS aCas, String aUser, AnnotationLayer aLayer, - Collection> aRecommendations, int aWindowBegin, int aWindowEnd) + protected MultiValuedMap groupAnnotationsInWindow(CAS aCas, + TypeAdapter aAdapter, int aWindowBegin, int aWindowEnd) { - var type = getAnnotationType(aCas, aLayer); - - if (type == null) { - // The type does not exist in the type system of the CAS. Probably it has not - // been upgraded to the latest version of the type system yet. If this is the case, - // we'll just skip. - return; - } + var adapter = (RelationAdapter) aAdapter; - var governorFeature = type.getFeatureByBaseName(FEAT_REL_SOURCE); - var dependentFeature = type.getFeatureByBaseName(FEAT_REL_TARGET); + var type = adapter.getAnnotationType(aCas); + var governorFeature = adapter.getSourceFeature(aCas); + var dependentFeature = adapter.getTargetFeature(aCas); if (dependentFeature == null || governorFeature == null) { - LOG.warn("Missing Dependent or Governor feature on [{}]", aLayer.getName()); - return; + LOG.warn("Missing Dependent or Governor feature on [{}]", type.getName()); + return MultiMapUtils.emptyMultiValuedMap(); } var annotationsInWindow = getAnnotationsInWindow(aCas, type, aWindowBegin, aWindowEnd); - - // Group annotations by relation position, that is (source, target) address var groupedAnnotations = new ArrayListValuedHashMap(); for (var annotationFS : annotationsInWindow) { var source = (AnnotationFS) annotationFS.getFeatureValue(governorFeature); @@ -257,115 +251,7 @@ public void calculateSuggestionVisibility(Strin groupedAnnotations.put(relationPosition, annotationFS); } - // Collect all suggestions of the given layer - var groupedSuggestions = aRecommendations.stream() - .filter(group -> group.getLayerId() == aLayer.getId()) // - .map(group -> (SuggestionGroup) group) // - .toList(); - - // Get previously rejected suggestions - var groupedRecordedAnnotations = new ArrayListValuedHashMap(); - for (var learningRecord : learningRecordService.listLearningRecords(aSessionOwner, aUser, - aLayer)) { - var relationPosition = new RelationPosition(learningRecord.getOffsetSourceBegin(), - learningRecord.getOffsetSourceEnd(), learningRecord.getOffsetTargetBegin(), - learningRecord.getOffsetTargetEnd()); - - groupedRecordedAnnotations.put(relationPosition, learningRecord); - } - - for (var feature : schemaService.listSupportedFeatures(aLayer)) { - var feat = type.getFeatureByBaseName(feature.getName()); - - if (feat == null) { - // The feature does not exist in the type system of the CAS. Probably it has not - // been upgraded to the latest version of the type system yet. If this is the case, - // we'll just skip. - return; - } - - for (var group : groupedSuggestions) { - if (!feature.getName().equals(group.getFeature())) { - continue; - } - - group.showAll(AnnotationSuggestion.FLAG_ALL); - - var position = group.getPosition(); - - // If any annotation at this position has a non-null label for this feature, - // then we hide the suggestion group - for (var annotationFS : groupedAnnotations.get(position)) { - if (annotationFS.getFeatureValueAsString(feat) != null) { - for (RelationSuggestion suggestion : group) { - suggestion.hide(FLAG_OVERLAP); - } - } - } - - // Hide previously rejected suggestions - for (var learningRecord : groupedRecordedAnnotations.get(position)) { - for (var suggestion : group) { - if (suggestion.labelEquals(learningRecord.getAnnotation())) { - suggestion.hideSuggestion(learningRecord.getUserAction()); - } - } - } - } - } - } - - @Nullable - private Type getAnnotationType(CAS aCas, AnnotationLayer aLayer) - { - // NOTE: In order to avoid having to upgrade the "original CAS" in computePredictions,this - // method is implemented in such a way that it gracefully handles cases where the CAS and - // the project type system are not in sync - specifically the CAS where the project defines - // layers or features which do not exist in the CAS. - - try { - return CasUtil.getAnnotationType(aCas, aLayer.getName()); - } - catch (IllegalArgumentException e) { - // Type does not exist in the type system of the CAS. Probably it has not been upgraded - // to the latest version of the type system yet. If this is the case, we'll just skip. - return null; - } - } - - private List getAnnotationsInWindow(CAS aCas, Type type, int aWindowBegin, - int aWindowEnd) - { - if (type == null) { - return Collections.emptyList(); - } - - return select(aCas, type).stream() // - .filter(fs -> fs.coveredBy(aWindowBegin, aWindowEnd)) // - .toList(); - } - - @Override - public LearningRecord toLearningRecord(SourceDocument aDocument, String aDataOwner, - AnnotationSuggestion aSuggestion, AnnotationFeature aFeature, - LearningRecordUserAction aUserAction, LearningRecordChangeLocation aLocation) - { - var pos = ((RelationSuggestion) aSuggestion).getPosition(); - var record = new LearningRecord(); - record.setUser(aDataOwner); - record.setSourceDocument(aDocument); - record.setUserAction(aUserAction); - record.setOffsetBegin(pos.getSourceBegin()); - record.setOffsetEnd(pos.getSourceEnd()); - record.setOffsetBegin2(pos.getTargetBegin()); - record.setOffsetEnd2(pos.getTargetEnd()); - record.setTokenText(""); - record.setAnnotation(aSuggestion.getLabel()); - record.setLayer(aFeature.getLayer()); - record.setSuggestionType(TYPE); - record.setChangeLocation(aLocation); - record.setAnnotationFeature(aFeature); - return record; + return groupedAnnotations; } @Override @@ -441,4 +327,12 @@ private static Optional findEquivalentSpan(CAS aOriginalCas, aAnnotation, null)) .findFirst(); } + + private List getAnnotationsInWindow(CAS aCas, Type type, int aWindowBegin, + int aWindowEnd) + { + return select(aCas, type).stream() // + .filter(fs -> fs.coveredBy(aWindowBegin, aWindowEnd)) // + .toList(); + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java index eb32370936a..2328f84083e 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java @@ -500,8 +500,11 @@ public void onDocumentOpened(DocumentOpenedEvent aEvent) var trigger = aEvent.getClass().getSimpleName(); if (!predictionSessionExistedOnOpen) { // Activate all non-trainable recommenders - execute synchronously - blocking - schedulingService.executeSync( - new NonTrainableRecommenderActivationTask(sessionOwner, project, trigger)); + schedulingService.executeSync(NonTrainableRecommenderActivationTask.builder() // + .withSessionOwner(sessionOwner) // + .withProject(project) // + .withTrigger(trigger) // + .build()); } // Check if we need to wait for the initial recommender run before displaying the document @@ -555,7 +558,12 @@ private boolean nonTrainableRecommenderRunSync(SourceDocument doc, Predictions p } LOG.trace("Running sync prediction for non-trainable recommenders"); - schedulingService.executeSync(new PredictionTask(aSessionOwner, trigger, doc, aDataOwner)); + schedulingService.executeSync(PredictionTask.builder() // + .withSessionOwner(aSessionOwner) // + .withTrigger(trigger) // + .withCurrentDocument(doc) // + .withDataOwner(aDataOwner) // + .build()); switchPredictions(aSessionOwner.getUsername(), doc.getProject()); return true; @@ -794,7 +802,12 @@ public void triggerPrediction(String aUsername, String aEventName, SourceDocumen return; } - schedulingService.enqueue(new PredictionTask(user, aEventName, aDocument, aDataOwner)); + schedulingService.enqueue(PredictionTask.builder() // + .withSessionOwner(user) // + .withTrigger(aEventName) // + .withCurrentDocument(aDocument) // + .withDataOwner(aDataOwner) // + .build()); } @Override @@ -838,8 +851,13 @@ private void triggerTraining(String aSessionOwner, Project aProject, String aEve // If it is time for a selection task, we just start a selection task. // The selection task then will start the training once its finished, // i.e. we do not start it here. - schedulingService.enqueue( - new SelectionTask(user, aProject, aEventName, aCurrentDocument, aDataOwner)); + schedulingService.enqueue(SelectionTask.builder() // + .withSessionOwner(user) // + .withProject(aProject) // + .withTrigger(aEventName) // + .withCurrentDocument(aCurrentDocument) // + .withDataOwner(aDataOwner) // + .build()); var state = getState(aSessionOwner, aProject); synchronized (state) { @@ -850,8 +868,13 @@ private void triggerTraining(String aSessionOwner, Project aProject, String aEve return; } - schedulingService.enqueue( - new TrainingTask(user, aProject, aEventName, aCurrentDocument, aDataOwner)); + schedulingService.enqueue(TrainingTask.builder() // + .withSessionOwner(user) // + .withProject(aProject) // + .withTrigger(aEventName) // + .withCurrentDocument(aCurrentDocument) // + .withDataOwner(aDataOwner) // + .build()); var state = getState(aSessionOwner, aProject); synchronized (state) { @@ -1375,9 +1398,18 @@ public void logRecord(LearningRecord aRecord) records.add(0, aRecord); } - public List listLearningRecords(AnnotationLayer aLayer) + public List listLearningRecords(String aDataOwner, AnnotationLayer aLayer) { - return learningRecords.getOrDefault(aLayer, Collections.emptyList()); + return learningRecords.computeIfAbsent(aLayer, + $ -> RecommendationServiceImpl.this.loadLearningRecords(aDataOwner, aLayer, 0)); + } + + public void removeLearningRecords(String aDataOwner, SourceDocument aDocument) + { + for (var records : learningRecords.values()) { + records.removeIf(r -> Objects.equals(r.getUser(), aDataOwner) && // + Objects.equals(r.getSourceDocument(), aDocument)); + } } public void removeLearningRecords(LearningRecord aRecord) @@ -1642,7 +1674,7 @@ public List listLearningRecords(String aSessionOwner, SourceDocu { var state = getState(aSessionOwner, aDocument.getProject()); synchronized (state) { - return state.listLearningRecords(aFeature.getLayer()).stream() + return state.listLearningRecords(aDataOwner, aFeature.getLayer()).stream() .filter(r -> Objects.equals(r.getAnnotationFeature(), aFeature) && Objects.equals(r.getSourceDocument(), aDocument) && Objects.equals(r.getUser(), aDataOwner) @@ -1658,7 +1690,7 @@ public List listLearningRecords(String aSessionOwner, String aDa { var state = getState(aSessionOwner, aLayer.getProject()); synchronized (state) { - var stream = state.listLearningRecords(aLayer).stream() + var stream = state.listLearningRecords(aDataOwner, aLayer).stream() .filter(r -> Objects.equals(r.getUser(), aDataOwner) && r.getUserAction() != LearningRecordUserAction.SHOWN); if (aLimit > 0) { @@ -1708,22 +1740,32 @@ public void createLearningRecord(LearningRecord aLearningRecord) entityManager.flush(); } - private void deleteLearningRecords(SourceDocument document, String user) + private void deleteLearningRecords(SourceDocument aDocument, String aDataOwner) { - String sql = "DELETE FROM LearningRecord l where l.sourceDocument = :document and l.user " + var state = getState(aDataOwner, aDocument.getProject()); + synchronized (state) { + state.removeLearningRecords(aDataOwner, aDocument); + } + + var sql = "DELETE FROM LearningRecord l where l.sourceDocument = :document and l.user " + "= :user"; entityManager.createQuery(sql) // - .setParameter("document", document) // - .setParameter("user", user) // + .setParameter("document", aDocument) // + .setParameter("user", aDataOwner) // .executeUpdate(); } @Override @Transactional - public void deleteLearningRecord(LearningRecord learningRecord) + public void deleteLearningRecord(LearningRecord aRecord) { - entityManager.remove(entityManager.contains(learningRecord) ? learningRecord - : entityManager.merge(learningRecord)); + var state = getState(aRecord.getUser(), aRecord.getLayer().getProject()); + synchronized (state) { + state.removeLearningRecords(aRecord); + } + + entityManager + .remove(entityManager.contains(aRecord) ? aRecord : entityManager.merge(aRecord)); } @Override diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java index 15f71df77ff..f57fdd45766 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionSupport.java @@ -17,9 +17,11 @@ */ package de.tudarmstadt.ukp.inception.recommendation.span; +import static de.tudarmstadt.ukp.clarin.webanno.model.MultiValueMode.NONE; import static de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion.FLAG_OVERLAP; import static java.lang.Math.max; import static java.lang.Math.min; +import static java.util.Arrays.asList; import static java.util.Comparator.comparingInt; import static org.apache.uima.cas.text.AnnotationPredicates.colocated; import static org.apache.uima.fit.util.CasUtil.getType; @@ -52,6 +54,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.AnchoringMode; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.clarin.webanno.model.MultiValueMode; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.TrimUtils; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence; @@ -110,7 +113,10 @@ public boolean accepts(Recommender aContext) } var feature = aContext.getFeature(); - if (CAS.TYPE_NAME_STRING.equals(feature.getType()) || feature.isVirtualFeature()) { + if (asList(CAS.TYPE_NAME_STRING, CAS.TYPE_NAME_BOOLEAN).contains(feature.getType()) + // || not all supported yet - ICasUtil.isPrimitive(feature.getType()) + || CAS.TYPE_NAME_STRING_ARRAY.equals(feature.getType()) + || feature.isVirtualFeature()) { return true; } @@ -153,7 +159,8 @@ public AnnotationFS acceptOrCorrectSuggestion(String aSessionOwner, SourceDocume // If there is an annotation where the predicted feature is unset, use it ... annotation = candidateWithEmptyLabel.get(); } - else if (candidates.isEmpty() || aAdapter.getLayer().isAllowStacking()) { + else if (candidates.isEmpty() || (aAdapter.getLayer().isAllowStacking() + && aFeature.getMultiValueMode() == NONE)) { // ... if not or if stacking is allowed, then we create a new annotation - this also // takes care of attaching to an annotation if necessary annotation = aAdapter.add(aDocument, aDataOwner, aCas, aBegin, aEnd); @@ -209,7 +216,7 @@ public void calculateSuggestionVisibility(Strin .filter(group -> group.getLayerId() == aLayer.getId()) // ... and in the given window .filter(group -> { - Offset offset = (Offset) group.getPosition(); + var offset = (Offset) group.getPosition(); return AnnotationPredicates.coveredBy(offset.getBegin(), offset.getEnd(), aWindowBegin, aWindowEnd); }) // @@ -279,6 +286,9 @@ private void hideSpanSuggestionsThatOverlapWithAnnotations( // the keys in a TreeSet - and the annotation offsets are sorted in the same way manually var oi = new OverlapIterator(new ArrayList<>(suggestions.keySet()), sortedAnnotationKeys); + var stackableSuggestions = feature.getLayer().isAllowStacking() + || feature.getMultiValueMode() != MultiValueMode.NONE; + // Bulk-hide any groups that overlap with existing annotations on the current layer // and for the current feature var hiddenForOverlap = new ArrayList(); @@ -289,56 +299,69 @@ private void hideSpanSuggestionsThatOverlapWithAnnotations( // Fetch the current suggestion and annotation var group = suggestions.get(suggestionOffset); for (var annotation : annotations.get(annotationOffset)) { - var label = annotation.getFeatureValueAsString(feat); - for (var suggestion : group) { - // The suggestion would just create an annotation and not set any - // feature - var colocated = colocated(annotation, suggestion.getBegin(), - suggestion.getEnd()); - if (suggestion.getLabel() == null) { - // If there is already an annotation, then we hide any suggestions - // that would just trigger the creation of the same annotation and - // not set any new feature. This applies whether stacking is allowed - // or not. - if (colocated) { - suggestion.hide(FLAG_OVERLAP); - hiddenForOverlap.add(suggestion); - continue; - } - - // If stacking is enabled, we do allow suggestions that create an - // annotation with no label, but only if the offsets differ - if (!(feature.getLayer().isAllowStacking() && !colocated)) { - suggestion.hide(FLAG_OVERLAP); - hiddenForOverlap.add(suggestion); - continue; - } - } - // The suggestion would merge the suggested feature value into an - // existing annotation or create a new annotation with the feature if - // stacking were enabled. - else { - // Is the feature still unset in the current annotation - i.e. would - // accepting the suggestion merge the feature into it? If yes, we do - // not hide - if (label == null && suggestion.getLabel() != null && colocated) { - continue; - } + Iterable labelObjects; + var value = featureSupportRegistry.findExtension(feature).get() + .getFeatureValue(feature, annotation); + if (value instanceof Iterable iterableValues) { + labelObjects = iterableValues; + } + else { + labelObjects = asList(value); + } - // Does the suggested label match the label of an existing annotation - // at the same position then we hide - if (label != null && label.equals(suggestion.getLabel()) && colocated) { - suggestion.hide(FLAG_OVERLAP); - hiddenForOverlap.add(suggestion); - continue; + for (var labelObject : labelObjects) { + var label = labelObject != null ? String.valueOf(labelObject) : null; + + for (var suggestion : group) { + // The suggestion would just create an annotation and not set any + // feature + var colocated = colocated(annotation, suggestion.getBegin(), + suggestion.getEnd()); + if (suggestion.getLabel() == null) { + // If there is already an annotation, then we hide any suggestions + // that would just trigger the creation of the same annotation and + // not set any new feature. This applies whether stacking is allowed + // or not. + if (colocated) { + suggestion.hide(FLAG_OVERLAP); + hiddenForOverlap.add(suggestion); + continue; + } + + // If stacking is enabled, we do allow suggestions that create an + // annotation with no label, but only if the offsets differ + if (!(stackableSuggestions && !colocated)) { + suggestion.hide(FLAG_OVERLAP); + hiddenForOverlap.add(suggestion); + continue; + } } - - // Would accepting the suggestion create a new annotation but - // stacking is not enabled - then we need to hide - if (!feature.getLayer().isAllowStacking()) { - suggestion.hide(FLAG_OVERLAP); - hiddenForOverlap.add(suggestion); - continue; + // The suggestion would merge the suggested feature value into an + // existing annotation or create a new annotation with the feature if + // stacking were enabled. + else { + // Is the feature still unset in the current annotation - i.e. would + // accepting the suggestion merge the feature into it? If yes, we do + // not hide + if (label == null && suggestion.getLabel() != null && colocated) { + continue; + } + + // Does the suggested label match the label of an existing annotation + // at the same position then we hide + if (label != null && label.equals(suggestion.getLabel()) && colocated) { + suggestion.hide(FLAG_OVERLAP); + hiddenForOverlap.add(suggestion); + continue; + } + + // Would accepting the suggestion create a new annotation but + // stacking is not enabled - then we need to hide + if (!stackableSuggestions) { + suggestion.hide(FLAG_OVERLAP); + hiddenForOverlap.add(suggestion); + continue; + } } } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/BulkPredictionTask.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/BulkPredictionTask.java index 3b356fd8adc..338af3f1929 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/BulkPredictionTask.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/BulkPredictionTask.java @@ -25,6 +25,7 @@ import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateChangeFlag.EXPLICIT_ANNOTATOR_USER_ACTION; import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordChangeLocation.AUTO_ACCEPT; import static de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecordUserAction.ACCEPTED; +import static de.tudarmstadt.ukp.inception.scheduling.TaskScope.PROJECT; import java.io.IOException; import java.lang.invoke.MethodHandles; @@ -32,6 +33,7 @@ import java.util.Objects; import java.util.Optional; +import org.apache.commons.lang3.Validate; import org.apache.uima.cas.CAS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +41,6 @@ import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.recommendation.api.SuggestionSupport; @@ -50,11 +51,14 @@ import de.tudarmstadt.ukp.inception.scheduling.TaskState; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; import de.tudarmstadt.ukp.inception.schema.api.adapter.AnnotationException; +import de.tudarmstadt.ukp.inception.support.WebAnnoConst; import de.tudarmstadt.ukp.inception.support.logging.LogMessage; public class BulkPredictionTask extends RecommendationTask_ImplBase { + public static final String TYPE = "BulkPredictionTask"; + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final String dataOwner; @@ -66,12 +70,12 @@ public class BulkPredictionTask private @Autowired SuggestionSupportRegistry suggestionSupportRegistry; private @Autowired AnnotationSchemaService schemaService; - public BulkPredictionTask(User aSessionOwner, Recommender aRecommender, String aTrigger, - String aDataOwner) + public BulkPredictionTask(Builder> aBuilder) { - super(aSessionOwner, aRecommender.getProject(), aTrigger); - dataOwner = aDataOwner; - recommender = aRecommender; + super(aBuilder.withType(TYPE).withCancellable(true).withScope(PROJECT)); + + recommender = aBuilder.recommender; + dataOwner = aBuilder.dataOwner; } @Override @@ -105,12 +109,12 @@ public void execute() var maxProgress = annotatableDocuments.size(); var progress = maxProgress - processableDocuments.size(); - if (processableDocuments.isEmpty() || isCancelled()) { + if (processableDocuments.isEmpty() || monitor.isCancelled()) { monitor.setProgressWithMessage(progress, maxProgress, LogMessage.info(this, "%d annotations generated from %d suggestions in %d documents", annotationsCount, suggestionsCount, processedDocumentsCount)); - if (isCancelled()) { + if (monitor.isCancelled()) { monitor.setState(TaskState.CANCELLED); } break; @@ -146,10 +150,15 @@ public void execute() private Predictions generatePredictions(SourceDocument doc) { - var predictionTask = new PredictionTask(getUser().get(), "Bulk prediction", doc, dataOwner); - predictionTask.setParentTask(this); - predictionTask.setIsolated(true); - predictionTask.setRecommender(recommender); + var predictionTask = PredictionTask.builder() // + .withSessionOwner(getUser().get()) // + .withTrigger("Bulk prediction") // + .withCurrentDocument(doc) // + .withDataOwner(dataOwner) // + .withParentTask(this) // + .withIsolated(true) // + .withRecommender(recommender) // + .build(); schedulingService.executeSync(predictionTask); return predictionTask.getPredictions(); } @@ -188,4 +197,49 @@ private int autoAccept(SourceDocument aDocument, Predictions predictions, CAS ca LOG.debug("Auto-accepted [{}] suggestions", accepted); return accepted; } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends RecommendationTask_ImplBase.Builder + { + private Recommender recommender; + private String dataOwner; + + @SuppressWarnings("unchecked") + public T withRecommender(Recommender aRecommender) + { + recommender = aRecommender; + return (T) this; + } + + /** + * @param aDataOwner + * the user owning the annotations currently shown in the editor (this can differ + * from the user owning the session e.g. if a manager views another users + * annotations or a curator is performing curation to the + * {@link WebAnnoConst#CURATION_USER}) + */ + @SuppressWarnings("unchecked") + public T withDataOwner(String aDataOwner) + { + dataOwner = aDataOwner; + return (T) this; + } + + public BulkPredictionTask build() + { + withProject(recommender.getProject()); + + Validate.notNull(sessionOwner, "BulkPredictionTask requires a session owner"); + Validate.notNull(dataOwner, "BulkPredictionTask requires a data owner"); + Validate.notNull(recommender, "BulkPredictionTask requires a recommender"); + Validate.notNull(project, "BulkPredictionTask requires a project"); + + return new BulkPredictionTask(this); + } + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/NonTrainableRecommenderActivationTask.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/NonTrainableRecommenderActivationTask.java index 46dd454c9ce..6ce250741ff 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/NonTrainableRecommenderActivationTask.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/NonTrainableRecommenderActivationTask.java @@ -21,6 +21,7 @@ import static de.tudarmstadt.ukp.inception.support.logging.LogLevel.ERROR; import static java.lang.System.currentTimeMillis; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -32,7 +33,6 @@ import org.springframework.context.ApplicationEventPublisher; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; import de.tudarmstadt.ukp.inception.recommendation.api.model.EvaluatedRecommender; @@ -52,15 +52,17 @@ public class NonTrainableRecommenderActivationTask extends RecommendationTask_ImplBase { - private final Logger log = LoggerFactory.getLogger(getClass()); + public static final String TYPE = "NonTrainableRecommenderActivationTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired AnnotationSchemaService annoService; private @Autowired RecommendationService recommendationService; private @Autowired ApplicationEventPublisher appEventPublisher; - public NonTrainableRecommenderActivationTask(User aUser, Project aProject, String aTrigger) + public NonTrainableRecommenderActivationTask(Builder> aBuilder) { - super(aUser, aProject, aTrigger); + super(aBuilder.withType(TYPE)); } @Override @@ -111,7 +113,7 @@ public void execute() catch (Throwable e) { // Catching Throwable is intentional here as we want to continue the execution // even if a particular recommender fails. - log.error("[{}][{}]: Failed", user.getUsername(), recommenderName, e); + LOG.error("[{}][{}]: Failed", user.getUsername(), recommenderName, e); appEventPublisher.publishEvent(RecommenderTaskNotificationEvent .builder(this, getProject(), user.getUsername()) // .withMessage(new LogMessage(this, ERROR, e.getMessage())) // @@ -164,7 +166,7 @@ private EvaluatedRecommender activateNonTrainableRecommender(User user, Recommen recommendationService.putContext(user, recommender, ctx); String recommenderName = recommender.getName(); - log.debug("[{}][{}]: Activating [{}] non-trainable recommender", user.getUsername(), + LOG.debug("[{}][{}]: Activating [{}] non-trainable recommender", user.getUsername(), recommenderName, recommenderName); info("Recommender [%s] activated because it is not trainable", recommenderName); return EvaluatedRecommender.makeActiveWithoutEvaluation(recommender); @@ -173,7 +175,7 @@ private EvaluatedRecommender activateNonTrainableRecommender(User user, Recommen private EvaluatedRecommender skipTrainableRecommender(User user, Recommender recommender) { String recommenderName = recommender.getName(); - log.debug( + LOG.debug( "[{}][{}]: Recommender requires training - deferring activation to selection task", user.getUsername(), recommenderName); info("Recommender [%s] requires training - deferring activation to selection task", @@ -185,7 +187,7 @@ private EvaluatedRecommender skipRecommenderWithInvalidSettings(User user, Recommender recommender) { String recommenderName = recommender.getName(); - log.info("[{}][{}]: Recommender configured with invalid layer or feature " + LOG.info("[{}][{}]: Recommender configured with invalid layer or feature " + "- skipping recommender", user.getUsername(), recommenderName); info("Recommender [%s] configured with invalid layer or feature - skipping recommender", recommenderName); @@ -195,7 +197,7 @@ private EvaluatedRecommender skipRecommenderWithInvalidSettings(User user, private void sendMissingFactoryNotification(User user, Recommender recommender) { - log.error("[{}][{}]: No recommender factory available for [{}]", user.getUsername(), + LOG.error("[{}][{}]: No recommender factory available for [{}]", user.getUsername(), recommender.getName(), recommender.getTool()); appEventPublisher.publishEvent( RecommenderTaskNotificationEvent.builder(this, getProject(), user.getUsername()) // @@ -213,17 +215,31 @@ private Optional freshenRecommender(User aUser, Recommender r) recommender = recommendationService.getRecommender(r.getId()); } catch (NoResultException e) { - log.info("[{}][{}]: Recommender no longer available - skipping", aUser.getUsername(), + LOG.info("[{}][{}]: Recommender no longer available - skipping", aUser.getUsername(), r.getName()); return Optional.empty(); } if (!recommender.isEnabled()) { - log.debug("[{}][{}]: Recommender is disabled - skipping", aUser.getUsername(), + LOG.debug("[{}][{}]: Recommender is disabled - skipping", aUser.getUsername(), recommender.getName()); return Optional.empty(); } return Optional.of(recommender); } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends RecommendationTask_ImplBase.Builder + { + public NonTrainableRecommenderActivationTask build() + { + return new NonTrainableRecommenderActivationTask(this); + } + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTask.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTask.java index 894a3daf725..b43a2cbe608 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTask.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTask.java @@ -36,6 +36,7 @@ import java.util.Objects; import java.util.Optional; +import org.apache.commons.lang3.Validate; import org.apache.uima.UIMAException; import org.apache.uima.cas.CAS; import org.apache.uima.resource.ResourceInitializationException; @@ -75,6 +76,8 @@ public class PredictionTask extends RecommendationTask_ImplBase { + public static final String TYPE = "PredictionTask"; + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String PREDICTION_CAS = "predictionCas"; @@ -89,66 +92,21 @@ public class PredictionTask private final int predictionBegin; private final int predictionEnd; private final String dataOwner; + private final boolean isolated; + private final Recommender recommender; - private boolean isolated = false; private Predictions predictions; - private Recommender recommender; - - /** - * Create a new prediction task. - * - * @param aSessionOwner - * the user owning the training session. - * @param aTrigger - * the trigger that caused the selection to be scheduled. - * @param aCurrentDocument - * the document currently open in the editor. - * @param aDataOwner - * the user owning the annotations currently shown in the editor (this can differ - * from the user owning the session e.g. if a manager views another users annotations - * or a curator is performing curation to the {@link WebAnnoConst#CURATION_USER}) - */ - public PredictionTask(User aSessionOwner, String aTrigger, SourceDocument aCurrentDocument, - String aDataOwner) - { - this(aSessionOwner, aTrigger, aCurrentDocument, -1, -1, aDataOwner); - } - public PredictionTask(User aSessionOwner, String aTrigger, SourceDocument aCurrentDocument, - int aBegin, int aEnd, String aDataOwner) + public PredictionTask(Builder> aBuilder) { - super(aSessionOwner, aCurrentDocument.getProject(), aTrigger); - currentDocument = aCurrentDocument; - predictionBegin = aBegin; - predictionEnd = aEnd; - dataOwner = aDataOwner; - } - - /** - * Whether to use the recommender session data from the recommender service or to run the - * prediction task in isolation from the session. Running in isolation is useful when - * predictions should be generated outside the normal automatically triggered recommender runs. - * After the isolated run has completed, the results can be picked up using - * {@link #getPredictions()}. - * - * @param aIsolated - * whether to run the task in isolation. - */ - public void setIsolated(boolean aIsolated) - { - isolated = aIsolated; - } - - /** - * Generate predictions only for the specified recommender. If this is not set, then predictions - * will be run for all active recommenders. - * - * @param aRecommender - * the one recommender to run. - */ - public void setRecommender(Recommender aRecommender) - { - recommender = aRecommender; + super(aBuilder.withType(TYPE)); + + currentDocument = aBuilder.currentDocument; + dataOwner = aBuilder.dataOwner; + predictionBegin = aBuilder.predictionBegin; + predictionEnd = aBuilder.predictionEnd; + isolated = aBuilder.isolated; + recommender = aBuilder.recommender; } /** @@ -977,4 +935,93 @@ public void close() final record ReconciliationResult(int added, int removed, int aged, List suggestions) {} + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends RecommendationTask_ImplBase.Builder + { + private Recommender recommender; + private SourceDocument currentDocument; + private String dataOwner; + private int predictionBegin = -1; + private int predictionEnd = -1; + private boolean isolated = false; + + /** + * Generate predictions only for the specified recommender. If this is not set, then + * predictions will be run for all active recommenders. + * + * @param aRecommender + * the one recommender to run. + */ + @SuppressWarnings("unchecked") + public T withRecommender(Recommender aRecommender) + { + recommender = aRecommender; + return (T) this; + } + + /** + * @param aCurrentDocuemnt + * the document currently open in the editor of the user triggering the task. + */ + @SuppressWarnings("unchecked") + public T withCurrentDocument(SourceDocument aCurrentDocuemnt) + { + currentDocument = aCurrentDocuemnt; + return (T) this; + } + + /** + * @param aDataOwner + * the user owning the annotations currently shown in the editor (this can differ + * from the user owning the session e.g. if a manager views another users + * annotations or a curator is performing curation to the + * {@link WebAnnoConst#CURATION_USER}) + */ + @SuppressWarnings("unchecked") + public T withDataOwner(String aDataOwner) + { + dataOwner = aDataOwner; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withRange(int aBegin, int aEnd) + { + predictionBegin = aBegin; + predictionEnd = aEnd; + return (T) this; + } + + /** + * Whether to use the recommender session data from the recommender service or to run the + * prediction task in isolation from the session. Running in isolation is useful when + * predictions should be generated outside the normal automatically triggered recommender + * runs. After the isolated run has completed, the results can be picked up using + * {@link #getPredictions()}. + * + * @param aIsolated + * whether to run the task in isolation. + */ + @SuppressWarnings("unchecked") + public T withIsolated(boolean aIsolated) + { + isolated = aIsolated; + return (T) this; + } + + public PredictionTask build() + { + Validate.notNull(sessionOwner, "SelectionTask requires a user"); + + withProject(currentDocument.getProject()); + + return new PredictionTask(this); + } + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/RecommendationTask_ImplBase.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/RecommendationTask_ImplBase.java index eaf0663c39f..57fedf41cf1 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/RecommendationTask_ImplBase.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/RecommendationTask_ImplBase.java @@ -22,8 +22,6 @@ import java.util.ArrayList; import java.util.List; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.scheduling.Task; import de.tudarmstadt.ukp.inception.support.logging.LogMessage; @@ -32,14 +30,9 @@ public abstract class RecommendationTask_ImplBase { private final List logMessages = new ArrayList<>(); - public RecommendationTask_ImplBase(Project aProject, String aTrigger) + protected RecommendationTask_ImplBase(Builder> aBuilder) { - super(aProject, aTrigger); - } - - public RecommendationTask_ImplBase(User aSessionOwner, Project aProject, String aTrigger) - { - super(aSessionOwner, aProject, aTrigger); + super(aBuilder); } public void inheritLog(List aLogMessages) @@ -76,4 +69,10 @@ public void log(LogMessage aMessage) { logMessages.add(aMessage); } + + public static abstract class Builder> + extends Task.Builder + { + // No changes + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java index c26f5c261d6..cc804374f3a 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/SelectionTask.java @@ -23,10 +23,12 @@ import static java.text.MessageFormat.format; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.ConcurrentException; import org.apache.commons.lang3.concurrent.LazyInitializer; import org.apache.uima.cas.CAS; @@ -66,7 +68,9 @@ public class SelectionTask extends RecommendationTask_ImplBase { - private final Logger log = LoggerFactory.getLogger(getClass()); + public static final String TYPE = "SelectionTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired AnnotationSchemaService annoService; private @Autowired DocumentService documentService; @@ -77,33 +81,12 @@ public class SelectionTask private final SourceDocument currentDocument; private final String dataOwner; - /** - * Create a new selection task. - * - * @param aUser - * the user owning the selection session. - * @param aProject - * the project to perform the selection on. - * @param aTrigger - * the trigger that caused the selection to be scheduled. - * @param aCurrentDocument - * the document currently open in the editor. - * @param aDataOwner - * the user owning the annotations currently shown in the editor (this can differ - * from the user owning the session e.g. if a manager views another users annotations - * or a curator is performing curation to the {@link WebAnnoConst#CURATION_USER}) - */ - public SelectionTask(User aUser, Project aProject, String aTrigger, - SourceDocument aCurrentDocument, String aDataOwner) + public SelectionTask(Builder> aBuilder) { - super(aUser, aProject, aTrigger); - - if (getUser().isEmpty()) { - throw new IllegalArgumentException("SelectionTask requires a user"); - } + super(aBuilder.withType(TYPE)); - currentDocument = aCurrentDocument; - dataOwner = aDataOwner; + currentDocument = aBuilder.currentDocument; + dataOwner = aBuilder.dataOwner; } @Override @@ -215,44 +198,49 @@ protected List initialize() private void logSelectionComplete(long startTime, String username) { var duration = currentTimeMillis() - startTime; - log.debug("[{}][{}]: Selection complete ({} ms)", getId(), username, duration); + LOG.debug("[{}][{}]: Selection complete ({} ms)", getId(), username, duration); info("Selection complete (%d ms).", duration); } private void logRecommenderGone(User user, Recommender aRecommender) { - log.debug("[{}][{}][{}]: Recommender no longer available... skipping", getId(), + LOG.debug("[{}][{}][{}]: Recommender no longer available... skipping", getId(), user.getUsername(), aRecommender.getName()); } private void logSelectionStarted(User sessionOwner) { - log.info("[{}]: Starting selection triggered by [{}]", sessionOwner.getUsername(), + LOG.info("[{}]: Starting selection triggered by [{}]", sessionOwner.getUsername(), getTrigger()); info("Starting selection triggered by [%s]", getTrigger()); } private void scheduleTrainingTask(User sessionOwner) { - TrainingTask trainingTask = new TrainingTask(sessionOwner, getProject(), - "SelectionTask after activating recommenders", currentDocument, dataOwner); + var trainingTask = TrainingTask.builder() // + .withSessionOwner(sessionOwner) // + .withProject(getProject()) // + .withTrigger("SelectionTask after activating recommenders") // + .withCurrentDocument(currentDocument) // + .withDataOwner(dataOwner) // + .build(); trainingTask.inheritLog(this); schedulingService.enqueue(trainingTask); } private void logNoRecommendersActive(String sessionOwnerName) { - log.debug("[{}]: No recommenders active, skipping training.", sessionOwnerName); + LOG.debug("[{}]: No recommenders active, skipping training.", sessionOwnerName); } private void logNoRecommendersSeen(String sessionOwnerName) { - log.trace("[{}]: No recommenders configured, skipping training.", sessionOwnerName); + LOG.trace("[{}]: No recommenders configured, skipping training.", sessionOwnerName); } private void logEvaluationSuccessful(User sessionOwner) { - log.info("[{}]: Evaluation complete", sessionOwner.getUsername()); + LOG.info("[{}]: Evaluation complete", sessionOwner.getUsername()); appEventPublisher.publishEvent(RecommenderTaskNotificationEvent .builder(this, getProject(), sessionOwner.getUsername()) // .withMessage(LogMessage.info(this, "Evaluation complete")) // @@ -262,7 +250,7 @@ private void logEvaluationSuccessful(User sessionOwner) private void logEvaluationFailed(Project project, User sessionOwner, String recommenderName, Throwable e) { - log.error("[{}][{}]: Evaluation failed", sessionOwner.getUsername(), recommenderName, e); + LOG.error("[{}][{}]: Evaluation failed", sessionOwner.getUsername(), recommenderName, e); appEventPublisher.publishEvent( RecommenderTaskNotificationEvent.builder(this, project, sessionOwner.getUsername()) // .withMessage(LogMessage.error(this, e.getMessage())) // @@ -271,7 +259,7 @@ private void logEvaluationFailed(Project project, User sessionOwner, String reco private void logNoRecommenders(String sessionOwnerName, AnnotationLayer layer) { - log.trace("[{}][{}]: No recommenders, skipping selection.", sessionOwnerName, + LOG.trace("[{}][{}]: No recommenders, skipping selection.", sessionOwnerName, layer.getUiName()); } @@ -300,7 +288,7 @@ private Optional evaluate(User user, Recommender recommend return Optional.of(activateNonEvaluatableRecommender(userName, recommender)); } - log.info("[{}][{}]: Evaluating...", userName, recommender.getName()); + LOG.info("[{}][{}]: Evaluating...", userName, recommender.getName()); var splitter = new PercentageBasedSplitter(0.8, 10); var recommendationEngine = factory.build(recommender); @@ -331,7 +319,7 @@ private EvaluatedRecommender skipRecommenderBelowThreshold(User user, Recommende EvaluationResult result, double score, double threshold) { String recommenderName = recommender.getName(); - log.info("[{}][{}]: Not activated ({} < threshold {})", user.getUsername(), recommenderName, + LOG.info("[{}][{}]: Not activated ({} < threshold {})", user.getUsername(), recommenderName, score, threshold); info("Recommender [%s] not activated (%f < threshold %f)", recommenderName, score, threshold); @@ -346,7 +334,7 @@ private EvaluatedRecommender activateRecommenderAboveThreshold(User user, EvaluatedRecommender evaluatedRecommender = EvaluatedRecommender.makeActive(recommender, result, format("Score {0,number,#.####} >= threshold {1,number,#.####}", score, threshold)); - log.info("[{}][{}]: Activated ({} >= threshold {})", user.getUsername(), recommenderName, + LOG.info("[{}][{}]: Activated ({} >= threshold {})", user.getUsername(), recommenderName, score, threshold); info("Recommender [%s] activated (%f >= threshold %f)", recommenderName, score, threshold); return evaluatedRecommender; @@ -358,7 +346,7 @@ private EvaluatedRecommender skipRecommenderDueToFailedEvaluation(User user, String recommenderName = recommender.getName(); String msg = String.format("Evaluation of recommender [%s] could not be performed: %s", recommenderName, result.getErrorMsg().orElse("unknown reason")); - log.info("[{}][{}]: {}", user.getUsername(), recommenderName, msg); + LOG.info("[{}][{}]: {}", user.getUsername(), recommenderName, msg); warn("%s", msg); return EvaluatedRecommender.makeInactiveWithoutEvaluation(recommender, msg); } @@ -367,7 +355,7 @@ private EvaluatedRecommender activateNonEvaluatableRecommender(String userName, Recommender recommender) { String recommenderName = recommender.getName(); - log.debug("[{}][{}]: Activating [{}] without evaluating - not evaluable", userName, + LOG.debug("[{}][{}]: Activating [{}] without evaluating - not evaluable", userName, recommenderName, recommenderName); info("Recommender [%s] activated without evaluating - not evaluable", recommenderName); return EvaluatedRecommender.makeActiveWithoutEvaluation(recommender); @@ -377,7 +365,7 @@ private EvaluatedRecommender activateAlwaysOnRecommender(String userName, Recommender recommender) { String recommenderName = recommender.getName(); - log.debug("[{}][{}]: Activating [{}] without evaluating - always selected", userName, + LOG.debug("[{}][{}]: Activating [{}] without evaluating - always selected", userName, recommenderName, recommenderName); info("Recommender [%s] activated without evaluating - always selected", recommenderName); return EvaluatedRecommender.makeActiveWithoutEvaluation(recommender); @@ -387,7 +375,7 @@ private EvaluatedRecommender skipRecommenderWithInvalidSettings(User user, Recommender recommender) { String recommenderName = recommender.getName(); - log.info("[{}][{}]: Recommender configured with invalid layer or feature " + LOG.info("[{}][{}]: Recommender configured with invalid layer or feature " + "- skipping recommender", user.getUsername(), recommenderName); info("Recommender [%s] configured with invalid layer or feature - skipping recommender", recommenderName); @@ -397,7 +385,7 @@ private EvaluatedRecommender skipRecommenderWithInvalidSettings(User user, private void sendMissingFactoryNotification(User user, Recommender recommender) { - log.error("[{}][{}]: No recommender factory available for [{}]", user.getUsername(), + LOG.error("[{}][{}]: No recommender factory available for [{}]", user.getUsername(), recommender.getName(), recommender.getTool()); appEventPublisher.publishEvent( RecommenderTaskNotificationEvent.builder(this, getProject(), user.getUsername()) // @@ -417,7 +405,7 @@ private List readCasses(Project aProject, String aUserName) casses.add(cas); } catch (IOException e) { - log.error("Cannot read annotation CAS.", e); + LOG.error("Cannot read annotation CAS.", e); } } return casses; @@ -432,16 +420,60 @@ private Optional freshenRecommender(User aUser, Recommender r) recommender = recommendationService.getRecommender(r.getId()); } catch (NoResultException e) { - log.info("[{}][{}]: Recommender no longer available... skipping", aUser.getUsername(), + LOG.info("[{}][{}]: Recommender no longer available... skipping", aUser.getUsername(), r.getName()); return Optional.empty(); } if (!recommender.isEnabled()) { - log.debug("[{}][{}]: Disabled - skipping", aUser.getUsername(), recommender.getName()); + LOG.debug("[{}][{}]: Disabled - skipping", aUser.getUsername(), recommender.getName()); return Optional.empty(); } return Optional.of(recommender); } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends RecommendationTask_ImplBase.Builder + { + private SourceDocument currentDocument; + private String dataOwner; + + /** + * @param aCurrentDocuemnt + * the document currently open in the editor of the user triggering the task. + */ + @SuppressWarnings("unchecked") + public T withCurrentDocument(SourceDocument aCurrentDocuemnt) + { + currentDocument = aCurrentDocuemnt; + return (T) this; + } + + /** + * @param aDataOwner + * the user owning the annotations currently shown in the editor (this can differ + * from the user owning the session e.g. if a manager views another users + * annotations or a curator is performing curation to the + * {@link WebAnnoConst#CURATION_USER}) + */ + @SuppressWarnings("unchecked") + public T withDataOwner(String aDataOwner) + { + dataOwner = aDataOwner; + return (T) this; + } + + public SelectionTask build() + { + Validate.notNull(sessionOwner, "SelectionTask requires a user"); + + return new SelectionTask(this); + } + } } diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java index 145be116fb1..6d9b7f6ed4e 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/tasks/TrainingTask.java @@ -25,6 +25,7 @@ import java.lang.invoke.MethodHandles; import java.util.List; +import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.ConcurrentException; import org.apache.uima.cas.CAS; import org.slf4j.Logger; @@ -32,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; @@ -51,6 +51,8 @@ public class TrainingTask extends RecommendationTask_ImplBase { + public static final String TYPE = "TrainingTask"; + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired DocumentService documentService; @@ -64,33 +66,12 @@ public class TrainingTask private boolean seenSuccessfulTraining = false; private boolean seenNonTrainingRecommender = false; - /** - * Create a new training task. - * - * @param aUser - * the user owning the training session. - * @param aProject - * the project to perform the selection on. - * @param aTrigger - * the trigger that caused the selection to be scheduled. - * @param aCurrentDocument - * the document currently open in the editor. - * @param aDataOwner - * the user owning the annotations currently shown in the editor (this can differ - * from the user owning the session e.g. if a manager views another users annotations - * or a curator is performing curation to the {@link WebAnnoConst#CURATION_USER}) - */ - public TrainingTask(User aUser, Project aProject, String aTrigger, - SourceDocument aCurrentDocument, String aDataOwner) + public TrainingTask(Builder> aBuilder) { - super(aUser, aProject, aTrigger); - - if (getUser().isEmpty()) { - throw new IllegalArgumentException("TrainingTask requires a user"); - } + super(aBuilder.withType(TYPE)); - currentDocument = aCurrentDocument; - dataOwner = aDataOwner; + currentDocument = aBuilder.currentDocument; + dataOwner = aBuilder.dataOwner; } @Override @@ -251,8 +232,12 @@ private void trainRecommender(Recommender aRecommender, LazyCasLoader casLoader) private void schedulePredictionTask() { - var predictionTask = new PredictionTask(getSessionOwner(), - String.format("TrainingTask %s complete", getId()), currentDocument, dataOwner); + var predictionTask = PredictionTask.builder() // + .withSessionOwner(getSessionOwner()) // + .withTrigger(String.format("TrainingTask %s complete", getId())) // + .withCurrentDocument(currentDocument) // + .withDataOwner(dataOwner) // + .build(); predictionTask.inheritLog(this); @@ -418,4 +403,48 @@ private void handleError(Recommender recommender, Throwable e) .withMessage(LogMessage.error(this, "Training failed with %s", e.getMessage())) .build()); } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends RecommendationTask_ImplBase.Builder + { + private SourceDocument currentDocument; + private String dataOwner; + + /** + * @param aCurrentDocuemnt + * the document currently open in the editor of the user triggering the task. + */ + @SuppressWarnings("unchecked") + public T withCurrentDocument(SourceDocument aCurrentDocuemnt) + { + currentDocument = aCurrentDocuemnt; + return (T) this; + } + + /** + * @param aDataOwner + * the user owning the annotations currently shown in the editor (this can differ + * from the user owning the session e.g. if a manager views another users + * annotations or a curator is performing curation to the + * {@link WebAnnoConst#CURATION_USER}) + */ + @SuppressWarnings("unchecked") + public T withDataOwner(String aDataOwner) + { + dataOwner = aDataOwner; + return (T) this; + } + + public TrainingTask build() + { + Validate.notNull(sessionOwner, "TrainingTask requires a user"); + + return new TrainingTask(this); + } + } } diff --git a/inception/inception-recommendation/src/main/resources/META-INF/asciidoc/admin-guide/settings_bulk_processing.adoc b/inception/inception-recommendation/src/main/resources/META-INF/asciidoc/admin-guide/settings_bulk_processing.adoc new file mode 100644 index 00000000000..0591203bd37 --- /dev/null +++ b/inception/inception-recommendation/src/main/resources/META-INF/asciidoc/admin-guide/settings_bulk_processing.adoc @@ -0,0 +1,38 @@ +// 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. + +[[sect_settings_bulk_processing]] += Bulk Processing Settings + +==== +CAUTION: Experimental feature. +==== + +This section describes the global settings related to the bulk processing module. + +.Recommender settings overview +[cols="4*", options="header"] +|=== +| Setting +| Description +| Default +| Example + +| `bulk-processing.enabled` +| enable/disable bulk processing +| `false` +| `true` +|=== \ No newline at end of file diff --git a/inception/inception-recommendation/src/main/ts_template/package-lock.json b/inception/inception-recommendation/src/main/ts_template/package-lock.json index bb9b21de9bc..9bca2941d7b 100644 --- a/inception/inception-recommendation/src/main/ts_template/package-lock.json +++ b/inception/inception-recommendation/src/main/ts_template/package-lock.json @@ -233,9 +233,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -244,10 +244,26 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", - "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -261,9 +277,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", - "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -277,9 +293,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", - "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -293,9 +309,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", - "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -309,9 +325,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", - "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -325,9 +341,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", - "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -341,9 +357,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", - "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -357,9 +373,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", - "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -373,9 +389,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", - "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -389,9 +405,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", - "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -405,9 +421,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", - "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -421,9 +437,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", - "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -437,9 +453,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", - "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -453,9 +469,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", - "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -469,9 +485,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", - "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -485,9 +501,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", - "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -501,9 +517,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", - "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -517,9 +533,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", - "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -533,9 +549,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", - "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -549,9 +565,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", - "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -565,9 +581,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", - "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -581,9 +597,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", - "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -643,29 +659,73 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -680,15 +740,15 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -701,9 +761,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -836,22 +896,22 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.1.tgz", - "integrity": "sha512-5bQDGkXaxD46bPvQt08BUz9YSaO4S0fB1LB5JHQuXTfkGPI3+UUeS387C/e9jRie5GqT8u5kFTrMvAjtX4O5kA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/type-utils": "6.13.1", - "@typescript-eslint/utils": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -877,15 +937,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.1.tgz", - "integrity": "sha512-fs2XOhWCzRhqMmQf0eicLa/CWSaYss2feXsy7xBD/pLyWke/jCIVc2s1ikEAtSW7ina1HNhv7kONoEfVNEcdDQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -905,13 +965,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.1.tgz", - "integrity": "sha512-BW0kJ7ceiKi56GbT2KKzZzN+nDxzQK2DS6x0PiSMPjciPgd/JRQGMibyaN2cPt2cAvuoH0oNvn2fwonHI+4QUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -922,13 +982,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.1.tgz", - "integrity": "sha512-A2qPlgpxx2v//3meMqQyB1qqTg1h1dJvzca7TugM3Yc2USDY+fsRBiojAEo92HO7f5hW5mjAUF6qobOPzlBCBQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.13.1", - "@typescript-eslint/utils": "6.13.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -949,9 +1009,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.1.tgz", - "integrity": "sha512-gjeEskSmiEKKFIbnhDXUyiqVma1gRCQNbVZ1C8q7Zjcxh3WZMbzWVfGE9rHfWd1msQtPS0BVD9Jz9jded44eKg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -962,16 +1022,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.1.tgz", - "integrity": "sha512-sBLQsvOC0Q7LGcUHO5qpG1HxRgePbT6wwqOiGLpR8uOJvPJbfs0mW3jPA3ujsDvfiVwVlWUDESNXv44KtINkUQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/visitor-keys": "6.13.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -989,17 +1050,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.1.tgz", - "integrity": "sha512-ouPn/zVoan92JgAegesTXDB/oUp6BP1v8WpfYcqh649ejNc9Qv+B4FF2Ff626kO1xg0wWwwG48lAJ4JuesgdOw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.13.1", - "@typescript-eslint/types": "6.13.1", - "@typescript-eslint/typescript-estree": "6.13.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -1014,12 +1075,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.1.tgz", - "integrity": "sha512-NDhQUy2tg6XGNBGDRm1XybOHSia8mcXmlbKWoQP+nm1BIIMxa55shyJfZkHpEBN62KNPLrocSM2PdPcaLgDKMQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.13.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1044,9 +1105,9 @@ "dev": true }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1075,9 +1136,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -1173,13 +1234,16 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1213,17 +1277,36 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", - "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", + "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", + "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1269,17 +1352,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -1305,10 +1389,13 @@ "dev": true }, "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -1332,13 +1419,12 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1384,14 +1470,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1419,9 +1510,9 @@ } }, "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -1721,17 +1812,20 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -1840,50 +1934,52 @@ } }, "node_modules/es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz", + "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", + "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", + "typed-array-buffer": "^1.0.1", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -1892,6 +1988,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -1913,14 +2036,14 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -1959,9 +2082,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", - "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1971,28 +2094,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-runner-plugins": { @@ -2048,9 +2172,9 @@ } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz", - "integrity": "sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.1.tgz", + "integrity": "sha512-mBB2aEF0xk7yo+Q9pSUh8xYED/1O2wbAM6IauGkDrqy6pl9SbJNakLeLGXiNpNujWIudu8TJTZCv2L5AQYRXtA==", "dev": true, "dependencies": { "resolve": "^1.22.6", @@ -2077,9 +2201,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -2119,15 +2243,15 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2261,9 +2385,9 @@ } }, "node_modules/eslint-plugin-chai-friendly": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.2.tgz", - "integrity": "sha512-LOIfGx5sZZ5FwM1shr2GlYAWV9Omdi+1/3byuVagvQNoGUuU0iHhp7AfjA1uR+4dJ4Isfb4+FwBJgQajIw9iAg==", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.7.4.tgz", + "integrity": "sha512-PGPjJ8diYgX1mjLxGJqRop2rrGwZRKImoEOwUOgoIhg0p80MkTaqvmFLe5TF7/iagZHggasvIfQlUyHIhK/PYg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2316,9 +2440,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -2337,7 +2461,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -2346,6 +2470,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -2367,6 +2501,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2377,9 +2523,9 @@ } }, "node_modules/eslint-plugin-mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", - "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz", + "integrity": "sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA==", "dev": true, "dependencies": { "eslint-utils": "^3.0.0", @@ -2417,6 +2563,28 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-n/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-n/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-promise": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", @@ -2519,6 +2687,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -2638,9 +2828,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2710,9 +2900,9 @@ } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -2827,28 +3017,33 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" }, "engines": { "node": ">= 0.4" @@ -2858,20 +3053,19 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2889,10 +3083,22 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2982,21 +3188,21 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true, "engines": { "node": ">= 0.4" @@ -3018,12 +3224,12 @@ } }, "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -3033,9 +3239,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -3105,18 +3311,18 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/import-fresh": { @@ -3161,12 +3367,12 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -3191,14 +3397,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3329,9 +3537,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "engines": { "node": ">= 0.4" @@ -3414,12 +3622,15 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3468,12 +3679,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -3759,12 +3970,12 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -3858,15 +4069,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -3894,9 +4108,9 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -3906,13 +4120,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -3927,10 +4140,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/mocha-junit-reporter": { @@ -3949,15 +4158,6 @@ "mocha": ">=2.2.5" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4027,10 +4227,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -4129,15 +4335,16 @@ } }, "node_modules/object.groupby": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", - "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", + "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1" + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" } }, "node_modules/object.values": { @@ -4306,10 +4513,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -4406,9 +4622,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4418,24 +4634,6 @@ "node": ">=4" } }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4546,20 +4744,21 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { "node": ">= 0.4" @@ -4646,6 +4845,48 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4670,13 +4911,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -4708,15 +4949,18 @@ ] }, "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4739,6 +4983,48 @@ "rimraf": "^2.5.2" } }, + "node_modules/sander/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sander/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sander/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/sander/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4793,9 +5079,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4817,29 +5103,32 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4867,14 +5156,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5109,27 +5402,28 @@ } }, "node_modules/svelte-preprocess": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.1.tgz", - "integrity": "sha512-p/Dp4hmrBW5mrCCq29lEMFpIJT2FZsRlouxEc5qpbOmXRbaFs7clLs8oKPwD3xCFyZfv1bIhvOzpQkhMEVQdMw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz", + "integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==", "dev": true, "hasInstallScript": true, "dependencies": { "@types/pug": "^2.0.6", "detect-indent": "^6.1.0", - "magic-string": "^0.27.0", + "magic-string": "^0.30.5", "sorcery": "^0.11.0", "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 14.10.0" + "node": ">= 16.0.0", + "pnpm": "^8.0.0" }, "peerDependencies": { "@babel/core": "^7.10.2", "coffeescript": "^2.5.1", "less": "^3.11.3 || ^4.0.0", "postcss": "^7 || ^8", - "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "pug": "^3.0.0", "sass": "^1.26.8", "stylus": "^0.55.0", @@ -5267,21 +5561,21 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -5330,29 +5624,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5362,16 +5657,17 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5381,23 +5677,29 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", - "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5571,16 +5873,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5619,9 +5921,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionExtractionTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionExtractionTest.java new file mode 100644 index 00000000000..556c6609b09 --- /dev/null +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionExtractionTest.java @@ -0,0 +1,199 @@ +/* + * 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.recommendation.link; + +import static de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService.FEATURE_NAME_IS_PREDICTION; +import static de.tudarmstadt.ukp.inception.support.uima.AnnotationBuilder.buildAnnotation; +import static java.util.Arrays.asList; +import static org.apache.uima.util.CasCreationUtils.mergeTypeSystems; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.apache.uima.cas.CAS; +import org.apache.uima.fit.factory.CasFactory; +import org.apache.uima.fit.factory.TypeSystemDescriptionFactory; +import org.apache.uima.fit.testing.factory.TokenBuilder; +import org.apache.uima.jcas.tcas.Annotation; +import org.apache.uima.resource.ResourceInitializationException; +import org.apache.uima.resource.metadata.impl.TypeSystemDescription_impl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +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.feature.link.LinkFeatureSupport; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerBehaviorRegistry; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerSupportRegistryImpl; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommendationService; +import de.tudarmstadt.ukp.inception.recommendation.api.RecommenderTypeSystemUtils; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; +import de.tudarmstadt.ukp.inception.recommendation.api.recommender.ExtractionContext; +import de.tudarmstadt.ukp.inception.recommendation.config.RecommenderProperties; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.feature.LinkWithRoleModel; +import de.tudarmstadt.ukp.inception.schema.service.FeatureSupportRegistryImpl; +import de.tudarmstadt.ukp.inception.support.uima.SegmentationUtils; + +@ExtendWith(MockitoExtension.class) +class LinkSuggestionExtractionTest +{ + private @Mock RecommendationService recommendationService; + private @Mock LearningRecordService learningRecordService; + private @Mock ApplicationEventPublisher applicationEventPublisher; + private @Mock AnnotationSchemaService schemaService; + private @Mock RecommenderProperties recommenderProperties; + private @Mock LayerBehaviorRegistry layerBehaviorRegistry; + + private TokenBuilder tokenBuilder; + private Project project; + private SourceDocument document; + private CAS originalCas; + + private LayerSupportRegistryImpl layerSupportRegistry; + private FeatureSupportRegistryImpl featureSupportRegistry; + + private LinkSuggestionSupport sut; + private LinkFeatureSupport linkFeatureSupport; + + private AnnotationLayer linkHostLayer; + private AnnotationLayer slotFillerLayer; + private AnnotationFeature linkFeature; + + private Recommender recommender; + + @BeforeEach + void setup() throws Exception + { + linkFeatureSupport = new LinkFeatureSupport(schemaService); + featureSupportRegistry = new FeatureSupportRegistryImpl(asList(linkFeatureSupport)); + featureSupportRegistry.init(); + layerSupportRegistry = new LayerSupportRegistryImpl( + asList(new SpanLayerSupport(featureSupportRegistry, null, layerBehaviorRegistry))); + layerSupportRegistry.init(); + + tokenBuilder = new TokenBuilder<>(Token.class, Sentence.class); + + project = Project.builder() // + .withId(1l) // + .withName("Test") // + .build(); + document = SourceDocument.builder() // + .withId(1l) // + .withProject(project) // + .withName("Doc") // + .build(); + linkHostLayer = AnnotationLayer.builder() // + .withId(1l) // + .withName("custom.Span") // + .withType(SpanLayerSupport.TYPE) // + .build(); + slotFillerLayer = AnnotationLayer.builder() // + .withId(1l) // + .withName("custom.Arg") // + .withType(SpanLayerSupport.TYPE) // + .build(); + linkFeature = AnnotationFeature.builder() // + .withLayer(linkHostLayer) // + .withType(slotFillerLayer.getName()) // + .withName("args") // + .build(); + linkFeatureSupport.configureFeature(linkFeature); + + recommender = Recommender.builder() // + .withId(1l) // + .withName("recommender") // + .withProject(project) // + .withLayer(linkHostLayer) // + .withFeature(linkFeature) // + .build(); + + originalCas = createCas(asList(linkHostLayer, slotFillerLayer), asList(linkFeature)); + originalCas.setDocumentText("This is a test."); + + SegmentationUtils.splitSentences(originalCas); + SegmentationUtils.tokenize(originalCas); + + sut = new LinkSuggestionSupport(recommendationService, learningRecordService, + applicationEventPublisher, schemaService, featureSupportRegistry); + + var linkHostLayerAdapter = layerSupportRegistry.getLayerSupport(linkHostLayer) + .createAdapter(linkHostLayer, () -> asList(linkFeature)); + when(schemaService.getAdapter(linkHostLayer)).thenReturn(linkHostLayerAdapter); + } + + @Test + void testLinkExtraction() throws Exception + { + var slotFiller = buildAnnotation(originalCas, slotFillerLayer.getName()) // + .onMatch("\\btest\\b") // + .buildAndAddToIndexes(); + + var linkHost = buildAnnotation(originalCas, linkHostLayer.getName()) // + .onMatch("\\bis\\b") // + .buildAndAddToIndexes(); + + var predictionCas = RecommenderTypeSystemUtils.makePredictionCas(originalCas, linkFeature); + + var preSlotFiller = predictionCas. select(slotFillerLayer.getName()).get(); + var prediction = buildAnnotation(predictionCas, linkHostLayer.getName()) // + .onMatch("\\bis\\b") // + .withFeature(FEATURE_NAME_IS_PREDICTION, true) // + .buildAndAddToIndexes(); + linkFeatureSupport.setFeatureValue(predictionCas, linkFeature, prediction.getAddress(), + asList(new LinkWithRoleModel("role", "label", preSlotFiller.getAddress()))); + + var ctx = new ExtractionContext(0, recommender, document, originalCas, predictionCas); + var suggestions = sut.extractSuggestions(ctx); + + assertThat(suggestions) // + .filteredOn(a -> a instanceof LinkSuggestion) // + .map(a -> (LinkSuggestion) a) // + .extracting( // + LinkSuggestion::getRecommenderName, // + LinkSuggestion::getLabel) // + .containsExactly( // + tuple(recommender.getName(), "role")); + } + + private CAS createCas(List aLayers, List aFeatures) + throws ResourceInitializationException + { + var globalTypes = TypeSystemDescriptionFactory.createTypeSystemDescription(); + var localTypes = new TypeSystemDescription_impl(); + + for (var layer : aLayers) { + layerSupportRegistry.getLayerSupport(layer).generateTypes(localTypes, layer, aFeatures); + } + + return CasFactory.createCas(mergeTypeSystems(asList(globalTypes, localTypes))); + } +} diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionVisibilityCalculationTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionVisibilityCalculationTest.java new file mode 100644 index 00000000000..863745ff8aa --- /dev/null +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/link/LinkSuggestionVisibilityCalculationTest.java @@ -0,0 +1,259 @@ +/* + * 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.recommendation.link; + +import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.getInvisibleSuggestions; +import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.getVisibleSuggestions; +import static de.tudarmstadt.ukp.inception.support.uima.AnnotationBuilder.buildAnnotation; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.apache.uima.util.CasCreationUtils.mergeTypeSystems; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.uima.cas.CAS; +import org.apache.uima.fit.factory.CasFactory; +import org.apache.uima.fit.factory.TypeSystemDescriptionFactory; +import org.apache.uima.resource.metadata.impl.TypeSystemDescription_impl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.annotation.feature.link.LinkFeatureSupport; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerBehaviorRegistry; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerSupportRegistryImpl; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkPosition; +import de.tudarmstadt.ukp.inception.recommendation.api.model.LinkSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; +import de.tudarmstadt.ukp.inception.schema.api.feature.LinkWithRoleModel; +import de.tudarmstadt.ukp.inception.schema.service.FeatureSupportRegistryImpl; +import de.tudarmstadt.ukp.inception.support.uima.SegmentationUtils; + +@ExtendWith(MockitoExtension.class) +public class LinkSuggestionVisibilityCalculationTest +{ + private final static String TEST_USER = "Testuser"; + private final static long RECOMMENDER_ID = 1; + private final static String RECOMMENDER_NAME = "TestEntityRecommender"; + private final static double CONFIDENCE = 0.2; + private final static String CONFIDENCE_EXPLANATION = "Predictor A: 0.05 | Predictor B: 0.15"; + + private @Mock LearningRecordService learningRecordService; + private @Mock AnnotationSchemaService schemaService; + private @Mock LayerBehaviorRegistry layerBehaviorRegistry; + + private LinkSuggestionSupport sut; + + private LayerSupportRegistryImpl layerSupportRegistry; + private FeatureSupportRegistryImpl featureSupportRegistry; + + private LinkFeatureSupport linkFeatureSupport; + + private Project project; + private SourceDocument document; + private AnnotationLayer linkHostLayer; + private AnnotationLayer slotFillerLayer; + private AnnotationFeature linkFeature; + + private CAS cas; + private Recommender recommender; + private TypeAdapter linkHostLayerAdapter; + + @BeforeEach + public void setUp() throws Exception + { + linkFeatureSupport = new LinkFeatureSupport(schemaService); + featureSupportRegistry = new FeatureSupportRegistryImpl(asList(linkFeatureSupport)); + featureSupportRegistry.init(); + layerSupportRegistry = new LayerSupportRegistryImpl( + asList(new SpanLayerSupport(featureSupportRegistry, null, layerBehaviorRegistry))); + layerSupportRegistry.init(); + + project = Project.builder() // + .withId(1l) // + .withName("Test") // + .build(); + document = SourceDocument.builder() // + .withId(1l) // + .withProject(project) // + .withName("Doc") // + .build(); + linkHostLayer = AnnotationLayer.builder() // + .withId(1l) // + .withName("custom.Span") // + .withType(SpanLayerSupport.TYPE) // + .build(); + slotFillerLayer = AnnotationLayer.builder() // + .withId(1l) // + .withName("custom.Arg") // + .withType(SpanLayerSupport.TYPE) // + .build(); + linkFeature = AnnotationFeature.builder() // + .withLayer(linkHostLayer) // + .withType(slotFillerLayer.getName()) // + .withName("args") // + .build(); + linkFeatureSupport.configureFeature(linkFeature); + recommender = Recommender.builder() // + .withId(RECOMMENDER_ID) // + .withName(RECOMMENDER_NAME) // + .withLayer(linkFeature.getLayer()) // + .withFeature(linkFeature) // + .build(); + + cas = createCas(asList(linkHostLayer, slotFillerLayer), asList(linkFeature)); + cas.setDocumentText("This is a test."); + + SegmentationUtils.splitSentences(cas); + SegmentationUtils.tokenize(cas); + + when(schemaService.listSupportedFeatures(linkHostLayer)).thenReturn(asList(linkFeature)); + + sut = new LinkSuggestionSupport(null, learningRecordService, null, schemaService, null); + + linkHostLayerAdapter = layerSupportRegistry.getLayerSupport(linkHostLayer) + .createAdapter(linkHostLayer, () -> asList(linkFeature)); + when(schemaService.getAdapter(linkHostLayer)).thenReturn(linkHostLayerAdapter); + } + + @Test + public void testCalculateVisibilityNoRecordsAllHidden() throws Exception + { + doReturn(emptyList()).when(learningRecordService).listLearningRecords(TEST_USER, TEST_USER, + linkHostLayer); + + var slotFiller = buildAnnotation(cas, slotFillerLayer.getName()) // + .onMatch("\\btest\\b") // + .buildAndAddToIndexes(); + + var linkHost = buildAnnotation(cas, linkHostLayer.getName()) // + .onMatch("\\bis\\b") // + .buildAndAddToIndexes(); + linkHostLayerAdapter.setFeatureValue(document, TEST_USER, linkHost, linkFeature, + asList(new LinkWithRoleModel("role", slotFiller))); + + var suggestions = makeLinkSuggestionGroup(document, linkFeature, + new int[][] { { 1, linkHost.getBegin(), linkHost.getEnd(), slotFiller.getBegin(), + slotFiller.getEnd() } }); + + sut.calculateSuggestionVisibility(TEST_USER, document, cas, TEST_USER, linkHostLayer, + suggestions, 0, cas.getDocumentText().length()); + + assertThat(getVisibleSuggestions(suggestions)) // + .as("No suggestions are visible as they overlap with annotations") // + .isEmpty(); + + assertThat(getInvisibleSuggestions(suggestions)) // + .as("Invisible suggestions are hidden because of overlapping") // + .extracting(AnnotationSuggestion::getReasonForHiding) // + .extracting(String::trim) // + .containsExactly("overlapping"); + } + + @Test + public void thatVisibilityIsRestoredWhenOverlappingAnnotationIsRemoved() throws Exception + { + doReturn(emptyList()).when(learningRecordService).listLearningRecords(TEST_USER, TEST_USER, + linkHostLayer); + + var slotFiller = buildAnnotation(cas, slotFillerLayer.getName()) // + .onMatch("\\btest\\b") // + .buildAndAddToIndexes(); + + var linkHost = buildAnnotation(cas, linkHostLayer.getName()) // + .onMatch("\\bis\\b") // + .buildAndAddToIndexes(); + linkHostLayerAdapter.setFeatureValue(document, TEST_USER, linkHost, linkFeature, + asList(new LinkWithRoleModel("role", slotFiller))); + + var suggestions = makeLinkSuggestionGroup(document, linkFeature, + new int[][] { { 1, linkHost.getBegin(), linkHost.getEnd(), slotFiller.getBegin(), + slotFiller.getEnd() } }); + + sut.calculateSuggestionVisibility(TEST_USER, document, cas, TEST_USER, linkHostLayer, + suggestions, 0, 25); + + assertThat(getVisibleSuggestions(suggestions)) // + .as("No suggestions are visible as they overlap with annotations") // + .isEmpty(); + assertThat(getInvisibleSuggestions(suggestions)) // + .as("All suggestions are hidden as the overlap with annotations") // + .isNotEmpty(); + + linkHostLayerAdapter.setFeatureValue(document, TEST_USER, linkHost, linkFeature, asList()); + + sut.calculateSuggestionVisibility(TEST_USER, document, cas, TEST_USER, linkHostLayer, + suggestions, 0, 25); + + assertThat(getInvisibleSuggestions(suggestions)) // + .as("No suggestions are hidden as they no longer overlap with annotations") // + .containsExactly(); + assertThat(getVisibleSuggestions(suggestions)) // + .as("All suggestions are visible as they no longer overlap with annotations") // + .containsExactlyInAnyOrderElementsOf( + suggestions.stream().flatMap(g -> g.stream()).toList()); + } + + private CAS createCas(List aLayers, List aFeatures) + throws Exception + { + var globalTypes = TypeSystemDescriptionFactory.createTypeSystemDescription(); + var localTypes = new TypeSystemDescription_impl(); + + for (var layer : aLayers) { + layerSupportRegistry.getLayerSupport(layer).generateTypes(localTypes, layer, aFeatures); + } + + return CasFactory.createCas(mergeTypeSystems(asList(globalTypes, localTypes))); + } + + SuggestionDocumentGroup makeLinkSuggestionGroup(SourceDocument doc, + AnnotationFeature aFeat, int[][] vals) + { + var suggestions = new ArrayList(); + for (int[] val : vals) { + var suggestion = LinkSuggestion.builder() // + .withId(val[0]) // + .withRecommender(recommender) // + .withDocument(doc) // + .withPosition(new LinkPosition(aFeat.getName(), val[1], val[2], val[3], val[4])) // + .withScore(CONFIDENCE) // + .withScoreExplanation(CONFIDENCE_EXPLANATION) // + .build(); + suggestions.add(suggestion); + } + + return SuggestionDocumentGroup.groupsOfType(LinkSuggestion.class, suggestions); + } +} diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionVisibilityCalculationTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionVisibilityCalculationTest.java index 35e431c88b4..605a252e4d5 100644 --- a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionVisibilityCalculationTest.java +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/relation/RelationSuggestionVisibilityCalculationTest.java @@ -19,7 +19,6 @@ import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.getInvisibleSuggestions; import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.getVisibleSuggestions; -import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.makeRelationSuggestionGroup; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; @@ -28,6 +27,9 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.List; + import org.apache.uima.cas.CAS; import org.apache.uima.fit.factory.JCasFactory; import org.junit.jupiter.api.BeforeEach; @@ -42,17 +44,34 @@ import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.dependency.Dependency; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerBehaviorRegistry; +import de.tudarmstadt.ukp.inception.annotation.layer.behaviors.LayerSupportRegistryImpl; +import de.tudarmstadt.ukp.inception.annotation.layer.relation.RelationLayerSupport; +import de.tudarmstadt.ukp.inception.annotation.layer.span.SpanLayerSupport; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; +import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationPosition; +import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.api.adapter.TypeAdapter; +import de.tudarmstadt.ukp.inception.schema.api.feature.FeatureSupportRegistry; @ExtendWith(MockitoExtension.class) public class RelationSuggestionVisibilityCalculationTest { private static final String TEST_USER = "Testuser"; - private @Mock AnnotationSchemaService annoService; + private final static long RECOMMENDER_ID = 1; + private final static String RECOMMENDER_NAME = "TestEntityRecommender"; + private final static double CONFIDENCE = 0.2; + private final static String CONFIDENCE_EXPLANATION = "Predictor A: 0.05 | Predictor B: 0.15"; + + private @Mock AnnotationSchemaService schemaService; private @Mock LearningRecordService learningRecordService; + private @Mock LayerBehaviorRegistry layerBehaviorRegistry; + private @Mock FeatureSupportRegistry featureSupportRegistry; private Project project; private SourceDocument doc; @@ -61,10 +80,20 @@ public class RelationSuggestionVisibilityCalculationTest private RelationSuggestionSupport sut; + private LayerSupportRegistryImpl layerSupportRegistry; + + private TypeAdapter relationAdapter; + @BeforeEach public void setUp() throws Exception { - layer = AnnotationLayer.builder().withId(42l).forJCasClass(Dependency.class).build(); + layerSupportRegistry = new LayerSupportRegistryImpl(asList( + new SpanLayerSupport(featureSupportRegistry, null, layerBehaviorRegistry), + new RelationLayerSupport(featureSupportRegistry, null, layerBehaviorRegistry))); + layerSupportRegistry.init(); + + layer = AnnotationLayer.builder().withId(42l).forJCasClass(Dependency.class) + .withType(RelationLayerSupport.TYPE).build(); feature = AnnotationFeature.builder().withId(2l).withLayer(layer) .withName(Dependency._FeatName_DependencyType).withType(TYPE_NAME_STRING).build(); @@ -73,9 +102,13 @@ public void setUp() throws Exception doc = SourceDocument.builder().withId(12l).withName("doc").withProject(project).build(); - when(annoService.listSupportedFeatures(layer)).thenReturn(asList(feature)); + when(schemaService.listSupportedFeatures(layer)).thenReturn(asList(feature)); - sut = new RelationSuggestionSupport(null, learningRecordService, null, annoService, null); + sut = new RelationSuggestionSupport(null, learningRecordService, null, schemaService, null); + + relationAdapter = layerSupportRegistry.getLayerSupport(layer).createAdapter(layer, + () -> asList()); + when(schemaService.getAdapter(layer)).thenReturn(relationAdapter); } @Test @@ -154,4 +187,22 @@ private CAS getTestCas() throws Exception return jcas.getCas(); } + + static SuggestionDocumentGroup makeRelationSuggestionGroup( + SourceDocument doc, AnnotationFeature aFeat, int[][] vals) + { + var rec = Recommender.builder().withId(RECOMMENDER_ID).withName(RECOMMENDER_NAME) + .withLayer(aFeat.getLayer()).withFeature(aFeat).build(); + + List suggestions = new ArrayList<>(); + for (int[] val : vals) { + var suggestion = RelationSuggestion.builder().withId(val[0]).withRecommender(rec) + .withDocument(doc) + .withPosition(new RelationPosition(val[1], val[2], val[3], val[4])) + .withScore(CONFIDENCE).withScoreExplanation(CONFIDENCE_EXPLANATION).build(); + suggestions.add(suggestion); + } + + return SuggestionDocumentGroup.groupsOfType(RelationSuggestion.class, suggestions); + } } diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/service/Fixtures.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/service/Fixtures.java index 4bc0266f1a4..bdf79b2e947 100644 --- a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/service/Fixtures.java +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/service/Fixtures.java @@ -25,8 +25,6 @@ import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; -import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationPosition; -import de.tudarmstadt.ukp.inception.recommendation.api.model.RelationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionGroup; @@ -74,22 +72,4 @@ public static SuggestionDocumentGroup makeSpanSuggestionGroup( return SuggestionDocumentGroup.groupsOfType(SpanSuggestion.class, suggestions); } - - public static SuggestionDocumentGroup makeRelationSuggestionGroup( - SourceDocument doc, AnnotationFeature aFeat, int[][] vals) - { - var rec = Recommender.builder().withId(RECOMMENDER_ID).withName(RECOMMENDER_NAME) - .withLayer(aFeat.getLayer()).withFeature(aFeat).build(); - - List suggestions = new ArrayList<>(); - for (int[] val : vals) { - var suggestion = RelationSuggestion.builder().withId(val[0]).withRecommender(rec) - .withDocument(doc) - .withPosition(new RelationPosition(val[1], val[2], val[3], val[4])) - .withScore(CONFIDENCE).withScoreExplanation(CONFIDENCE_EXPLANATION).build(); - suggestions.add(suggestion); - } - - return SuggestionDocumentGroup.groupsOfType(RelationSuggestion.class, suggestions); - } } diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionMultiValueFeatureSuggestionVisibilityCalculationTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionMultiValueFeatureSuggestionVisibilityCalculationTest.java new file mode 100644 index 00000000000..bb316bc833a --- /dev/null +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionMultiValueFeatureSuggestionVisibilityCalculationTest.java @@ -0,0 +1,218 @@ +/* + * 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.recommendation.span; + +import static de.tudarmstadt.ukp.inception.recommendation.service.Fixtures.getInvisibleSuggestions; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import org.apache.uima.cas.CAS; +import org.apache.uima.fit.factory.CasFactory; +import org.apache.uima.fit.util.FSUtil; +import org.apache.uima.resource.metadata.FeatureDescription; +import org.apache.uima.resource.metadata.TypeDescription; +import org.apache.uima.resource.metadata.TypeSystemDescription; +import org.apache.uima.resource.metadata.impl.TypeSystemDescription_impl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; +import de.tudarmstadt.ukp.clarin.webanno.model.MultiValueMode; +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; +import de.tudarmstadt.ukp.inception.annotation.feature.multistring.MultiValueStringFeatureSupport; +import de.tudarmstadt.ukp.inception.annotation.feature.string.StringFeatureSupport; +import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; +import de.tudarmstadt.ukp.inception.recommendation.api.model.Recommender; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion; +import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; +import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.service.FeatureSupportRegistryImpl; + +@ExtendWith(MockitoExtension.class) +public class SpanSuggestionMultiValueFeatureSuggestionVisibilityCalculationTest +{ + private static final String TEST_USER = "Testuser"; + + private @Mock AnnotationSchemaService annoService; + private @Mock LearningRecordService learningRecordService; + + private Project project; + private SourceDocument doc; + + private TypeSystemDescription tsd; + private TypeDescription typeDesc; + private FeatureDescription featureDesc; + private AnnotationLayer layer; + private AnnotationFeature feature; + private Recommender rec; + private CAS cas; + private SpanSuggestion.Builder suggestionTemplate; + + private SpanSuggestionSupport sut; + + @BeforeEach + public void setUp() throws Exception + { + project = Project.builder().withName("Test Project").build(); + + doc = SourceDocument.builder().withId(12l).withName("doc").withProject(project).build(); + + var featureSupportRegistry = new FeatureSupportRegistryImpl( + asList(new StringFeatureSupport(), new MultiValueStringFeatureSupport())); + featureSupportRegistry.init(); + + sut = new SpanSuggestionSupport(null, learningRecordService, null, annoService, + featureSupportRegistry, null); + + tsd = new TypeSystemDescription_impl(); + typeDesc = tsd.addType("Span", null, CAS.TYPE_NAME_ANNOTATION); + featureDesc = typeDesc.addFeature("values", null, CAS.TYPE_NAME_STRING_ARRAY, + CAS.TYPE_NAME_STRING, false); + layer = AnnotationLayer.builder().withId(44l).withName(typeDesc.getName()).build(); + feature = AnnotationFeature.builder().withId(4l) // + .withLayer(layer) // + .withName(featureDesc.getName()) // + .withMultiValueMode(MultiValueMode.ARRAY) // + .withType(CAS.TYPE_NAME_STRING_ARRAY) // + .build(); + + rec = Recommender.builder().withId(123l).withName("rec").withLayer(layer) + .withFeature(feature).build(); + + cas = CasFactory.createCas(tsd); + + doReturn(emptyList()).when(learningRecordService).listLearningRecords(TEST_USER, TEST_USER, + layer); + when(annoService.listSupportedFeatures(layer)).thenReturn(asList(feature)); + + suggestionTemplate = SpanSuggestion.builder() // + .withDocument(doc) // + .withRecommender(rec) // + .withId(1) // + .withPosition(0, 1); + } + + @Test + public void collocatedWithAnnotationWithoutLabelAreNotHidden() throws Exception + { + var suggestion = suggestionTemplate.withLabel("blah").build(); + + // Add annotation without label at same location as suggestion + var ann = cas.createAnnotation(cas.getTypeSystem().getType(typeDesc.getName()), + suggestion.getBegin(), suggestion.getEnd()); + cas.addFsToIndexes(ann); + + var documentSuggestions = SuggestionDocumentGroup.groupsOfType(SpanSuggestion.class, + asList(suggestion)); + sut.calculateSuggestionVisibility(TEST_USER, doc, cas, TEST_USER, layer, + documentSuggestions, 0, 2); + + assertThat(getInvisibleSuggestions(documentSuggestions)) // + .as("Hidden suggestions") // + .isEmpty(); + } + + @Test + public void collocatedWithAnnotationWithSameLabelAreHidden() throws Exception + { + var suggestion = suggestionTemplate.withLabel("blah").build(); + + // Add annotation with label at same location as suggestion + var ann = cas.createAnnotation(cas.getTypeSystem().getType(typeDesc.getName()), + suggestion.getBegin(), suggestion.getEnd()); + FSUtil.setFeature(ann, "values", asList(suggestion.getLabel())); + cas.addFsToIndexes(ann); + + var documentSuggestions = calculateVisibility(suggestion); + + assertThat(getInvisibleSuggestions(documentSuggestions)) // + .as("Hidden suggestions") // + .containsExactly(suggestion); + } + + @Test + public void collocatedWithAnnotationWithDifferentLabelAreNotHidden() throws Exception + { + var suggestion = suggestionTemplate.withLabel("blah").build(); + + // Add annotation with alternative label at same location as suggestion + var ann = cas.createAnnotation(cas.getTypeSystem().getType(typeDesc.getName()), + suggestion.getBegin(), suggestion.getEnd()); + FSUtil.setFeature(ann, "values", asList(suggestion.getLabel() + "_other")); + cas.addFsToIndexes(ann); + + var documentSuggestions = calculateVisibility(suggestion); + + assertThat(getInvisibleSuggestions(documentSuggestions)) // + .as("Hidden suggestions") // + .isEmpty(); + } + + @Test + public void withoutLabelCollocatedWithAnnotationWithLabelAreHidden() throws Exception + { + var suggestion = suggestionTemplate.withLabel(null).build(); + + // Add annotation with alternative label at same location as suggestion + var ann = cas.createAnnotation(cas.getTypeSystem().getType(typeDesc.getName()), + suggestion.getBegin(), suggestion.getEnd()); + FSUtil.setFeature(ann, "values", asList("blah")); + cas.addFsToIndexes(ann); + + var documentSuggestions = calculateVisibility(suggestion); + + assertThat(getInvisibleSuggestions(documentSuggestions)) // + .as("Hidden suggestions") // + .containsExactly(suggestion); + } + + @Test + public void withoutLabelOverlappingWithAnnotationWithLabelAreNotHidden() throws Exception + { + var suggestion = suggestionTemplate.withLabel(null).build(); + + // Add annotation with alternative label at overlapping location as suggestion + var ann = cas.createAnnotation(cas.getTypeSystem().getType(typeDesc.getName()), + suggestion.getBegin(), suggestion.getEnd() - 1); + FSUtil.setFeature(ann, "values", asList("blah")); + cas.addFsToIndexes(ann); + + var documentSuggestions = calculateVisibility(suggestion); + + assertThat(getInvisibleSuggestions(documentSuggestions)) // + .as("Hidden suggestions") // + .isEmpty(); + } + + private SuggestionDocumentGroup calculateVisibility(SpanSuggestion suggestion) + { + var documentSuggestions = SuggestionDocumentGroup.groupsOfType(SpanSuggestion.class, + asList(suggestion)); + sut.calculateSuggestionVisibility(TEST_USER, doc, cas, TEST_USER, layer, + documentSuggestions, 0, 2); + return documentSuggestions; + } +} diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionVisibilityCalculationTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionVisibilityCalculationTest.java index a608d91ee24..8b0f84197c5 100644 --- a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionVisibilityCalculationTest.java +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/span/SpanSuggestionVisibilityCalculationTest.java @@ -32,7 +32,6 @@ import static org.mockito.Mockito.lenient; import java.util.ArrayList; -import java.util.List; import org.apache.uima.cas.CAS; import org.apache.uima.fit.factory.JCasFactory; @@ -48,6 +47,8 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.dkpro.core.api.ner.type.NamedEntity; +import de.tudarmstadt.ukp.inception.annotation.feature.multistring.MultiValueStringFeatureSupport; +import de.tudarmstadt.ukp.inception.annotation.feature.string.StringFeatureSupport; import de.tudarmstadt.ukp.inception.recommendation.api.LearningRecordService; import de.tudarmstadt.ukp.inception.recommendation.api.model.AnnotationSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.LearningRecord; @@ -56,6 +57,7 @@ import de.tudarmstadt.ukp.inception.recommendation.api.model.SpanSuggestion; import de.tudarmstadt.ukp.inception.recommendation.api.model.SuggestionDocumentGroup; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.schema.service.FeatureSupportRegistryImpl; @ExtendWith(MockitoExtension.class) public class SpanSuggestionVisibilityCalculationTest @@ -94,7 +96,12 @@ public void setUp() throws Exception lenient().when(annoService.listSupportedFeatures(layer)).thenReturn(asList(feature)); lenient().when(annoService.listSupportedFeatures(layer2)).thenReturn(asList(feature2)); - sut = new SpanSuggestionSupport(null, learningRecordService, null, annoService, null, null); + var featureSupportRegistry = new FeatureSupportRegistryImpl( + asList(new StringFeatureSupport(), new MultiValueStringFeatureSupport())); + featureSupportRegistry.init(); + + sut = new SpanSuggestionSupport(null, learningRecordService, null, annoService, + featureSupportRegistry, null); } @Test @@ -147,8 +154,8 @@ public void testCalculateVisibilityNoRecordsNotHidden() throws Exception @Test public void testCalculateVisibilityRejected() throws Exception { - List records = new ArrayList<>(); - LearningRecord rejectedRecord = new LearningRecord(); + var records = new ArrayList(); + var rejectedRecord = new LearningRecord(); rejectedRecord.setSourceDocument(doc); rejectedRecord.setUserAction(LearningRecordUserAction.REJECTED); rejectedRecord.setLayer(layer); @@ -221,18 +228,16 @@ public void thatOverlappingSuggestionsAreNotHiddenWhenStackingIsEnabled() throws var cas = JCasFactory.createText("a b", "de"); - var suggestion1 = SpanSuggestion.builder() // - .withId(1) // + var suggestionTemplate = SpanSuggestion.builder() // .withDocument(doc) // .withRecommender(rec) // - .withLabel("blah") // + .withLabel("blah"); + var suggestion1 = suggestionTemplate // + .withId(1) // .withPosition(0, 1) // .build(); - var suggestion2 = SpanSuggestion.builder() // + var suggestion2 = suggestionTemplate // .withId(2) // - .withDocument(doc) // - .withRecommender(rec) // - .withLabel("blah") // .withPosition(1, 2) // .build(); var suggestions = SuggestionDocumentGroup.groupsOfType(SpanSuggestion.class, @@ -272,7 +277,7 @@ public void thatOverlappingSuggestionsAreNotHiddenWhenStackingIsEnabled() throws suggestions, 0, 2); assertThat(getInvisibleSuggestions(suggestions)) // - .as("Second suggestion is noew also no longer visible because annotation with the same label exists") // + .as("Second suggestion is now also no longer visible because annotation with the same label exists") // .containsExactly(suggestion1, suggestion2); } @@ -280,8 +285,8 @@ public void thatOverlappingSuggestionsAreNotHiddenWhenStackingIsEnabled() throws void thatRejectedSuggestionIsHidden() { var rec1 = Recommender.builder().withId(1l).withLayer(layer).withFeature(feature).build(); - var rec2 = Recommender.builder().withId(1l).withLayer(layer2).withFeature(feature).build(); - var rec3 = Recommender.builder().withId(1l).withLayer(layer).withFeature(feature2).build(); + var rec2 = Recommender.builder().withId(2l).withLayer(layer2).withFeature(feature).build(); + var rec3 = Recommender.builder().withId(3l).withLayer(layer).withFeature(feature2).build(); var label = "x"; var records = asList(LearningRecord.builder() // @@ -332,8 +337,8 @@ void thatRejectedSuggestionIsHidden() void thatSkippedSuggestionIsHidden() { var rec1 = Recommender.builder().withId(1l).withLayer(layer).withFeature(feature).build(); - var rec2 = Recommender.builder().withId(1l).withLayer(layer2).withFeature(feature).build(); - var rec3 = Recommender.builder().withId(1l).withLayer(layer).withFeature(feature2).build(); + var rec2 = Recommender.builder().withId(2l).withLayer(layer2).withFeature(feature).build(); + var rec3 = Recommender.builder().withId(3l).withLayer(layer).withFeature(feature2).build(); var label = "x"; var records = asList(LearningRecord.builder() // @@ -381,9 +386,8 @@ void thatSkippedSuggestionIsHidden() private CAS getTestCas() throws Exception { - var documentText = "Dies ist ein Testtext, ach ist der schoen, der schoenste von allen" - + " Testtexten."; - var jcas = JCasFactory.createText(documentText, "de"); + var text = "Dies ist ein Testtext, ach ist der schoen, der schoenste von allen Testtexten."; + var jcas = JCasFactory.createText(text, "de"); var neLabel = new NamedEntity(jcas, 0, 3); neLabel.setValue("LOC"); diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTaskTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTaskTest.java index 701e0bacc0c..bf1e1518e09 100644 --- a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTaskTest.java +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/tasks/PredictionTaskTest.java @@ -88,7 +88,12 @@ public void monkeyPatchTypeSystem_WithNer_CreatesScoreFeatures() throws Exceptio { var schemaService = Mockito.mock(AnnotationSchemaServiceImpl.class); - var sut = new PredictionTask(sessionOwner, TRIGGER, document, DATA_OWNER); + var sut = PredictionTask.builder() // + .withSessionOwner(sessionOwner) // + .withTrigger(TRIGGER) // + .withCurrentDocument(document) // + .withDataOwner(DATA_OWNER) // + .build(); sut.setSchemaService(schemaService); var jCas = createText("I am text CAS", "de"); diff --git a/inception/inception-recommendation/src/test/resources/log4j2-test.xml b/inception/inception-recommendation/src/test/resources/log4j2-test.xml index cfac64c1a0c..b278a724bc3 100644 --- a/inception/inception-recommendation/src/test/resources/log4j2-test.xml +++ b/inception/inception-recommendation/src/test/resources/log4j2-test.xml @@ -7,7 +7,7 @@ - + diff --git a/inception/inception-remote/pom.xml b/inception/inception-remote/pom.xml index 6198ae4ea14..32424602943 100644 --- a/inception/inception-remote/pom.xml +++ b/inception/inception-remote/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-remote INCEpTION - Core - Remote API diff --git a/inception/inception-review-editor/pom.xml b/inception/inception-review-editor/pom.xml index e5850d381a6..d33cccb74fc 100644 --- a/inception/inception-review-editor/pom.xml +++ b/inception/inception-review-editor/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT INCEpTION - Editor - Review (deprecated) inception-review-editor diff --git a/inception/inception-scheduling/pom.xml b/inception/inception-scheduling/pom.xml index 4e4efff0ec8..2439fe835cb 100644 --- a/inception/inception-scheduling/pom.xml +++ b/inception/inception-scheduling/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-scheduling INCEpTION - Scheduling diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/DebouncingTask.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/DebouncingTask.java index 40feff0ad03..13175f488f5 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/DebouncingTask.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/DebouncingTask.java @@ -19,29 +19,16 @@ import java.time.Duration; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; - public abstract class DebouncingTask extends Task { private final long runnableAfter; - public DebouncingTask(Project aProject, String aTrigger, Duration aDebounceDelay) - { - this(null, aProject, aTrigger, aDebounceDelay.toMillis()); - } - - public DebouncingTask(Project aProject, String aTrigger, long aDebounceMillis) - { - this(null, aProject, aTrigger, aDebounceMillis); - } - - public DebouncingTask(User aUser, Project aProject, String aTrigger, long aDebouncePeriod) + protected DebouncingTask(Builder> aBuilder) { - super(aUser, aProject, aTrigger); + super(aBuilder); - runnableAfter = System.currentTimeMillis() + aDebouncePeriod; + runnableAfter = System.currentTimeMillis() + aBuilder.debounceMillis; } @Override @@ -49,4 +36,22 @@ public boolean isReadyToStart() { return System.currentTimeMillis() > runnableAfter; } + + public static abstract class Builder> + extends Task.Builder + { + private long debounceMillis; + + public T withDebounceMillis(Duration aDebounceDelay) + { + debounceMillis = aDebounceDelay.toMillis(); + return (T) this; + } + + public T withDebounceMillis(long aDebounceMillis) + { + debounceMillis = aDebounceMillis; + return (T) this; + } + } } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/NotifyingTaskMonitor.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/NotifyingTaskMonitor.java index e8101054845..93adf3abf34 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/NotifyingTaskMonitor.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/NotifyingTaskMonitor.java @@ -32,10 +32,9 @@ public class NotifyingTaskMonitor private MTaskStateUpdate lastUpdate; - public NotifyingTaskMonitor(TaskHandle aHandle, String aUser, String aTitle, - SimpMessagingTemplate aMsgTemplate) + public NotifyingTaskMonitor(TaskHandle aHandle, Task aTask, SimpMessagingTemplate aMsgTemplate) { - super(aHandle, aUser, aTitle); + super(aHandle, aTask); msgTemplate = aMsgTemplate; } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java index 9a6eb7243c5..57a19c9a409 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingService.java @@ -18,6 +18,7 @@ package de.tudarmstadt.ukp.inception.scheduling; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; import de.tudarmstadt.ukp.clarin.webanno.model.Project; @@ -39,6 +40,11 @@ public interface SchedulingService */ List getRunningTasks(); + /** + * @return tasks which are no longer running (completed, failed). + */ + List getTasksPendingAcknowledgment(); + List getScheduledAndRunningTasks(); List getAllTasks(); @@ -59,6 +65,8 @@ public interface SchedulingService */ void enqueue(Task aTask); + Optional findTask(Predicate aPredicate); + /** * Removes all task for the user with name {@code aUsername} from the scheduler's queue. * diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceImpl.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceImpl.java index 4b403b76d69..a0cec1b937f 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceImpl.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceImpl.java @@ -26,9 +26,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -40,13 +42,11 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.event.EventListener; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.core.session.SessionDestroyedEvent; -import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; import de.tudarmstadt.ukp.clarin.webanno.model.Project; @@ -74,6 +74,7 @@ public class SchedulingServiceImpl private final List runningTasks; private final List enqueuedTasks; + private final List pendingAcknowledgement; private final Set deletionPending; @Autowired @@ -86,26 +87,43 @@ public SchedulingServiceImpl(ApplicationContext aApplicationContext, aConfig.getQueueSize(), this::beforeExecute, this::afterExecute); runningTasks = Collections.synchronizedList(new ArrayList<>()); enqueuedTasks = Collections.synchronizedList(new ArrayList<>()); + pendingAcknowledgement = Collections.synchronizedList(new ArrayList<>()); deletionPending = Collections.synchronizedSet(new LinkedHashSet<>()); watchdog = Executors.newScheduledThreadPool(1); watchdog.scheduleAtFixedRate(this::scheduleEligibleTasks, 5, 5, SECONDS); + watchdog.scheduleAtFixedRate(this::cleanUpTasks, 10, 10, SECONDS); } private void beforeExecute(Thread aThread, Runnable aRunnable) { Validate.notNull(aRunnable, "Task cannot be null"); runningTasks.add((Task) aRunnable); - LOG.debug("Starting task [{}]", aRunnable); + LOG.debug("Starting task: {} ", aRunnable); } private void afterExecute(Runnable aRunnable, Throwable aThrowable) { Validate.notNull(aRunnable, "Task cannot be null"); - runningTasks.remove(aRunnable); - LOG.debug("Completed task [{}]", aRunnable); + + var task = (Task) aRunnable; + + LOG.debug("Ended task [{}]: {}", task, task.getMonitor().getState()); + handleTaskEnded(task); + scheduleEligibleTasks(); } + private void handleTaskEnded(Task aTask) + { + runningTasks.remove(aTask); + if (aTask.getMonitor().isCancelled() || !aTask.getScope().isDestroyOnEnd()) { + pendingAcknowledgement.add(aTask); + } + else { + aTask.destroy(); + } + } + /** * @return tasks which have not been handed to the executor yet. */ @@ -139,10 +157,22 @@ public List getRunningTasks() return new ArrayList<>(runningTasks); } + /** + * @return tasks which are no longer running (completed, failed) and which require the user to + * acknowledge the result. + */ + @Override + public List getTasksPendingAcknowledgment() + { + // We return copy here, as else the list the receiver sees might be updated + // when new tasks are running or existing ones stopped. + return new ArrayList<>(pendingAcknowledgement); + } + @Override public List getScheduledAndRunningTasks() { - List result = new ArrayList<>(); + var result = new ArrayList(); result.addAll(getScheduledTasks()); result.addAll(getRunningTasks()); return result; @@ -151,10 +181,11 @@ public List getScheduledAndRunningTasks() @Override public List getAllTasks() { - List result = new ArrayList<>(); + var result = new ArrayList(); result.addAll(getEnqueuedTasks()); result.addAll(getScheduledTasks()); result.addAll(getRunningTasks()); + result.addAll(getTasksPendingAcknowledgment()); return result; } @@ -183,8 +214,8 @@ public synchronized void enqueue(Task aTask) return; } - List tasksToUnqueue = new ArrayList<>(); - for (Task enqueuedTask : enqueuedTasks) { + var tasksToUnqueue = new ArrayList(); + for (var enqueuedTask : enqueuedTasks) { switch (matchTask(aTask, enqueuedTask)) { case DISCARD_OR_QUEUE_THIS: // Check if the incoming task should be discarded @@ -233,8 +264,8 @@ public synchronized void enqueue(Task aTask) private MatchResult matchTask(Task aTask, Task aEnqueueTask) { - if (aTask instanceof MatchableTask) { - return ((MatchableTask) aTask).matches(aEnqueueTask); + if (aTask instanceof MatchableTask task) { + return task.matches(aEnqueueTask); } return aTask.equals(aEnqueueTask) ? UNQUEUE_EXISTING_AND_QUEUE_THIS : NO_MATCH; @@ -242,8 +273,8 @@ private MatchResult matchTask(Task aTask, Task aEnqueueTask) private boolean containsMatchingTask(Collection aTasks, Task aTask) { - if (aTask instanceof MatchableTask) { - return aTasks.stream().anyMatch(t -> ((MatchableTask) aTask).matches(t) != NO_MATCH); + if (aTask instanceof MatchableTask task) { + return aTasks.stream().anyMatch(t -> task.matches(t) != NO_MATCH); } return aTasks.contains(aTask); @@ -261,7 +292,7 @@ private void schedule(Task aTask) try { // This auto-wires the task fields manually - AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory(); + var factory = applicationContext.getAutowireCapableBeanFactory(); factory.autowireBean(aTask); factory.initializeBean(aTask, "transientTask"); } @@ -272,6 +303,40 @@ private void schedule(Task aTask) executor.execute(aTask); } + private synchronized void cleanUpTasks() + { + // var activeSessionCount = 0; + var activeUsers = new HashSet(); + for (var principal : sessionRegistry.getAllPrincipals()) { + var sessions = sessionRegistry.getAllSessions(principal, false); + if (!sessions.isEmpty()) { + activeUsers.add(getUsernameFromPrincipal(principal)); + // activeSessionCount += sessions.size(); + } + } + + // LOG.debug("Found a total of [{}] active sessions for users {}", activeSessionCount, + // activeUsers); + + stopAllTasksMatching(t -> { + var requiresActiveUser = t.getScope().isRemoveWhenUserSessionEnds() + || t.getScope().isRemoveWhenLastUserSessionEnds(); + + var ownedByActiveUser = t.getUser().map(u -> activeUsers.contains(u.getUsername())) + .orElse(false); + + if (requiresActiveUser && !ownedByActiveUser) { + LOG.debug("Task {} requires active user but user is not logged in - cleaning up", + t); + return true; + } + + return false; + }); + + logState(); + } + private synchronized void scheduleEligibleTasks() { Iterator i = enqueuedTasks.iterator(); @@ -316,6 +381,16 @@ public void stopAllTasksForProject(Project aProject) stopAllTasksMatching(t -> t.getProject().equals(aProject)); } + @Override + public synchronized Optional findTask(Predicate aPredicate) + { + return enqueuedTasks.stream().filter(aPredicate).findFirst() // + .or(() -> executor.getQueue().stream().map(Task.class::cast).filter(aPredicate) + .findFirst()) + .or(() -> runningTasks.stream().filter(aPredicate).findFirst()) + .or(() -> pendingAcknowledgement.stream().filter(aPredicate).findFirst()); + } + @Override public synchronized void stopAllTasksMatching(Predicate aPredicate) { @@ -338,9 +413,19 @@ public synchronized void stopAllTasksMatching(Predicate aPredicate) runningTasks.forEach(task -> { if (aPredicate.test(task)) { - task.cancel(); + task.getMonitor().cancel(); + // The task will be destroyed if necessary by the afterExecute callback } }); + + pendingAcknowledgement.removeIf(runnable -> { + var task = (Task) runnable; + if (aPredicate.test(task)) { + task.destroy(); + return true; + } + return false; + }); } @EventListener @@ -361,24 +446,59 @@ public void afterProjectRemoved(AfterProjectRemovedEvent aEvent) throws IOExcept @Order(Ordered.HIGHEST_PRECEDENCE) public void onSessionDestroyed(SessionDestroyedEvent event) { - SessionInformation info = sessionRegistry.getSessionInformation(event.getId()); + LOG.debug("Cleaning up tasks on session destroyed"); + + var sessionInfo = sessionRegistry.getSessionInformation(event.getId()); + // Could be an anonymous session without information. - if (info == null) { + if (sessionInfo == null) { return; } - String username = null; - if (info.getPrincipal() instanceof String) { - username = (String) info.getPrincipal(); + var username = getUsernameFromPrincipal(sessionInfo.getPrincipal()); + if (username == null) { + return; } - if (info.getPrincipal() instanceof User) { - username = ((User) info.getPrincipal()).getUsername(); + var userHasOtherSession = isSessionOwnerLoggedInToOtherActiveSession( + sessionInfo.getPrincipal()); + + stopAllTasksMatching(t -> { + if (!t.getUser().map(_user -> username.equals(_user.getUsername())).orElse(false)) { + return false; + } + + if (t.getScope().isRemoveWhenUserSessionEnds()) { + LOG.debug("Stopping task {} because session has ended", t); + return true; + } + + if (t.getScope().isRemoveWhenLastUserSessionEnds() && !userHasOtherSession) { + LOG.debug("Stopping task {} because last session of user [{}] has ended", t, + username); + return true; + } + + return false; + }); + } + + private boolean isSessionOwnerLoggedInToOtherActiveSession(Object aPrincipal) + { + return !sessionRegistry.getAllSessions(aPrincipal, false).isEmpty(); + } + + private String getUsernameFromPrincipal(Object aPrincipal) + { + if (aPrincipal instanceof String username) { + return username; } - if (username != null) { - stopAllTasksForUser(username); + if (aPrincipal instanceof User user) { + return user.getUsername(); } + + return null; } @Override @@ -389,22 +509,32 @@ public void destroy() executor.getQueue().clear(); watchdog.shutdownNow(); executor.shutdownNow(); + pendingAcknowledgement.clear(); } private void logState() { - getEnqueuedTasks().forEach(t -> LOG.debug("Queued : {}", t)); - getScheduledTasks().forEach(t -> LOG.debug("Scheduled: {}", t)); - getRunningTasks().forEach(t -> LOG.debug("Running : {}", t)); + getEnqueuedTasks().forEach(t -> LOG.debug("Queued : {}", t)); + getScheduledTasks().forEach(t -> LOG.debug("Scheduled : {}", t)); + getRunningTasks().forEach(t -> LOG.debug("Running : {}", t)); + getTasksPendingAcknowledgment().forEach(t -> LOG.debug("Pending ack : {}", t)); } @Override public void executeSync(Task aTask) { - AutowireCapableBeanFactory factory = applicationContext.getAutowireCapableBeanFactory(); - factory.autowireBean(aTask); - factory.initializeBean(aTask, "transientTask"); - aTask.execute(); // Execute synchronously - blocking - aTask.destroy(); + try { + var factory = applicationContext.getAutowireCapableBeanFactory(); + factory.autowireBean(aTask); + factory.initializeBean(aTask, "transientTask"); + + LOG.debug("Starting task (sync): {} ", aTask); + runningTasks.add(aTask); + aTask.runSync(); + } + finally { + LOG.debug("Ended task (sync) [{}]: {}", aTask, aTask.getMonitor().getState()); + handleTaskEnded(aTask); + } } } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java index 2233b4fd812..b706a443a9b 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/Task.java @@ -48,27 +48,31 @@ public abstract class Task private final Project project; private final String trigger; private final int id; + private final String type; + private final boolean cancellable; private TaskMonitor monitor; private Task parentTask; - private boolean cancelled; + private TaskScope scope; - public Task(Project aProject, String aTrigger) + protected Task(Builder> builder) { - this(null, aProject, aTrigger); - } - - public Task(User aSessionOwner, Project aProject, String aTrigger) - { - notNull(aProject, "Project must be specified"); - notNull(aTrigger, "Trigger must be specified"); + notNull(builder.project, "Project must be specified"); + notNull(builder.trigger, "Trigger must be specified"); + notNull(builder.scope, "Scope must be specified"); + notNull(builder.type, "Type must be specified"); id = nextId.getAndIncrement(); handle = new TaskHandle(id); - sessionOwner = aSessionOwner; - project = aProject; - trigger = aTrigger; + sessionOwner = builder.sessionOwner; + project = builder.project; + trigger = builder.trigger; + type = builder.type; + + cancellable = builder.cancellable; + parentTask = builder.parentTask; + scope = builder.scope; } @Override @@ -77,22 +81,16 @@ public void afterPropertiesSet() // For tasks that have a parent task, we use a non-notifying monitor. Also, we do not report // such subtasks ia the SchedulerControllerImpl - they are internal. if (msgTemplate != null && sessionOwner != null && parentTask == null) { - monitor = new NotifyingTaskMonitor(handle, sessionOwner.getUsername(), getTitle(), - msgTemplate); + monitor = new NotifyingTaskMonitor(handle, this, msgTemplate); } else { - monitor = new TaskMonitor(handle, - sessionOwner != null ? sessionOwner.getUsername() : null, getTitle()); + monitor = new TaskMonitor(handle, this); } } - /** - * @param aParentTask - * parent task for this task. - */ - public void setParentTask(Task aParentTask) + public boolean isCancellable() { - parentTask = aParentTask; + return cancellable; } public Task getParentTask() @@ -100,6 +98,11 @@ public Task getParentTask() return parentTask; } + public String getType() + { + return type; + } + public String getTitle() { return getClass().getSimpleName(); @@ -140,14 +143,9 @@ public boolean isReadyToStart() return true; } - public void cancel() - { - cancelled = true; - } - - public boolean isCancelled() + public TaskScope getScope() { - return cancelled; + return scope; } void destroy() @@ -172,31 +170,35 @@ public final void run() MDC.put(KEY_PROJECT_ID, String.valueOf(getProject().getId())); } - monitor.setState(TaskState.RUNNING); - execute(); - if (monitor.getState() == TaskState.RUNNING) { - monitor.setState(TaskState.COMPLETED); - } + runSync(); } finally { - destroy(); MDC.remove(KEY_REPOSITORY_PATH); MDC.remove(KEY_USERNAME); MDC.remove(KEY_PROJECT_ID); } } + public void runSync() + { + monitor.setState(TaskState.RUNNING); + execute(); + if (monitor.getState() == TaskState.RUNNING) { + monitor.setState(TaskState.COMPLETED); + } + } + public abstract void execute(); @Override public String toString() { StringBuilder sb = new StringBuilder(getName()); - sb.append('{'); + sb.append('['); sb.append("user=").append(sessionOwner != null ? sessionOwner.getUsername() : ""); sb.append(", project=").append(project.getName()); sb.append(", trigger=\"").append(trigger); - sb.append("\"}"); + sb.append("\"]"); return sb.toString(); } @@ -218,4 +220,84 @@ public int hashCode() { return Objects.hash(sessionOwner, project); } + + public static abstract class Builder> + { + protected User sessionOwner; + protected Project project; + protected String trigger; + protected String type; + protected boolean cancellable; + protected Task parentTask; + protected TaskScope scope = TaskScope.EPHEMERAL; + + protected Builder() + { + } + + /** + * @param aSessionOwner + * the user owning the task. + */ + @SuppressWarnings("unchecked") + public T withSessionOwner(User aSessionOwner) + { + this.sessionOwner = aSessionOwner; + return (T) this; + } + + /** + * @param aProject + * the project on which the task operates. + */ + @SuppressWarnings("unchecked") + public T withProject(Project aProject) + { + this.project = aProject; + return (T) this; + } + + /** + * @param aTrigger + * the trigger that caused the selection to be scheduled. + */ + @SuppressWarnings("unchecked") + public T withTrigger(String aTrigger) + { + this.trigger = aTrigger; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withType(String aType) + { + this.type = aType; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withCancellable(boolean aCancellable) + { + this.cancellable = aCancellable; + return (T) this; + } + + /** + * @param aParentTask + * parent task for this task. + */ + @SuppressWarnings("unchecked") + public T withParentTask(Task aParentTask) + { + this.parentTask = aParentTask; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withScope(TaskScope aScope) + { + this.scope = aScope; + return (T) this; + } + } } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskMonitor.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskMonitor.java index 2eb73f3894a..d2406ca7a85 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskMonitor.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskMonitor.java @@ -26,6 +26,7 @@ import java.util.Deque; import java.util.concurrent.ConcurrentLinkedDeque; +import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.support.logging.LogMessage; public class TaskMonitor @@ -35,22 +36,29 @@ public class TaskMonitor private final TaskHandle handle; private final String user; private final String title; + private final String type; - private long createTime; + private final long createTime; private long startTime = -1; private long endTime = -1; + private int progress = 0; private int maxProgress = 0; + private TaskState state = NOT_STARTED; + private final boolean cancellable; + private boolean cancelled = false; private boolean destroyed = false; - public TaskMonitor(TaskHandle aHandle, String aUser, String aTitle) + public TaskMonitor(TaskHandle aHandle, Task aTask) { handle = aHandle; - user = aUser; - title = aTitle; + type = aTask.getType(); + user = aTask.getUser().map(User::getUsername).orElse(null); + title = aTask.getTitle(); createTime = System.currentTimeMillis(); + cancellable = aTask.isCancellable(); } public TaskHandle getHandle() @@ -73,6 +81,11 @@ public String getTitle() return title; } + public String getType() + { + return type; + } + public synchronized void setState(TaskState aState) { state = aState; @@ -91,11 +104,6 @@ public synchronized long getCreateTime() return createTime; } - public synchronized void setCreateTime(long aCreateTime) - { - createTime = aCreateTime; - } - public synchronized long getStartTime() { return startTime; @@ -178,8 +186,23 @@ protected void onDestroy() // By default do nothing } - protected boolean isDestroyed() + public boolean isDestroyed() { return destroyed; } + + public boolean isCancellable() + { + return cancellable; + } + + public void cancel() + { + cancelled = true; + } + + public boolean isCancelled() + { + return cancelled; + } } diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnitState.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskScope.java similarity index 53% rename from inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnitState.java rename to inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskScope.java index 52bc770fa98..51c86e9b33d 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnitState.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/TaskScope.java @@ -15,49 +15,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package de.tudarmstadt.ukp.clarin.webanno.ui.curation.overview; +package de.tudarmstadt.ukp.inception.scheduling; -/** - * An ennumeration to differentiate sentences in a document with different colors so as to easily - * identify - * - */ -public enum CurationUnitState +public enum TaskScope { /** - * No conflicts of annotation in this sentence, no color - null- white + * Task can be cleared as soon as it has ended. */ - AGREE("agree"), + EPHEMERAL, /** - * Stacked annotations + * Task can be cleared when the last session of the user is cleared or when the system is + * restarted. */ - STACKED("stacked"), + LAST_USER_SESSION, /** - * Incomplete annotations + * Task can be cleared when the project is deleted or when the system is restarted. */ - INCOMPLETE("incomplete"), + PROJECT; - /** - * Conflicts of annotation found in this sentence, mark background in red - */ - DISAGREE("disagree"), - - /** - * Confirmed annotation. - */ - CURATED("curated"); - - private String cssClass; + boolean isDestroyOnEnd() + { + return this == EPHEMERAL; + } - CurationUnitState(String aCssClass) + boolean isRemoveWhenUserSessionEnds() { - cssClass = aCssClass; + return this == EPHEMERAL; } - public String getCssClass() + boolean isRemoveWhenLastUserSessionEnds() { - return cssClass; + return this == LAST_USER_SESSION || this == EPHEMERAL; } } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/SchedulerController.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/SchedulerController.java index 11002ce832d..10bc08de700 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/SchedulerController.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/SchedulerController.java @@ -23,6 +23,7 @@ public interface SchedulerController { String BASE_URL = BASE_API_URL + "/scheduler"; String CANCEL = "cancel"; + String ACKNOWLEDGE = "acknowledge"; String TASKS = "/tasks"; String PARAM_TASK_ID = "taskId"; } diff --git a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/model/MTaskStateUpdate.java b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/model/MTaskStateUpdate.java index 7c58ea3462c..b9e6472eaa8 100644 --- a/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/model/MTaskStateUpdate.java +++ b/inception/inception-scheduling/src/main/java/de/tudarmstadt/ukp/inception/scheduling/controller/model/MTaskStateUpdate.java @@ -31,12 +31,16 @@ public class MTaskStateUpdate { private final long timestamp; private final int id; + private final String type; private final int progress; private final int maxProgress; private final TaskState state; private final String title; private final int messageCount; + @JsonInclude(Include.NON_DEFAULT) + private final boolean cancellable; + @JsonInclude(Include.NON_DEFAULT) private final boolean removed; @@ -53,12 +57,14 @@ public MTaskStateUpdate(TaskMonitor aMonitor, boolean aRemoved) timestamp = System.currentTimeMillis(); title = aMonitor.getTitle(); id = aMonitor.getHandle().getId(); + type = aMonitor.getType(); progress = aMonitor.getProgress(); maxProgress = aMonitor.getMaxProgress(); state = aMonitor.getState(); messageCount = aMonitor.getMessages().size(); latestMessage = aMonitor.getMessages().peekLast(); removed = aRemoved; + cancellable = aMonitor.isCancellable(); } public String getTitle() @@ -86,6 +92,11 @@ public TaskState getState() return state; } + public String getType() + { + return type; + } + public LogMessage getLatestMessage() { return latestMessage; @@ -101,6 +112,11 @@ public boolean isRemoved() return removed; } + public boolean isCancellable() + { + return cancellable; + } + @Override public boolean equals(final Object other) { diff --git a/inception/inception-scheduling/src/test/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceTest.java b/inception/inception-scheduling/src/test/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceTest.java index b113fc3f30b..8dbe8ead4ec 100644 --- a/inception/inception-scheduling/src/test/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceTest.java +++ b/inception/inception-scheduling/src/test/java/de/tudarmstadt/ukp/inception/scheduling/SchedulingServiceTest.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.scheduling; +import static java.time.Duration.ofSeconds; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; @@ -127,7 +128,8 @@ private Project buildProject(String aProjectName) private Task buildDummyTask(String aUsername, String aProjectName) { - var task = new DummyTask(buildUser(aUsername), buildProject(aProjectName)); + var task = DummyTask.builder().withSessionOwner(buildUser(aUsername)) + .withProject(buildProject(aProjectName)).build(); task.afterPropertiesSet(); return task; } @@ -139,9 +141,11 @@ private Task buildDummyTask(String aUsername, String aProjectName) private static class DummyTask extends Task { - DummyTask(User aUser, Project aProject) + private static final String TYPE = "DummyTask"; + + DummyTask(Builder> aBuilder) { - super(aUser, aProject, "JUnit"); + super(aBuilder.withType(TYPE).withTrigger("test")); } @Override @@ -156,5 +160,24 @@ public void execute() } } } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends DebouncingTask.Builder + { + protected Builder() + { + withDebounceMillis(ofSeconds(3)); + } + + public DummyTask build() + { + return new DummyTask(this); + } + } } } diff --git a/inception/inception-schema-api/pom.xml b/inception/inception-schema-api/pom.xml index 1e4575ded2a..709e56334a2 100644 --- a/inception/inception-schema-api/pom.xml +++ b/inception/inception-schema-api/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-schema-api INCEpTION - Core - Annotation Schema API diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/rendering/Renderer.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/rendering/Renderer.java index 551bb6c14b3..811b633c997 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/rendering/Renderer.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/rendering/Renderer.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.NoSuchElementException; +import org.apache.commons.lang3.StringUtils; import org.apache.uima.cas.CAS; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.text.AnnotationFS; @@ -75,15 +76,15 @@ List render(VDocument aVDocument, AnnotationFS aFS, List renderLabelFeatureValues(TypeAdapter aAdapter, FeatureStructure aFs, List aFeatures) { - FeatureSupportRegistry fsr = getFeatureSupportRegistry(); - Map features = new LinkedHashMap<>(); + var fsr = getFeatureSupportRegistry(); + var features = new LinkedHashMap(); - for (AnnotationFeature feature : aFeatures) { + for (var feature : aFeatures) { if (!feature.isEnabled() || !feature.isVisible()) { continue; } - String label = defaultString(fsr.findExtension(feature) + var label = defaultString(fsr.findExtension(feature) .orElseThrow(() -> new NoSuchElementException( "No feature support found for feature " + feature)) .renderFeatureValue(feature, aFs)); @@ -97,7 +98,7 @@ default Map renderLabelFeatureValues(TypeAdapter aAdapter, Featu default void renderRequiredFeatureErrors(List aFeatures, FeatureStructure aFS, VDocument aResponse) { - for (AnnotationFeature f : aFeatures) { + for (var f : aFeatures) { if (!f.isEnabled()) { continue; } @@ -109,8 +110,7 @@ default void renderRequiredFeatureErrors(List aFeatures, } } - default List lookupLazyDetails(CAS aCas, VID aVid, int windowBeginOffset, - int windowEndOffset) + default List lookupLazyDetails(CAS aCas, VID aVid) { var fsr = getFeatureSupportRegistry(); @@ -130,12 +130,13 @@ default void generateLazyDetailsForFeaturesIncludedInHover(FeatureSupportRegistr continue; } - String text = defaultString( - fsr.findExtension(feature).orElseThrow().renderFeatureValue(feature, aFs)); + var label = fsr.findExtension(feature).orElseThrow().renderFeatureValue(feature, aFs); - var group = new VLazyDetailGroup(); - group.addDetail(new VLazyDetail(feature.getName(), text)); - details.add(group); + if (StringUtils.isNotBlank(label)) { + var group = new VLazyDetailGroup(); + group.addDetail(new VLazyDetail(feature.getName(), label)); + details.add(group); + } } } } diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/AnnotationSchemaService.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/AnnotationSchemaService.java index c10d78b8ec2..2139a2ca8ba 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/AnnotationSchemaService.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/AnnotationSchemaService.java @@ -154,6 +154,15 @@ public interface AnnotationSchemaService */ boolean existsTagSet(Project project); + /** + * Check if any {@link AnnotationLayer} exists with this name in the given {@link Project}. + * + * @param project + * the project. + * @return if a layer exists. + */ + boolean existsLayer(Project project); + /** * Check if an {@link AnnotationLayer} exists with this name in the given {@link Project}. * @@ -178,7 +187,7 @@ public interface AnnotationSchemaService */ boolean existsLayer(String name, String type, Project project); - boolean existsEnabledLayerOfType(Project aProject, String aType); + boolean existsEnabledLayerOfType(Project project, String type); /** * Check if this {@link AnnotationFeature} already exists diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/adapter/TypeAdapter.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/adapter/TypeAdapter.java index 943a93b8ce6..083f92a5f02 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/adapter/TypeAdapter.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/adapter/TypeAdapter.java @@ -154,6 +154,28 @@ void setFeatureValue(SourceDocument aDocument, String aDocumentOwner, CAS aCas, AnnotationFeature aFeature, Object aValue) throws AnnotationException; + default void setFeatureValue(SourceDocument aDocument, String aDocumentOwner, + FeatureStructure aFs, AnnotationFeature aFeature, Object aValue) + throws AnnotationException + + { + setFeatureValue(aDocument, aDocumentOwner, aFs.getCAS(), aFs.getAddress(), aFeature, + aValue); + } + + void pushFeatureValue(SourceDocument aDocument, String aUsername, CAS aCas, int aAddress, + AnnotationFeature aFeature, Object aValue) + throws AnnotationException; + + default void pushFeatureValue(SourceDocument aDocument, String aDocumentOwner, + FeatureStructure aFs, AnnotationFeature aFeature, Object aValue) + throws AnnotationException + + { + pushFeatureValue(aDocument, aDocumentOwner, aFs.getCAS(), aFs.getAddress(), aFeature, + aValue); + } + /** * Get the value of the given feature. * diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureSupport.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureSupport.java index 2529d42c52f..1c8106957da 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureSupport.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureSupport.java @@ -274,6 +274,12 @@ default void setFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, setFeature(fs, aFeature, value); } + default void pushFeatureValue(CAS aCas, AnnotationFeature aFeature, int aAddress, Object aValue) + throws AnnotationException + { + setFeatureValue(aCas, aFeature, aAddress, aValue); + } + @SuppressWarnings("unchecked") default V getFeatureValue(AnnotationFeature aFeature, FeatureStructure aFS) { diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureType.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureType.java index 21bec203cdb..d184a556dfe 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureType.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureType.java @@ -19,6 +19,9 @@ import java.io.Serializable; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + public class FeatureType implements Serializable { @@ -71,6 +74,15 @@ public boolean isInternal() return internal; } + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.JSON_STYLE) // + .append("name", name) // + .append("featureSupportId", featureSupportId) // + .toString(); + } + @Override public int hashCode() { diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureUtil.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureUtil.java index 6d7283b664e..3e5a61e3cb0 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureUtil.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/FeatureUtil.java @@ -53,7 +53,7 @@ public static void setFeature(FeatureStructure aFS, AnnotationFeature aFeature, return; } - Feature feature = aFS.getType().getFeatureByBaseName(aFeature.getName()); + var feature = aFS.getType().getFeatureByBaseName(aFeature.getName()); if (feature == null) { throw new IllegalArgumentException("On [" + aFS.getType().getName() + "] the feature [" @@ -62,7 +62,7 @@ public static void setFeature(FeatureStructure aFS, AnnotationFeature aFeature, switch (aFeature.getMultiValueMode()) { case NONE: { - String effectiveType = aFeature.getType(); + var effectiveType = aFeature.getType(); if (effectiveType.contains(":")) { effectiveType = CAS.TYPE_NAME_STRING; } @@ -74,6 +74,11 @@ public static void setFeature(FeatureStructure aFS, AnnotationFeature aFeature, + "] does not match expected feature type [" + effectiveType + "]."); } + if (aValue instanceof String stringValue) { + ICasUtil.setFeature(aFS, feature, stringValue); + break; + } + switch (effectiveType) { case CAS.TYPE_NAME_STRING: aFS.setStringValue(feature, (String) aValue); diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/LinkWithRoleModel.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/LinkWithRoleModel.java index f0de33544ef..e0e811af39f 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/LinkWithRoleModel.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/feature/LinkWithRoleModel.java @@ -19,6 +19,8 @@ import java.io.Serializable; +import org.apache.uima.cas.FeatureStructure; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -51,6 +53,13 @@ public LinkWithRoleModel(LinkWithRoleModel aOther) targetAddr = aOther.targetAddr; } + public LinkWithRoleModel(String aRole, FeatureStructure aFS) + { + role = aRole; + label = aRole; + targetAddr = aFS.getAddress(); + } + public LinkWithRoleModel(String aRole, String aLabel, int aTargetAddr) { role = aRole; diff --git a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/validation/ValidationUtils.java b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/validation/ValidationUtils.java index a793e754971..da9c1315c63 100644 --- a/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/validation/ValidationUtils.java +++ b/inception/inception-schema-api/src/main/java/de/tudarmstadt/ukp/inception/schema/api/validation/ValidationUtils.java @@ -20,6 +20,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.uima.cas.CAS.TYPE_NAME_STRING; +import java.util.List; + +import org.apache.uima.cas.CAS; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.fit.util.FSUtil; @@ -33,11 +36,16 @@ public static boolean isRequiredFeatureMissing(AnnotationFeature aFeature, Featu return false; } - if (TYPE_NAME_STRING.equals(aFeature.getType())) { + if (TYPE_NAME_STRING.equals(aFeature.getType()) || aFeature.isVirtualFeature()) { // Only string features can have null values and be required return isBlank(FSUtil.getFeature(aFS, aFeature.getName(), String.class)); } + if (CAS.TYPE_NAME_STRING_ARRAY.equals(aFeature.getType())) { + var value = FSUtil.getFeature(aFS, aFeature.getName(), List.class); + return value == null || value.isEmpty(); + } + return false; } } diff --git a/inception/inception-schema/pom.xml b/inception/inception-schema/pom.xml index 321a4dcc3cc..8c49d25af35 100644 --- a/inception/inception-schema/pom.xml +++ b/inception/inception-schema/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-schema INCEpTION - Core - Annotation Schema diff --git a/inception/inception-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/service/AnnotationSchemaServiceImpl.java b/inception/inception-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/service/AnnotationSchemaServiceImpl.java index f3cfe1d012c..7ba4bb2fe07 100644 --- a/inception/inception-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/service/AnnotationSchemaServiceImpl.java +++ b/inception/inception-schema/src/main/java/de/tudarmstadt/ukp/inception/schema/service/AnnotationSchemaServiceImpl.java @@ -395,6 +395,18 @@ public boolean existsTagSet(Project aProject) } } + @Override + @Transactional(noRollbackFor = NoResultException.class) + public boolean existsLayer(Project aProject) + { + return entityManager.createQuery( + "SELECT COUNT(*) FROM AnnotationLayer WHERE project = :project AND name NOT IN (:excluded)", + Long.class) // + .setParameter("project", aProject) // + .setParameter("excluded", asList(Token._TypeName, Sentence._TypeName)) // + .getSingleResult() > 0; + } + @Override @Transactional(noRollbackFor = NoResultException.class) public boolean existsLayer(String aName, Project aProject) @@ -403,7 +415,8 @@ public boolean existsLayer(String aName, Project aProject) entityManager .createQuery("FROM AnnotationLayer WHERE name = :name AND project = :project", AnnotationLayer.class) - .setParameter("name", aName).setParameter("project", aProject) + .setParameter("name", aName) // + .setParameter("project", aProject) // .getSingleResult(); return true; } @@ -419,13 +432,15 @@ public boolean existsLayer(String aName, String aType, Project aProject) try { entityManager.createQuery( "FROM AnnotationLayer WHERE name = :name AND type = :type AND project = :project", - AnnotationLayer.class).setParameter("name", aName).setParameter("type", aType) - .setParameter("project", aProject).getSingleResult(); + AnnotationLayer.class) // + .setParameter("name", aName) // + .setParameter("type", aType) // + .setParameter("project", aProject) // + .getSingleResult(); return true; } catch (NoResultException e) { return false; - } } @@ -1067,17 +1082,17 @@ public TypeSystemDescription getCustomProjectTypes(Project aProject) public TypeSystemDescription getAllProjectTypes(Project aProject) throws ResourceInitializationException { - List allLayersInProject = listSupportedLayers(aProject); - List allFeaturesInProject = listSupportedFeatures(aProject); + var allLayersInProject = listSupportedLayers(aProject); + var allFeaturesInProject = listSupportedFeatures(aProject); - List allTsds = new ArrayList<>(); - for (AnnotationLayer layer : allLayersInProject) { + var allTsds = new ArrayList(); + for (var layer : allLayersInProject) { LayerSupport layerSupport = layerSupportRegistry.getLayerSupport(layer); // for built-in layers, we clone the information from the built-in type descriptors - TypeSystemDescription tsd = new TypeSystemDescription_impl(); + var tsd = new TypeSystemDescription_impl(); if (layer.isBuiltIn()) { - for (String typeName : layerSupport.getGeneratedTypeNames(layer)) { + for (var typeName : layerSupport.getGeneratedTypeNames(layer)) { exportBuiltInTypeDescription(builtInTypes, tsd, typeName); } } @@ -1090,14 +1105,14 @@ public TypeSystemDescription getAllProjectTypes(Project aProject) { // Explicitly add Token because the layer may not be declared in the project - TypeSystemDescription tsd = new TypeSystemDescription_impl(); + var tsd = new TypeSystemDescription_impl(); exportBuiltInTypeDescription(builtInTypes, tsd, Token.class.getName()); allTsds.add(tsd); } { // Explicitly add Sentence because the layer may not be declared in the project - TypeSystemDescription tsd = new TypeSystemDescription_impl(); + var tsd = new TypeSystemDescription_impl(); exportBuiltInTypeDescription(builtInTypes, tsd, Sentence.class.getName()); allTsds.add(tsd); } @@ -1110,18 +1125,18 @@ public TypeSystemDescription getAllProjectTypes(Project aProject) private void exportBuiltInTypeDescription(TypeSystemDescription aSource, TypeSystemDescription aTarget, String aType) { - TypeDescription builtInType = aSource.getType(aType); + var builtInType = aSource.getType(aType); if (builtInType == null) { throw new IllegalArgumentException( "No type description found for type [" + aType + "]"); } - TypeDescription clonedType = aTarget.addType(builtInType.getName(), - builtInType.getDescription(), builtInType.getSupertypeName()); + var clonedType = aTarget.addType(builtInType.getName(), builtInType.getDescription(), + builtInType.getSupertypeName()); if (builtInType.getFeatures() != null) { - for (FeatureDescription feature : builtInType.getFeatures()) { + for (var feature : builtInType.getFeatures()) { clonedType.addFeature(feature.getName(), feature.getDescription(), feature.getRangeTypeName(), feature.getElementType(), feature.getMultipleReferencesAllowed()); diff --git a/inception/inception-search-core/pom.xml b/inception/inception-search-core/pom.xml index f1b94df498c..2ef2c899e10 100644 --- a/inception/inception-search-core/pom.xml +++ b/inception/inception-search-core/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-search-core INCEpTION - Search - Core diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchService.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchService.java index 2f3e8cad63f..da1ca785180 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchService.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchService.java @@ -166,5 +166,5 @@ public StatisticsResult getQueryStatistics(User aUser, Project aProject, String int aMinTokenPerDoc, int aMaxTokenPerDoc, Set aFeatures) throws ExecutionException, IOException; - void enqueueReindexTask(Project aProject, String aUser, String aTrigger); + void enqueueReindexTask(Project aProject, User aUser, String aTrigger); } diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchServiceImpl.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchServiceImpl.java index 4cccad77d12..b841d93bf87 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchServiceImpl.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/SearchServiceImpl.java @@ -837,24 +837,37 @@ private void invalidateIndexAndForceIndexRebuild(Project aProject, Index aIndex, private void enqueueReindexTask(Project aProject, String aTrigger) { - enqueue(new ReindexTask(aProject, null, aTrigger)); + enqueue(ReindexTask.builder() // + .withProject(aProject) // + .withTrigger(aTrigger) // + .build()); } @Override @Transactional - public void enqueueReindexTask(Project aProject, String aUser, String aTrigger) + public void enqueueReindexTask(Project aProject, User aUser, String aTrigger) { - enqueue(new ReindexTask(aProject, aUser, aTrigger)); + enqueue(ReindexTask.builder() // + .withProject(aProject) // + .withSessionOwner(aUser) // + .withTrigger(aTrigger) // + .build()); } private void enqueueIndexDocument(SourceDocument aSourceDocument, String aTrigger) { - enqueue(new IndexSourceDocumentTask(aSourceDocument, aTrigger)); + enqueue(IndexSourceDocumentTask.builder() // + .withSourceDocument(aSourceDocument) // + .withTrigger(aTrigger) // + .build()); } private void enqueueIndexDocument(AnnotationDocument aAnnotationDocument, String aTrigger) { - enqueue(new IndexAnnotationDocumentTask(aAnnotationDocument, aTrigger)); + enqueue(IndexAnnotationDocumentTask.builder() // + .withAnnotationDocument(aAnnotationDocument) // + .withTrigger(aTrigger) // + .build()); } /** diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexAnnotationDocumentTask.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexAnnotationDocumentTask.java index 293c1e78e8c..1ddd3ebca83 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexAnnotationDocumentTask.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexAnnotationDocumentTask.java @@ -28,13 +28,14 @@ import static de.tudarmstadt.ukp.inception.scheduling.MatchResult.UNQUEUE_EXISTING_AND_QUEUE_THIS; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.Objects; +import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument; import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.scheduling.MatchResult; @@ -49,16 +50,20 @@ public class IndexAnnotationDocumentTask extends IndexingTask_ImplBase { - private final Logger log = LoggerFactory.getLogger(getClass()); + public static final String TYPE = "IndexAnnotationDocumentTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired SearchService searchService; private @Autowired DocumentService documentService; private int done = 0; - public IndexAnnotationDocumentTask(AnnotationDocument aAnnotationDocument, String aTrigger) + public IndexAnnotationDocumentTask(Builder> aBuilder) { - super(aAnnotationDocument, aTrigger); + super(aBuilder // + .withProject(aBuilder.annotationDocument.getProject()) // + .withType(TYPE)); } @Override @@ -77,7 +82,7 @@ public void execute() searchService.indexDocument(aDoc, WebAnnoCasUtil.casToByteArray(cas)); } catch (IOException e) { - log.error("Error indexing annotation document {}", getSourceDocument(), e); + LOG.error("Error indexing annotation document {}", getSourceDocument(), e); } done++; @@ -110,4 +115,20 @@ public MatchResult matches(Task aTask) return NO_MATCH; } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends IndexingTask_ImplBase.Builder + { + public IndexAnnotationDocumentTask build() + { + Validate.notNull(annotationDocument, "Annotation document must be specified"); + + return new IndexAnnotationDocumentTask(this); + } + } } diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexSourceDocumentTask.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexSourceDocumentTask.java index 465c39476e8..89ff71ca169 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexSourceDocumentTask.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexSourceDocumentTask.java @@ -28,13 +28,14 @@ import static de.tudarmstadt.ukp.inception.scheduling.MatchResult.UNQUEUE_EXISTING_AND_QUEUE_THIS; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.Objects; +import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.scheduling.MatchResult; @@ -49,16 +50,20 @@ public class IndexSourceDocumentTask extends IndexingTask_ImplBase { - private final Logger log = LoggerFactory.getLogger(getClass()); + public static final String TYPE = "IndexSourceDocumentTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired SearchService searchService; private @Autowired DocumentService documentService; private int done = 0; - public IndexSourceDocumentTask(SourceDocument aSourceDocument, String aTrigger) + public IndexSourceDocumentTask(Builder> aBuilder) { - super(aSourceDocument, aTrigger); + super(aBuilder // + .withProject(aBuilder.sourceDocument.getProject()) // + .withType(TYPE)); } @Override @@ -76,7 +81,7 @@ public void execute() searchService.indexDocument(getSourceDocument(), WebAnnoCasUtil.casToByteArray(cas)); } catch (IOException e) { - log.error("Error indexing source document {}", getSourceDocument(), e); + LOG.error("Error indexing source document {}", getSourceDocument(), e); } done++; @@ -109,4 +114,20 @@ public MatchResult matches(Task aTask) return NO_MATCH; } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends IndexingTask_ImplBase.Builder + { + public IndexSourceDocumentTask build() + { + Validate.notNull(sourceDocument, "Annotation document must be specified"); + + return new IndexSourceDocumentTask(this); + } + } } diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexingTask_ImplBase.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexingTask_ImplBase.java index bfa900be549..029f88dde8b 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexingTask_ImplBase.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/IndexingTask_ImplBase.java @@ -20,9 +20,7 @@ import java.util.Objects; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.inception.scheduling.MatchableTask; import de.tudarmstadt.ukp.inception.scheduling.Task; import de.tudarmstadt.ukp.inception.search.model.Progress; @@ -37,28 +35,12 @@ public abstract class IndexingTask_ImplBase private final SourceDocument sourceDocument; private final AnnotationDocument annotationDocument; - public IndexingTask_ImplBase(Project aProject, String aUser, String aTrigger) + protected IndexingTask_ImplBase(Builder> aBuilder) { - super(new User(aUser), aProject, aTrigger); + super(aBuilder); - sourceDocument = null; - annotationDocument = null; - } - - public IndexingTask_ImplBase(SourceDocument aSourceDocument, String aTrigger) - { - super(aSourceDocument.getProject(), aTrigger); - - sourceDocument = aSourceDocument; - annotationDocument = null; - } - - public IndexingTask_ImplBase(AnnotationDocument aAnnotationDocument, String aTrigger) - { - super(new User(aAnnotationDocument.getUser()), aAnnotationDocument.getProject(), aTrigger); - - sourceDocument = null; - annotationDocument = aAnnotationDocument; + sourceDocument = aBuilder.sourceDocument; + annotationDocument = aBuilder.annotationDocument; } public SourceDocument getSourceDocument() @@ -80,8 +62,10 @@ public String toString() builder.append(getClass().getSimpleName()); builder.append(" [project="); builder.append(getProject().getName()); - builder.append(", user="); - builder.append((getUser().isPresent()) ? " " : getUser().get()); + if (getUser().isPresent()) { + builder.append(", user="); + builder.append(getUser().get()); + } builder.append(", sourceDocument="); builder.append(sourceDocument == null ? "null" : sourceDocument.getName()); builder.append(", annotationDocument="); @@ -111,4 +95,29 @@ public int hashCode() { return Objects.hash(super.hashCode(), sourceDocument, annotationDocument); } + + public static abstract class Builder> + extends Task.Builder + { + protected SourceDocument sourceDocument; + protected AnnotationDocument annotationDocument; + + protected Builder() + { + } + + @SuppressWarnings("unchecked") + public T withSourceDocument(SourceDocument aSourceDocument) + { + this.sourceDocument = aSourceDocument; + return (T) this; + } + + @SuppressWarnings("unchecked") + public T withAnnotationDocument(AnnotationDocument aAnnotationDocument) + { + this.annotationDocument = aAnnotationDocument; + return (T) this; + } + } } diff --git a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/ReindexTask.java b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/ReindexTask.java index f9287c2a270..6ad28256c6b 100644 --- a/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/ReindexTask.java +++ b/inception/inception-search-core/src/main/java/de/tudarmstadt/ukp/inception/search/scheduling/tasks/ReindexTask.java @@ -25,13 +25,13 @@ import static de.tudarmstadt.ukp.inception.scheduling.MatchResult.UNQUEUE_EXISTING_AND_QUEUE_THIS; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.scheduling.MatchResult; import de.tudarmstadt.ukp.inception.scheduling.Task; import de.tudarmstadt.ukp.inception.search.SearchService; @@ -44,15 +44,17 @@ public class ReindexTask extends IndexingTask_ImplBase { - private Logger log = LoggerFactory.getLogger(getClass()); + public static final String TYPE = "ReindexTask"; + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private @Autowired SearchService searchService; private Monitor monitor = new Monitor(); - public ReindexTask(Project aProject, String aUser, String aTrigger) + public ReindexTask(Builder> aBuilder) { - super(aProject, aUser, aTrigger); + super(aBuilder.withType(TYPE)); } @Override @@ -68,7 +70,7 @@ public void execute() searchService.reindex(super.getProject(), monitor); } catch (IOException e) { - log.error("Unable to reindex project [{}]({})", getProject().getName(), + LOG.error("Unable to reindex project [{}]({})", getProject().getName(), getProject().getId(), e); } } @@ -93,4 +95,18 @@ public MatchResult matches(Task aTask) return NO_MATCH; } + + public static Builder> builder() + { + return new Builder<>(); + } + + public static class Builder> + extends IndexingTask_ImplBase.Builder + { + public ReindexTask build() + { + return new ReindexTask(this); + } + } } diff --git a/inception/inception-search-mtas/pom.xml b/inception/inception-search-mtas/pom.xml index 8932e9e2175..1f522ffb030 100644 --- a/inception/inception-search-mtas/pom.xml +++ b/inception/inception-search-mtas/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-search-mtas INCEpTION - Search - MTAS diff --git a/inception/inception-search-mtas/src/test/java/de/tudarmstadt/ukp/inception/search/index/mtas/MtasUpgradeTest.java b/inception/inception-search-mtas/src/test/java/de/tudarmstadt/ukp/inception/search/index/mtas/MtasUpgradeTest.java index 6ae89a6ee5a..e28b0bd83b8 100644 --- a/inception/inception-search-mtas/src/test/java/de/tudarmstadt/ukp/inception/search/index/mtas/MtasUpgradeTest.java +++ b/inception/inception-search-mtas/src/test/java/de/tudarmstadt/ukp/inception/search/index/mtas/MtasUpgradeTest.java @@ -50,6 +50,7 @@ import de.tudarmstadt.ukp.inception.annotation.storage.config.CasStorageServiceAutoConfiguration; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryPropertiesImpl; import de.tudarmstadt.ukp.inception.documents.config.DocumentServiceAutoConfiguration; import de.tudarmstadt.ukp.inception.export.config.DocumentImportExportServiceAutoConfiguration; import de.tudarmstadt.ukp.inception.io.xmi.XmiFormatSupport; @@ -185,7 +186,7 @@ ApplicationContextProvider applicationContextProvider() @Bean RepositoryProperties repositoryProperties() { - var props = new RepositoryProperties(); + var props = new RepositoryPropertiesImpl(); props.setPath(new File(WORK_DIR)); return props; } diff --git a/inception/inception-security/pom.xml b/inception/inception-security/pom.xml index e68ec566c1e..a9f9c33e154 100644 --- a/inception/inception-security/pom.xml +++ b/inception/inception-security/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-security INCEpTION - Core - Security diff --git a/inception/inception-sharing/pom.xml b/inception/inception-sharing/pom.xml index 0d2a2a2b197..c9a39e41383 100644 --- a/inception/inception-sharing/pom.xml +++ b/inception/inception-sharing/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-sharing INCEpTION - Sharing diff --git a/inception/inception-support-bootstrap/pom.xml b/inception/inception-support-bootstrap/pom.xml index 9d3f93ee43c..9513571f4d6 100644 --- a/inception/inception-support-bootstrap/pom.xml +++ b/inception/inception-support-bootstrap/pom.xml @@ -20,7 +20,7 @@ inception-app de.tudarmstadt.ukp.inception.app - 31.0-SNAPSHOT + 32.0-SNAPSHOT 4.0.0 inception-support-bootstrap diff --git a/inception/inception-support-bootstrap/src/main/ts_template/package-lock.json b/inception/inception-support-bootstrap/src/main/ts_template/package-lock.json index 9aaeabf1766..cedaec5ff2b 100644 --- a/inception/inception-support-bootstrap/src/main/ts_template/package-lock.json +++ b/inception/inception-support-bootstrap/src/main/ts_template/package-lock.json @@ -9,17 +9,33 @@ "version": "${semver}", "license": "Apache-2.0", "devDependencies": { - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "cross-env": "^7.0.3", "esbuild": "~0.19.5", "esbuild-sass-plugin": "~2.16.0", "sass": "~1.68.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz", - "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -33,9 +49,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz", - "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -49,9 +65,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz", - "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -65,9 +81,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz", - "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -81,9 +97,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz", - "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -97,9 +113,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz", - "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -113,9 +129,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz", - "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -129,9 +145,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz", - "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -145,9 +161,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz", - "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -161,9 +177,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz", - "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -177,9 +193,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz", - "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -193,9 +209,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz", - "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -209,9 +225,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz", - "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -225,9 +241,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz", - "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -241,9 +257,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz", - "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -257,9 +273,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz", - "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -273,9 +289,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz", - "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -289,9 +305,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz", - "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -305,9 +321,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz", - "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -321,9 +337,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz", - "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -337,9 +353,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz", - "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -353,9 +369,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz", - "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -402,9 +418,9 @@ } }, "node_modules/bootstrap": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "dev": true, "funding": [ { @@ -433,16 +449,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -455,6 +465,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -492,9 +505,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz", - "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -504,34 +517,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/esbuild-sass-plugin": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.0.tgz", - "integrity": "sha512-mGCe9MxNYvZ+j77Q/QFO+rwUGA36mojDXkOhtVmoyz1zwYbMaNrtVrmXwwYDleS/UMKTNU3kXuiTtPiAD3K+Pw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-2.16.1.tgz", + "integrity": "sha512-mBB2aEF0xk7yo+Q9pSUh8xYED/1O2wbAM6IauGkDrqy6pl9SbJNakLeLGXiNpNujWIudu8TJTZCv2L5AQYRXtA==", "dev": true, "dependencies": { "resolve": "^1.22.6", @@ -589,9 +603,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -601,9 +615,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "dev": true }, "node_modules/is-binary-path": { diff --git a/inception/inception-support-standalone/pom.xml b/inception/inception-support-standalone/pom.xml index 4f32b697404..77f65487d06 100644 --- a/inception/inception-support-standalone/pom.xml +++ b/inception/inception-support-standalone/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-support-standalone INCEpTION - Core - Support for standalone mode diff --git a/inception/inception-support/pom.xml b/inception/inception-support/pom.xml index 806c906912f..19879a4b0d0 100644 --- a/inception/inception-support/pom.xml +++ b/inception/inception-support/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-support INCEpTION - Support library diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/io/ZipUtils.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/io/ZipUtils.java index 82adeca7ae0..36aab5320f9 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/io/ZipUtils.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/io/ZipUtils.java @@ -19,11 +19,14 @@ import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; @@ -117,4 +120,40 @@ public static String normalizeEntryName(ZipEntry aEntry) return entryName; } + + public static InputStream openResourceStream(File aZipFile, String aEntryName) + throws IOException + { + if (aEntryName.contains("..") || aEntryName.contains("//")) { + throw new FileNotFoundException("Resource not found [" + aEntryName + "]"); + } + + ZipFile zipFile = null; + var success = false; + try { + zipFile = new ZipFile(aZipFile); + var entry = zipFile.getEntry(aEntryName); + if (entry == null) { + throw new FileNotFoundException("Resource not found [" + aEntryName + "]"); + } + + var finalZipFile = zipFile; + var is = new FilterInputStream(zipFile.getInputStream(entry)) + { + @Override + public void close() throws IOException + { + super.close(); + finalZipFile.close(); + } + }; + success = true; + return is; + } + finally { + if (!success && zipFile != null) { + zipFile.close(); + } + } + } } diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaAjaxBehavior.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaAjaxBehavior.java new file mode 100644 index 00000000000..4b3b9dcc847 --- /dev/null +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaAjaxBehavior.java @@ -0,0 +1,101 @@ +/* + * 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.support.lambda; + +import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; +import org.apache.wicket.feedback.IFeedback; +import org.apache.wicket.request.RequestHandlerExecutor.ReplaceHandlerException; +import org.slf4j.LoggerFactory; + +public class LambdaAjaxBehavior + extends AbstractDefaultAjaxBehavior +{ + private static final long serialVersionUID = 4211352559572747320L; + + private AjaxCallback action; + private AjaxExceptionHandler exceptionHandler; + private boolean preventDefault = false; + + public LambdaAjaxBehavior() + { + this(null, null); + } + + public LambdaAjaxBehavior(AjaxCallback aAction) + { + this(aAction, null); + } + + public LambdaAjaxBehavior(AjaxCallback aAction, AjaxExceptionHandler aExceptionHandler) + { + action = aAction; + exceptionHandler = aExceptionHandler; + } + + public void setAction(AjaxCallback aAction) + { + action = aAction; + } + + public void setExceptionHandler(AjaxExceptionHandler aExceptionHandler) + { + exceptionHandler = aExceptionHandler; + } + + @Override + protected void respond(AjaxRequestTarget aTarget) + { + if (action == null) { + return; + } + + try { + action.accept(aTarget); + } + catch (ReplaceHandlerException e) { + // Let Wicket redirects still work + throw e; + } + catch (Exception e) { + if (exceptionHandler != null) { + exceptionHandler.accept(aTarget, e); + } + else { + LoggerFactory.getLogger(getComponent().getPage().getClass()) + .error("Error: " + e.getMessage(), e); + getComponent().error("Error: " + e.getMessage()); + aTarget.addChildren(getComponent().getPage(), IFeedback.class); + } + } + } + + public LambdaAjaxBehavior setPreventDefault(boolean aPreventDefault) + { + preventDefault = aPreventDefault; + return this; + } + + @Override + protected void updateAjaxAttributes(AjaxRequestAttributes aAttributes) + { + super.updateAjaxAttributes(aAttributes); + aAttributes.setPreventDefault(preventDefault); + }; +} diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaBehavior.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaBehavior.java index caad84ec395..f365ba15746 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaBehavior.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/lambda/LambdaBehavior.java @@ -29,6 +29,20 @@ public class LambdaBehavior { + public static Behavior onDetach(SerializableRunnable aAction) + { + return new Behavior() + { + private static final long serialVersionUID = -6144591383577622961L; + + @Override + public void detach(Component aComponent) + { + aAction.run(); + } + }; + } + public static Behavior onConfigure(SerializableRunnable aAction) { return new Behavior() diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/markdown/MarkdownUtil.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/markdown/MarkdownUtil.java index ef75b3af2b7..13582409a6a 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/markdown/MarkdownUtil.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/markdown/MarkdownUtil.java @@ -17,9 +17,18 @@ */ package de.tudarmstadt.ukp.inception.support.markdown; +import static java.util.Arrays.asList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; import org.owasp.html.HtmlPolicyBuilder; +import org.owasp.html.HtmlStreamEventProcessor; +import org.owasp.html.HtmlStreamEventReceiver; +import org.owasp.html.HtmlStreamEventReceiverWrapper; import org.owasp.html.PolicyFactory; import org.owasp.html.Sanitizers; @@ -27,14 +36,17 @@ public class MarkdownUtil { - public static final PolicyFactory TERSE_POLICY = Sanitizers.FORMATTING.and(Sanitizers.LINKS); + public static final PolicyFactory STYLED_EXTERNAL_LINKS = new HtmlPolicyBuilder() // + .withPostprocessor(new Postprocessor()) // + .toFactory(); + + public static final PolicyFactory TERSE_POLICY = Sanitizers.FORMATTING // + .and(Sanitizers.LINKS) // + .and(STYLED_EXTERNAL_LINKS); public static final PolicyFactory DEFAULT_POLICY = new HtmlPolicyBuilder() // - .allowStandardUrlProtocols() // // Allow title="..." on any element. .allowAttributes("title").globally() // - // Allow href="..." on elements. - .allowAttributes("href").onElements("a") // // Defeat link spammers. .requireRelNofollowOnLinks() // // Allow lang= with an alphabetic value on any element. @@ -48,9 +60,11 @@ public class MarkdownUtil .allowCommonBlockElements() // .allowCommonInlineFormattingElements() // .allowElements("table", "th", "td", "tr", "tbody", "thead", "col", "colgroup", - "caption") + "caption") // .toFactory() // - .and(Sanitizers.IMAGES); + .and(Sanitizers.LINKS) // + .and(Sanitizers.IMAGES) // + .and(STYLED_EXTERNAL_LINKS); public static String markdownToTerseHtml(String aMarkdown) { @@ -71,4 +85,67 @@ public static String markdownToHtml(String aMarkdown) var html = Processor.process(aMarkdown, true); return DEFAULT_POLICY.sanitize(html); } + + private static final class Postprocessor + implements HtmlStreamEventProcessor + { + + @Override + public HtmlStreamEventReceiver wrap(HtmlStreamEventReceiver sink) + { + return new OpenExternalLinksInNewWindow(sink); + } + } + + static class OpenExternalLinksInNewWindow + extends HtmlStreamEventReceiverWrapper + { + private Stack stack = new Stack<>(); + + OpenExternalLinksInNewWindow(HtmlStreamEventReceiver underlying) + { + super(underlying); + } + + @Override + public void openTag(String elementName, List attribs) + { + var externalLink = false; + var attribsCopy = new ArrayList<>(attribs); + for (int i = attribsCopy.size() - 2; i >= 0; i -= 2) { + if ("href".equalsIgnoreCase(attribs.get(i))) { + var link = attribs.get(i + 1); + if (StringUtils.contains(link, "://")) { + externalLink = true; + break; + } + } + + if ("target".equalsIgnoreCase(attribs.get(i))) { + attribs.remove(i + 1); + attribs.remove(i); + } + } + + if (externalLink) { + attribs.add("target"); + attribs.add("_blank"); + } + stack.push(externalLink); + + underlying.openTag(elementName, attribs); + } + + @Override + public void closeTag(String elementName) + { + underlying.closeTag(elementName); + var externalLink = stack.pop(); + if (externalLink) { + underlying.openTag("i", + asList("class", "ms-1 small fas fa-external-link-alt text-muted")); + underlying.closeTag("i"); + } + } + } } diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/uima/ICasUtil.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/uima/ICasUtil.java index 112cf616147..c8daccb9fd7 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/uima/ICasUtil.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/uima/ICasUtil.java @@ -17,18 +17,65 @@ */ package de.tudarmstadt.ukp.inception.support.uima; +import static org.apache.uima.fit.util.FSCollectionFactory.createStringArrayFS; +import static org.apache.uima.fit.util.FSCollectionFactory.createStringList; + import org.apache.uima.cas.CAS; import org.apache.uima.cas.Feature; import org.apache.uima.cas.FeatureStructure; import org.apache.uima.cas.impl.CASImpl; import org.apache.uima.cas.text.AnnotationFS; import org.apache.uima.fit.util.CasUtil; +import org.apache.uima.fit.util.FSUtil; import org.apache.uima.jcas.cas.TOP; public class ICasUtil { public static final String UIMA_BUILTIN_JCAS_PREFIX = "org.apache.uima.jcas."; + public static boolean isPrimitive(String aTypeName) + { + if (aTypeName == null) { + return false; + } + + switch (aTypeName) { + case CAS.TYPE_NAME_BOOLEAN, // + CAS.TYPE_NAME_BYTE, // + CAS.TYPE_NAME_DOUBLE, // + CAS.TYPE_NAME_FLOAT, // + CAS.TYPE_NAME_INTEGER, // + CAS.TYPE_NAME_LONG, // + CAS.TYPE_NAME_SHORT, // + CAS.TYPE_NAME_STRING: + return true; + default: + return false; + } + } + + /** + * Copied from {@link FSUtil#setFeature(FeatureStructure, Feature, String...)} but changed it to + * allow setting any primitive feature value from a string value. + */ + public static void setFeature(FeatureStructure aFS, Feature feat, String... aValue) + { + if (feat.getRange().isPrimitive()) { + // requireSingleValue(feat, aValue); + aFS.setFeatureValueFromString(feat, aValue[0]); + // aFS.setStringValue(feat, aValue[0]); + } + else if (aValue == null) { + aFS.setFeatureValue(feat, null); + } + else if (feat.getRange().isArray()) { + aFS.setFeatureValue(feat, createStringArrayFS(aFS.getCAS(), aValue)); + } + else { + aFS.setFeatureValue(feat, createStringList(aFS.getCAS(), aValue)); + } + } + /** * @param aFS * the feature structure to retrieve the value from diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/AjaxDownloadBehavior.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/AjaxDownloadBehavior.java index dd8dc79760e..4dfbff4598c 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/AjaxDownloadBehavior.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/AjaxDownloadBehavior.java @@ -19,6 +19,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AbstractAjaxBehavior; import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; import org.apache.wicket.request.resource.ContentDisposition; import org.apache.wicket.util.resource.FileResourceStream; @@ -43,6 +44,11 @@ public class AjaxDownloadBehavior private boolean addAntiCache = true; + public AjaxDownloadBehavior() + { + this(null, Model.of()); + } + public AjaxDownloadBehavior(IModel aData) { this(null, aData); @@ -73,6 +79,13 @@ public void initiate(AjaxRequestTarget aTarget) aTarget.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);"); } + public void initiate(AjaxRequestTarget aTarget, String aFilename, IResourceStream aStream) + { + filename = Model.of(aFilename); + data = Model.of(aStream); + initiate(aTarget); + } + @Override public void onRequest() { diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/SanitizingHtmlLabel.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/SanitizingHtmlLabel.java new file mode 100644 index 00000000000..f6cb8e9ecef --- /dev/null +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/SanitizingHtmlLabel.java @@ -0,0 +1,60 @@ +/* + * 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.support.wicket; + +import java.io.Serializable; + +import org.apache.wicket.markup.ComponentTag; +import org.apache.wicket.markup.MarkupStream; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.model.IModel; + +public class SanitizingHtmlLabel + extends Label +{ + private static final long serialVersionUID = 7376235338667827923L; + + public SanitizingHtmlLabel(String aId, IModel aModel) + { + super(aId, aModel); + } + + public SanitizingHtmlLabel(String aId, Serializable aLabel) + { + super(aId, aLabel); + } + + public SanitizingHtmlLabel(String aId) + { + super(aId); + } + + @Override + protected void onInitialize() + { + super.onInitialize(); + setEscapeModelStrings(false); // SAFE - SPECIAL HTML-SANITIZING COMPONENT + } + + @Override + public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) + { + var htmlString = getDefaultModelObjectAsString(); + replaceComponentTagBody(markupStream, openTag, htmlString); + } +} diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/WicketUtil.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/WicketUtil.java index 70d94a02c97..a7c6c316479 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/WicketUtil.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/wicket/WicketUtil.java @@ -49,6 +49,14 @@ private WicketUtil() // No instances } + public static void ajaxFallbackScript(IHeaderResponse aResponse, String aScript) + { + RequestCycle.get().find(AjaxRequestTarget.class).ifPresentOrElse( + target -> target.appendJavaScript(aScript), + () -> aResponse.render(OnDomReadyHeaderItem.forScript(aScript))); + + } + /** * Focus a component either via the current AJAX request or by adding a focus script as a header * item. @@ -62,10 +70,7 @@ public static void ajaxFallbackFocus(IHeaderResponse aResponse, Component aCompo { var script = "setTimeout(() => document.getElementById('" + aComponent.getMarkupId() + "')?.focus(), 100)"; - RequestCycle.get().find(AjaxRequestTarget.class).ifPresentOrElse( - target -> target.appendJavaScript(script), - () -> aResponse.render(OnDomReadyHeaderItem.forScript(script))); - + ajaxFallbackScript(aResponse, script); } public static Optional getPage() diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/NamspaceDecodingContentHandlerAdapter.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/NamspaceDecodingContentHandlerAdapter.java new file mode 100644 index 00000000000..6ba27760af1 --- /dev/null +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/NamspaceDecodingContentHandlerAdapter.java @@ -0,0 +1,179 @@ +/* + * 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.support.xml; + +import static org.apache.commons.lang3.StringUtils.startsWith; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Stack; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; + +import org.apache.commons.lang3.StringUtils; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +public abstract class NamspaceDecodingContentHandlerAdapter + extends ContentHandlerAdapter +{ + private static final String XMLNS = "xmlns"; + private static final String XMLNS_PREFIX = "xmlns:"; + + private final Stack stack; + + private final Map namespaceMappings = new LinkedHashMap<>(); + + public NamspaceDecodingContentHandlerAdapter(ContentHandler aDelegate) + { + super(aDelegate); + stack = new Stack<>(); + namespaceMappings.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI); + } + + @Override + public void startDocument() throws SAXException + { + stack.clear(); + namespaceMappings.clear(); + namespaceMappings.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI); + super.startDocument(); + } + + @Override + public void startPrefixMapping(String aPrefix, String aUri) throws SAXException + { + namespaceMappings.put(aPrefix, aUri); + + super.startPrefixMapping(aPrefix, aUri); + } + + @Override + public void endPrefixMapping(String aPrefix) throws SAXException + { + namespaceMappings.remove(aPrefix); + + super.endPrefixMapping(aPrefix); + } + + @Override + public void startElement(String aUri, String aLocalName, String aQName, Attributes aAtts) + throws SAXException + { + var localNamespaces = new LinkedHashMap(); + for (var nsDecl : prefixMappings(aAtts).entrySet()) { + var oldValue = namespaceMappings.put(nsDecl.getKey(), nsDecl.getValue()); + if (oldValue == null) { + localNamespaces.put(nsDecl.getKey(), nsDecl.getValue()); + } + } + + var element = toQName(aUri, aLocalName, aQName); + + stack.push(new Frame(element, localNamespaces)); + + super.startElement(aUri, aLocalName, aQName, aAtts); + } + + private Map prefixMappings(Attributes aAtts) + { + var mappings = new LinkedHashMap(); + if (aAtts != null) { + for (int i = 0; i < aAtts.getLength(); i++) { + String qName = aAtts.getQName(i); + if (XMLNS.equals(qName)) { + mappings.put("", aAtts.getValue(i)); + } + if (startsWith(qName, XMLNS_PREFIX)) { + mappings.put(qName.substring(XMLNS_PREFIX.length()), aAtts.getValue(i)); + } + } + } + return mappings; + } + + @Override + public void endElement(String aUri, String aLocalName, String aQName) throws SAXException + { + var frame = stack.pop(); + super.endElement(frame.element.getNamespaceURI(), frame.element.getLocalPart(), aQName); + frame.namespaces.keySet().forEach(namespaceMappings::remove); + } + + private static final class Frame + { + final QName element; + final Map namespaces; + + public Frame(QName aElement, Map aLocalNamespaces) + { + element = aElement; + + if (aLocalNamespaces != null && !aLocalNamespaces.isEmpty()) { + namespaces = aLocalNamespaces; + } + else { + namespaces = Collections.emptyMap(); + } + } + + @Override + public String toString() + { + return "[" + element.getLocalPart() + "]"; + } + } + + protected QName toQName(String aUri, String aLocalName, String aQName) + { + String prefix = XMLConstants.DEFAULT_NS_PREFIX; + String localName = aLocalName; + + // Workaround bug: localname may contain prefix + if (localName != null) { + var li = localName.indexOf(':'); + if (li >= 0) { + localName = localName.substring(li + 1); + } + } + + var qi = aQName.indexOf(':'); + if (qi >= 0) { + prefix = aQName.substring(0, qi); + } + + if (StringUtils.isEmpty(localName)) { + if (qi >= 0) { + localName = aQName.substring(qi + 1, aQName.length()); + } + else { + localName = aQName; + } + } + + String uri = aUri; + if (StringUtils.isEmpty(uri)) { + uri = namespaceMappings.getOrDefault(prefix, XMLConstants.NULL_NS_URI); + } + + return new QName(uri, localName, prefix); + } +} diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/ElementAction.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/ElementAction.java index 57f76b8e72c..7fc4ac25777 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/ElementAction.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/ElementAction.java @@ -39,5 +39,4 @@ public enum ElementAction * marked to pass. */ PRUNE; - } diff --git a/inception/inception-support/src/test/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/SanitizingContentHandlerTest.java b/inception/inception-support/src/test/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/SanitizingContentHandlerTest.java index 678ee50da5e..fb1fd363e48 100644 --- a/inception/inception-support/src/test/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/SanitizingContentHandlerTest.java +++ b/inception/inception-support/src/test/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/SanitizingContentHandlerTest.java @@ -202,10 +202,10 @@ void thatAttributesCanBeDroppedSelectively(String aName, PolicyCollectionBuilder void thatDisallowedAttributesAreDroppped(String aName, PolicyCollectionBuilder aBuilder) throws Exception { - QName root = new QName("root"); - QName child = new QName("child"); - QName attr1 = new QName("attr1"); - QName attr2 = new QName("attr2"); + var root = new QName("root"); + var child = new QName("child"); + var attr1 = new QName("attr1"); + var attr2 = new QName("attr2"); var buffer = new StringWriter(); var policy = aBuilder // @@ -258,9 +258,9 @@ void thatPrunedBranchesAreDropped(String aName, PolicyCollectionBuilder aBuilder @Test void thatCaseInsensitiveModeWorks() throws Exception { - QName root = new QName("root"); - QName child = new QName("child"); - QName attr1 = new QName("attr1"); + var root = new QName("root"); + var child = new QName("child"); + var attr1 = new QName("attr1"); var buffer = new StringWriter(); var policy = PolicyCollectionBuilder.caseInsensitive() // @@ -285,9 +285,9 @@ void thatCaseInsensitiveModeWorks() throws Exception @Test void thatCaseSensitiveModeWorks() throws Exception { - QName root = new QName("ROOT"); - QName child = new QName("child"); - QName attr1 = new QName("attr1"); + var root = new QName("ROOT"); + var child = new QName("child"); + var attr1 = new QName("attr1"); var buffer = new StringWriter(); var policy = PolicyCollectionBuilder.caseSensitive() // @@ -314,7 +314,7 @@ void thatCaseSensitiveModeWorks() throws Exception @Test void thatSanitizingDefaultXmlParserWorks() throws Exception { - QName root = new QName("http://namespace.org", "ROOT"); + var root = new QName("http://namespace.org", "ROOT"); var buffer = new StringWriter(); var policy = PolicyCollectionBuilder.caseSensitive() // @@ -323,7 +323,7 @@ void thatSanitizingDefaultXmlParserWorks() throws Exception var sut = new SanitizingContentHandler(makeXmlSerializer(buffer), policy); - String xml = ""; + var xml = ""; var parser = newSaxParser(); parser.parse(toInputStream(xml, UTF_8), new DefaultHandlerToContentHandlerAdapter<>(sut)); @@ -334,7 +334,7 @@ void thatSanitizingDefaultXmlParserWorks() throws Exception @Test void thatNamespaceDeclarationsPass() throws Exception { - QName root = new QName("http://namespace.org", "ROOT"); + var root = new QName("http://namespace.org", "ROOT"); var buffer = new StringWriter(); var policy = PolicyCollectionBuilder.caseSensitive() // @@ -343,7 +343,7 @@ void thatNamespaceDeclarationsPass() throws Exception var sut = new SanitizingContentHandler(makeXmlSerializer(buffer), policy); - String xml = ""; + var xml = ""; var parser = newSaxParser(); parser.parse(toInputStream(xml, UTF_8), new DefaultHandlerToContentHandlerAdapter<>(sut)); diff --git a/inception/inception-telemetry/pom.xml b/inception/inception-telemetry/pom.xml index 16f4a2b3539..4622829ae3f 100644 --- a/inception/inception-telemetry/pom.xml +++ b/inception/inception-telemetry/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-telemetry INCEpTION - Telemetry diff --git a/inception/inception-test-dependencies/pom.xml b/inception/inception-test-dependencies/pom.xml index e756aa752cb..9461fe17fcc 100644 --- a/inception/inception-test-dependencies/pom.xml +++ b/inception/inception-test-dependencies/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception - 31.0-SNAPSHOT + 32.0-SNAPSHOT ../.. diff --git a/inception/inception-testing/pom.xml b/inception/inception-testing/pom.xml index 2fc6b0c7d0e..f6bba4a0863 100644 --- a/inception/inception-testing/pom.xml +++ b/inception/inception-testing/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-testing INCEpTION - Test Support library diff --git a/inception/inception-tutorial/pom.xml b/inception/inception-tutorial/pom.xml index ef4b9fc7a52..2a443e28c22 100644 --- a/inception/inception-tutorial/pom.xml +++ b/inception/inception-tutorial/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-tutorial INCEpTION - UI - Tutorial diff --git a/inception/inception-ui-agreement/pom.xml b/inception/inception-ui-agreement/pom.xml index ccc8a28d9f7..fb9065eb434 100644 --- a/inception/inception-ui-agreement/pom.xml +++ b/inception/inception-ui-agreement/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-ui-agreement INCEpTION - UI - Agreement @@ -41,16 +41,24 @@ de.tudarmstadt.ukp.inception.app - inception-agreement + inception-annotation-storage de.tudarmstadt.ukp.inception.app inception-annotation-storage-api + + de.tudarmstadt.ukp.inception.app + inception-agreement + de.tudarmstadt.ukp.inception.app inception-schema-api + + de.tudarmstadt.ukp.inception.app + inception-scheduling + de.tudarmstadt.ukp.inception.app inception-ui-core @@ -85,8 +93,6 @@ spring-context - - org.apache.uima uimaj-core @@ -108,6 +114,10 @@ org.apache.wicket wicket-core + + org.apache.wicket + wicket-util + org.apache.wicket wicket-request @@ -116,6 +126,10 @@ org.apache.wicket wicket-spring + + org.wicketstuff + wicketstuff-annotationeventdispatcher + org.wicketstuff wicketstuff-input-events diff --git a/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.html b/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.html index 12d04398136..55ad9150734 100644 --- a/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.html +++ b/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.html @@ -20,8 +20,8 @@
    -
    - +
    + diff --git a/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.java b/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.java index 50cadc969cd..15b9aa15180 100644 --- a/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.java +++ b/inception/inception-ui-agreement/src/main/java/de/tudarmstadt/ukp/inception/ui/agreement/page/AgreementPage.java @@ -19,26 +19,31 @@ import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasAccessMode.SHARED_READ_ONLY_ACCESS; import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasUpgradeMode.AUTO_CAS_UPGRADE; -import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState.FINISHED; import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.ANNOTATOR; import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.CURATOR; import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.MANAGER; import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.NS_PROJECT; import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.PAGE_PARAM_PROJECT; import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.enabledWhen; - +import static de.tudarmstadt.ukp.inception.support.logging.Logging.KEY_PROJECT_ID; +import static de.tudarmstadt.ukp.inception.support.logging.Logging.KEY_REPOSITORY_PATH; +import static de.tudarmstadt.ukp.inception.support.logging.Logging.KEY_USERNAME; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.groupingBy; + +import java.io.IOException; +import java.io.OutputStream; import java.io.Serializable; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Optional; -import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.uima.cas.CAS; -import org.apache.uima.cas.FeatureStructure; import org.apache.uima.fit.util.FSUtil; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -57,13 +62,21 @@ import org.apache.wicket.spring.injection.annot.SpringBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.wicketstuff.annotation.mount.MountPath; +import org.wicketstuff.event.annotation.OnEvent; +import de.tudarmstadt.ukp.clarin.webanno.agreement.AgreementUtils; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasure; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasureSupport; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.AgreementMeasureSupportRegistry; import de.tudarmstadt.ukp.clarin.webanno.agreement.measures.DefaultAgreementTraits; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.CodingAgreementMeasure_ImplBase; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.FullCodingAgreementResult; +import de.tudarmstadt.ukp.clarin.webanno.agreement.results.coding.event.PairwiseAgreementScoreClickedEvent; +import de.tudarmstadt.ukp.clarin.webanno.agreement.task.CalculatePairwiseAgreementTask; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocument; +import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; @@ -71,15 +84,23 @@ import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase; import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token; +import de.tudarmstadt.ukp.inception.annotation.storage.CasStorageSession; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; +import de.tudarmstadt.ukp.inception.documents.api.RepositoryProperties; import de.tudarmstadt.ukp.inception.project.api.ProjectService; +import de.tudarmstadt.ukp.inception.scheduling.SchedulingService; +import de.tudarmstadt.ukp.inception.scheduling.TaskScope; +import de.tudarmstadt.ukp.inception.scheduling.TaskState; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; import de.tudarmstadt.ukp.inception.support.WebAnnoConst; import de.tudarmstadt.ukp.inception.support.help.DocLink; +import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxBehavior; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxButton; import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxFormComponentUpdatingBehavior; import de.tudarmstadt.ukp.inception.support.lambda.LambdaChoiceRenderer; import de.tudarmstadt.ukp.inception.support.uima.WebAnnoCasUtil; +import de.tudarmstadt.ukp.inception.support.wicket.AjaxDownloadBehavior; +import de.tudarmstadt.ukp.inception.support.wicket.PipedStreamResource; @MountPath(NS_PROJECT + "/${" + PAGE_PARAM_PROJECT + "}/agreement") public class AgreementPage @@ -87,7 +108,7 @@ public class AgreementPage { private static final long serialVersionUID = 5333662917247971912L; - private static final Logger LOG = LoggerFactory.getLogger(AgreementPage.class); + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String MID_TRAITS_CONTAINER = "traitsContainer"; private static final String MID_TRAITS = "traits"; @@ -98,9 +119,13 @@ public class AgreementPage private @SpringBean AnnotationSchemaService annotationService; private @SpringBean UserDao userRepository; private @SpringBean AgreementMeasureSupportRegistry agreementRegistry; + private @SpringBean SchedulingService schedulingService; + private @SpringBean RepositoryProperties repositoryProperties; private AgreementForm agreementForm; private WebMarkupContainer resultsContainer; + private LambdaAjaxBehavior refreshResultsBehavior; + private AjaxDownloadBehavior downloadBehavior; public AgreementPage(final PageParameters aPageParameters) { @@ -112,7 +137,7 @@ protected void onInitialize() { super.onInitialize(); - User user = userRepository.getCurrentUser(); + var user = userRepository.getCurrentUser(); requireProjectRole(user, MANAGER, CURATOR); @@ -121,6 +146,47 @@ protected void onInitialize() add(resultsContainer = new WebMarkupContainer("resultsContainer")); resultsContainer.setOutputMarkupPlaceholderTag(true); resultsContainer.add(new EmptyPanel(MID_RESULTS)); + + downloadBehavior = new AjaxDownloadBehavior(); + add(downloadBehavior); + + // add(new TaskMonitorPanel("runningProcesses") // + // .setPopupMode(false) // + // .setTypePattern(CalculatePairwiseAgreementTask.TYPE) // + // .setKeepRemovedTasks(true)); + // + // refreshResultsBehavior = new LambdaAjaxBehavior(this::actionRefreshResults); + // add(refreshResultsBehavior); + } + + private CalculatePairwiseAgreementTask getCurrentTask() + { + var maybeTask = schedulingService.findTask(t -> t instanceof CalculatePairwiseAgreementTask + && t.getUser().map(userRepository.getCurationUser()::equals).orElse(false) + && getProject().equals(t.getProject())); + + if (maybeTask.isEmpty()) { + return null; + } + + return (CalculatePairwiseAgreementTask) maybeTask.get(); + + } + + private void actionRefreshResults(AjaxRequestTarget aTarget, + CalculatePairwiseAgreementTask aTask) + { + // var task = getCurrentTask(); + var task = aTask; + if (task != null && task.getMonitor().getState() == TaskState.COMPLETED) { + AgreementMeasureSupport ams = agreementRegistry.getAgreementMeasureSupport( + agreementForm.measureDropDown.getModelObject().getKey()); + + var result = task.getResult(); + var resultsPanel = ams.createResultsPanel(MID_RESULTS, Model.of(result)); + resultsContainer.addOrReplace(resultsPanel); + aTarget.add(resultsContainer); + } } private class AgreementForm @@ -201,9 +267,10 @@ private void actionSelectFeature(AjaxRequestTarget aTarget) // If the currently selected measure is not compatible with the selected feature, then // we clear the measure selection. AnnotationFeature selectedFeature = featureList.getModelObject(); - boolean measureCompatibleWithFeature = measureDropDown.getModel() - .map(k -> agreementRegistry.getAgreementMeasureSupport(k.getKey())) - .map(s -> selectedFeature != null && s.accepts(selectedFeature)).orElse(false) + boolean measureCompatibleWithFeature = measureDropDown.getModel() // + .map(k -> agreementRegistry.getAgreementMeasureSupport(k.getKey())) // + .map(s -> selectedFeature != null && s.accepts(selectedFeature)) // + .orElse(false) // .getObject(); if (!measureCompatibleWithFeature) { measureDropDown.setModelObject(null); @@ -215,34 +282,59 @@ private void actionSelectFeature(AjaxRequestTarget aTarget) @SuppressWarnings({ "rawtypes", "unchecked" }) private void actionRunCalculations(AjaxRequestTarget aTarget, Form aForm) { - AnnotationFeature feature = featureList.getModelObject(); - Pair measureHandle = measureDropDown.getModelObject(); + var feature = featureList.getModelObject(); + var measureHandle = measureDropDown.getModelObject(); // Do not do any agreement if no feature or measure has been selected yet. if (feature == null || measureHandle == null) { return; } + var traits = (DefaultAgreementTraits) agreementForm.traitsContainer.get(MID_TRAITS) + .getDefaultModelObject(); + AgreementMeasureSupport ams = agreementRegistry .getAgreementMeasureSupport(measureDropDown.getModelObject().getKey()); - AgreementMeasure measure = ams.createMeasure(feature, - (DefaultAgreementTraits) traitsContainer.get(MID_TRAITS) - .getDefaultModelObject()); + var measure = ams.createMeasure(feature, traits); + + var states = new ArrayList(); + states.add(AnnotationDocumentState.FINISHED); + if (!traits.isLimitToFinishedDocuments()) { + states.add(AnnotationDocumentState.IN_PROGRESS); + } - Map> casMap = getCasMap(); + var allAnnDocs = documentService.listAnnotationDocumentsInState(getProject(), // + states.toArray(AnnotationDocumentState[]::new)).stream() // + .collect(groupingBy(AnnotationDocument::getDocument)); - if (casMap.values().stream().allMatch(list -> list == null || list.isEmpty())) { + if (allAnnDocs.isEmpty()) { error("No documents with annotations were found."); aTarget.addChildren(getPage(), IFeedback.class); - } - else { - Serializable result = measure.getAgreement(casMap); - resultsContainer.addOrReplace(ams.createResultsPanel(MID_RESULTS, Model.of(result), - AgreementPage.this::getCasMap)); - aTarget.add(resultsContainer); + return; } + var aAnnotators = projectService.listProjectUsersWithPermissions(getProject(), + ANNOTATOR); + + var task = CalculatePairwiseAgreementTask.builder() // + .withSessionOwner(userRepository.getCurrentUser()) // + .withProject(getProject()) // + .withTrigger("Agreement page") // + .withAnnotators(aAnnotators) // + .withTraits(traits) // + .withFeature(feature) // + .withMeasure(measure) // + .withDocuments(allAnnDocs) // + .withScope(TaskScope.LAST_USER_SESSION) // + // When running sync, we cannot cancel because the browser will still be in an + // AJAX request when we try to fire a second one and that second one will fail + // then. This would only work if the cancel action would be sent through + // WebSocket + .withCancellable(false).build(); + + schedulingService.executeSync(task); + actionRefreshResults(aTarget, task); } List> listMeasures() @@ -252,15 +344,15 @@ List> listMeasures() } return agreementRegistry.getAgreementMeasureSupports(getModelObject().feature).stream() - .map(s -> Pair.of(s.getId(), s.getName())).collect(Collectors.toList()); + .map(s -> Pair.of(s.getId(), s.getName())) // + .toList(); } private List getEligibleFeatures() { - List features = annotationService - .listAnnotationFeature(getProject()); - List unusedFeatures = new ArrayList<>(); - for (AnnotationFeature feature : features) { + var features = annotationService.listAnnotationFeature(getProject()); + var unusedFeatures = new ArrayList(); + for (var feature : features) { if (feature.getLayer().getName().equals(Token.class.getName()) || feature.getLayer().getName().equals(WebAnnoConst.COREFERENCE_LAYER)) { unusedFeatures.add(feature); @@ -271,103 +363,243 @@ private List getEligibleFeatures() } } - static class AgreementFormModel - implements Serializable + @OnEvent + public void onPairwiseAgreementScoreClicked(PairwiseAgreementScoreClickedEvent aEvent) { - private static final long serialVersionUID = -1L; + // Copy the relevant information from the event to avoid having to pass the event into the + // lambda which would cause problems here since the event is not serializable + var annotator1 = aEvent.getAnnotator1(); + var annotator2 = aEvent.getAnnotator2(); + var currentUser = userRepository.getCurrentUser(); + + var measure = createSelectedMeasure(getTraits()); + if (!(measure instanceof CodingAgreementMeasure_ImplBase)) { + aEvent.getTarget().addChildren(getPage(), IFeedback.class); + warn("Aggreement report currently only supported for coding measures."); + return; + } - AnnotationFeature feature; + downloadBehavior.initiate(aEvent.getTarget(), "agreement.csv", + new PipedStreamResource((os) -> { + // PipedStreamResource runs the lambda in a separate thread, so we need to make + // sure the MDC is correctly set up here. + try (var ctx = new DefaultMdcSetup(repositoryProperties, getProject(), + currentUser)) { + exportAgreement(os, annotator1, annotator2, currentUser); + } + })); + } - Pair measure; + private void exportAgreement(OutputStream aOut, String aAnnotator1, String aAnnotator2, + User aCurrentUser) + { + var traits = getTraits(); + var measure = createSelectedMeasure(traits); + + var states = new ArrayList(); + states.add(AnnotationDocumentState.FINISHED); + if (!traits.isLimitToFinishedDocuments()) { + states.add(AnnotationDocumentState.IN_PROGRESS); + } + + var allAnnDocs = documentService.listAnnotationDocumentsInState(getProject(), // + states.toArray(AnnotationDocumentState[]::new)).stream() // + .collect(groupingBy(AnnotationDocument::getDocument)); + + var docs = allAnnDocs.keySet().stream() // + .sorted(comparing(SourceDocument::getName)) // + .toList(); + var countWritten = 0; + for (var doc : docs) { + try (var session = CasStorageSession.openNested()) { + var maybeCas1 = loadCas(doc, aAnnotator1, allAnnDocs); + var maybeCas2 = loadCas(doc, aAnnotator1, allAnnDocs); + var cas1 = maybeCas1.isPresent() ? maybeCas1.get() : loadInitialCas(doc); + var cas2 = maybeCas2.isPresent() ? maybeCas2.get() : loadInitialCas(doc); + + var casMap = new LinkedHashMap(); + casMap.put(aAnnotator1, cas1); + casMap.put(aAnnotator2, cas2); + var res = measure.getAgreement(casMap); + AgreementUtils.generateCsvReport(aOut, (FullCodingAgreementResult) res, + countWritten == 0); + countWritten++; + } + catch (Exception e) { + LOG.error("Unable to load data", e); + } + } } - // The CASes cannot be serialized, so we make them transient here. However, it does not matter - // as we do not access the field directly but via getCases() which will re-load them if - // necessary, e.g. if the transient field is empty after a session is restored from a - // persisted state. - private transient Map> cachedCASes; - private transient Project cachedProject; - private transient boolean cachedLimitToFinishedDocuments; + private DefaultAgreementTraits getTraits() + { + return (DefaultAgreementTraits) agreementForm.traitsContainer.get(MID_TRAITS) + .getDefaultModelObject(); + } - public Map> getCasMap() + private AgreementMeasure createSelectedMeasure(DefaultAgreementTraits traits) { - if (agreementForm.featureList.getModelObject() == null) { - return Collections.emptyMap(); - } + AgreementMeasureSupport ams = agreementRegistry.getAgreementMeasureSupport( + agreementForm.measureDropDown.getModelObject().getKey()); - Project project = agreementForm.featureList.getModelObject().getProject(); + var measure = ams.createMeasure(agreementForm.featureList.getModelObject(), traits); + return measure; + } - DefaultAgreementTraits traits = (DefaultAgreementTraits) agreementForm.traitsContainer - .get(MID_TRAITS).getDefaultModelObject(); + private CAS loadInitialCas(SourceDocument aDocument) throws IOException + { + var cas = documentService.createOrReadInitialCas(aDocument, AUTO_CAS_UPGRADE, + SHARED_READ_ONLY_ACCESS); - // Avoid reloading the CASes when switching features within the same project - if (cachedCASes != null && project.equals(cachedProject) - && cachedLimitToFinishedDocuments == traits.isLimitToFinishedDocuments()) { - return cachedCASes; - } + // Set the CAS name in the DocumentMetaData so that we can pick it + // up in the Diff position for the purpose of debugging / transparency. + var dmd = WebAnnoCasUtil.getDocumentMetadata(cas); + FSUtil.setFeature(dmd, "documentId", aDocument.getName()); + FSUtil.setFeature(dmd, "collectionId", aDocument.getProject().getName()); - List users = projectService.listProjectUsersWithPermissions(project, ANNOTATOR); + return cas; + } - List sourceDocuments = documentService.listSourceDocuments(project); + private Optional loadCas(SourceDocument aDocument, String aDataOwner, + Map> aAllAnnDocs) + throws IOException + { + var annDocs = aAllAnnDocs.get(aDocument); - cachedCASes = new LinkedHashMap<>(); - for (User user : users) { - List cases = new ArrayList<>(); + if (annDocs.stream().noneMatch(annDoc -> aDataOwner.equals(annDoc.getUser()))) { + return Optional.empty(); + } - // Bulk-fetch all source documents for which there is already an annotation document for - // the user which is faster then checking for their existence individually - List docsForUser = documentService - .listAnnotationDocuments(project, user).stream() - .map(AnnotationDocument::getDocument).distinct().collect(Collectors.toList()); + if (!documentService.existsCas(aDocument, aDataOwner)) { + Optional.empty(); + } - nextDocument: for (SourceDocument document : sourceDocuments) { - CAS cas = null; + var cas = documentService.readAnnotationCas(aDocument, aDataOwner, AUTO_CAS_UPGRADE, + SHARED_READ_ONLY_ACCESS); - try { - if (docsForUser.contains(document)) { - AnnotationDocument annotationDocument = documentService - .getAnnotationDocument(document, user); + // Set the CAS name in the DocumentMetaData so that we can pick it + // up in the Diff position for the purpose of debugging / transparency. + var dmd = WebAnnoCasUtil.getDocumentMetadata(cas); + FSUtil.setFeature(dmd, "documentId", aDocument.getName()); + FSUtil.setFeature(dmd, "collectionId", aDocument.getProject().getName()); - if (traits.isLimitToFinishedDocuments() - && !annotationDocument.getState().equals(FINISHED)) { - // Add a skip marker (null) for the current CAS to the CAS list - this - // is necessary because we expect the CAS lists for all users to have - // the same size - cases.add(null); - continue nextDocument; - } - } + return Optional.of(cas); + } - // Reads the user's annotation document or the initial source document - - // depending on what is available - cas = documentService.readAnnotationCas(document, user.getUsername(), - AUTO_CAS_UPGRADE, SHARED_READ_ONLY_ACCESS); - } - catch (Exception e) { - error("Unable to load data: " + ExceptionUtils.getRootCauseMessage(e)); - LOG.error("Unable to load data", e); - } + static class AgreementFormModel + implements Serializable + { + private static final long serialVersionUID = -1L; - if (cas != null) { - // Set the CAS name in the DocumentMetaData so that we can pick it - // up in the Diff position for the purpose of debugging / transparency. - FeatureStructure dmd = WebAnnoCasUtil.getDocumentMetadata(cas); - FSUtil.setFeature(dmd, "documentId", document.getName()); - FSUtil.setFeature(dmd, "collectionId", document.getProject().getName()); + AnnotationFeature feature; - } + Pair measure; + } + + // FIXME: Would be good to move this somewhere else to make it properly reusable + static class DefaultMdcSetup + implements AutoCloseable + { - // The next line can enter null values into the list if a user didn't work on this - // source document yet. - cases.add(cas); + public DefaultMdcSetup(RepositoryProperties repositoryProperties, Project aProject, + User aUser) + { + // We are in a new thread. Set up thread-specific MDC + if (repositoryProperties != null) { + MDC.put(KEY_REPOSITORY_PATH, repositoryProperties.getPath().toString()); } - cachedCASes.put(user.getUsername(), cases); - } + if (aUser != null) { + MDC.put(KEY_USERNAME, aUser.getUsername()); + } - cachedProject = project; - cachedLimitToFinishedDocuments = traits.isLimitToFinishedDocuments(); + if (aProject != null) { + MDC.put(KEY_PROJECT_ID, String.valueOf(aProject.getId())); + } + } - return cachedCASes; + @Override + public void close() + { + MDC.remove(KEY_REPOSITORY_PATH); + MDC.remove(KEY_USERNAME); + MDC.remove(KEY_PROJECT_ID); + } } + + // private final DropDownChoice formatField; + + // add(formatField = new DropDownChoice("exportFormat", + // Model.of(CSV), asList(AgreementReportExportFormat.values()), + // new EnumChoiceRenderer<>(this))); + // formatField.add(new LambdaAjaxFormComponentUpdatingBehavior("change")); + // + + // private Behavior makeDownloadBehavior(final String aKey1, final String aKey2) + // { + // return new AjaxEventBehavior("click") + // { + // private static final long serialVersionUID = 1L; + // + // @Override + // protected void onEvent(AjaxRequestTarget aTarget) + // { + // var download = new AjaxDownloadBehavior( + // LoadableDetachableModel.of(PairwiseCodingAgreementTable.this::getFilename), + // LoadableDetachableModel.of(() -> getAgreementTableData(aKey1, aKey2))); + // getComponent().add(download); + // download.initiate(aTarget); + // } + // }; + // } + // + // private AbstractResourceStream getAgreementTableData(final String aKey1, final String aKey2) + // { + // return new AbstractResourceStream() + // { + // private static final long serialVersionUID = 1L; + // + // @Override + // public InputStream getInputStream() throws ResourceStreamNotFoundException + // { + // try { + // var result = PairwiseCodingAgreementTable.this.getModelObject().getStudy(aKey1, + // aKey2); + // + // switch (formatField.getModelObject()) { + // case CSV: + // return AgreementUtils.generateCsvReport(result); + // case DEBUG: + // return generateDebugReport(result); + // default: + // throw new IllegalStateException( + // "Unknown export format [" + formatField.getModelObject() + "]"); + // } + // } + // catch (Exception e) { + // // FIXME Is there some better error handling here? + // LOG.error("Unable to generate agreement report", e); + // throw new ResourceStreamNotFoundException(e); + // } + // } + // + // @Override + // public void close() throws IOException + // { + // // Nothing to do + // } + // }; + // } + // + // private String getFilename() + // { + // return "agreement" + formatField.getModelObject().getExtension(); + // } + // + // private InputStream generateDebugReport(FullCodingAgreementResult aResult) + // { + // var buf = new ByteArrayOutputStream(); + // AgreementUtils.dumpAgreementStudy(new PrintStream(buf), aResult); + // return new ByteArrayInputStream(buf.toByteArray()); + // } } diff --git a/inception/inception-ui-agreement/src/main/resources/META-INF/asciidoc/user-guide/agreement.adoc b/inception/inception-ui-agreement/src/main/resources/META-INF/asciidoc/user-guide/agreement.adoc index 18449a1a401..176a6a80bee 100644 --- a/inception/inception-ui-agreement/src/main/resources/META-INF/asciidoc/user-guide/agreement.adoc +++ b/inception/inception-ui-agreement/src/main/resources/META-INF/asciidoc/user-guide/agreement.adoc @@ -17,7 +17,7 @@ [[sect_monitoring_agreement]] = Agreement -NOTE: This functionality is only available to *curators* and *managers*. +NOTE: This functionality is only available to *curators* and *managers*. Agreement can only be calculated for span and relation layers. The set of available agreement measures depends on the layer configuration. This page allows you to calculate inter-annotator agreement between users. Agreement can be inspected on a per-feature basis and is calculated pair-wise between all annotators across all documents. @@ -62,11 +62,12 @@ Several agreement measures are supported. units coded with the same categories by a single annotator may not overlap with each other. |==== + == Coding vs. Unitizing Coding measures are based on positions. I.e. two annotations are either at the same position or not. If they are, they can be compared - otherwise they cannot be compared. This makes coding measures -unsuitable in cases where partital overlap of annotations needs to be considered, e.g. in the case +unsuitable in cases where partial overlap of annotations needs to be considered, e.g. in the case of named entity annotations where it is common that annotators do not agree on the boundaries of the entity. In order to calculate the positions, all documents are scanned for annotations and annotations located at the same positions are collected in configuration sets. To determine if two annotations are at the same position, different approaches are used depending on the layer type. For a span layer, the begin and end offsets are used. For a relation layer, the begin and end offsets of the source and target annotation are used. Chains are currently not supported. @@ -102,27 +103,27 @@ labels) or even a unitizing measure which is able to produce partial agreement s |==== | Feature value annotator 1 | Feature value annotator 2 | Agreement | Complete -| `X` +| `X` | `X` | yes | yes -| `X` +| `X` | `Y` | no | yes -| *no annotation* +| *no annotation* | `Y` | no | no -| *empty* +| *empty* | `Y` | no | yes -| *empty* +| *empty* | *empty* | yes | yes @@ -132,7 +133,7 @@ labels) or even a unitizing measure which is able to produce partial agreement s | yes | yes -| *empty* +| *empty* | *no annotation* | no | no @@ -148,6 +149,11 @@ calculation! This also includes relations for which source or targets spans are [[sect_agreement_matrix]] == Pairwise agreement matrix +To calculate the pairwise agreement, the measure is applied pairs of documents, each document containing annotations from +one annotator. If an annotator has not yet annotated a document, the original state of the document after the import +is considered. To calculate the overall agreement between two annotators over all documents, the average of the +per-document agreements is used. + The lower part of the agreement matrix displays how many configuration sets were used to calculate agreement and how many were found in total. The upper part of the agreement matrix displays the pairwise agreement scores. diff --git a/inception/inception-ui-annotation/pom.xml b/inception/inception-ui-annotation/pom.xml index fae63b5c941..212ca9b8475 100644 --- a/inception/inception-ui-annotation/pom.xml +++ b/inception/inception-ui-annotation/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-ui-annotation INCEpTION - UI - Annotation diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.html b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.html index 1708b8020bb..fc1450569c0 100755 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.html +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.html @@ -23,7 +23,7 @@
    -
    +
    diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java index 73cfd197675..601ba3d925e 100755 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPage.java @@ -20,6 +20,7 @@ import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationEditorState.KEY_EDITOR_STATE; import static de.tudarmstadt.ukp.clarin.webanno.api.annotation.page.AnnotationPageBase.PAGE_PARAM_DOCUMENT; import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasUpgradeMode.FORCE_CAS_UPGRADE; +import static de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasUpgradeMode.NO_CAS_UPGRADE; import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentState.IGNORE; import static de.tudarmstadt.ukp.clarin.webanno.model.AnnotationDocumentStateChangeFlag.EXPLICIT_ANNOTATOR_USER_ACTION; import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.ANNOTATOR; @@ -390,7 +391,7 @@ public CAS getEditorCas() throws IOException documentService .verifyAnnotationCasTimestamp(state.getDocument(), state.getUser().getUsername(), - state.getAnnotationDocumentTimestamp().get(), "reading") + state.getAnnotationDocumentTimestamp().get(), "reading the editor CAS") .ifPresent(state::setAnnotationDocumentTimestamp); } @@ -457,12 +458,13 @@ protected void actionLoadDocument(AjaxRequestTarget aTarget, int aFocus) .createOrGetAnnotationDocument(state.getDocument(), state.getUser()); var stateBeforeOpening = annotationDocument.getState(); + var editable = isEditable(); + // Read the CAS // Update the annotation document CAS var editorCas = documentService.readAnnotationCas(annotationDocument, - FORCE_CAS_UPGRADE); + editable ? FORCE_CAS_UPGRADE : NO_CAS_UPGRADE); - var editable = isEditable(); applicationEventPublisherHolder.get() .publishEvent(new BeforeDocumentOpenedEvent(this, editorCas, getModelObject().getDocument(), diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPageMenuItem.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPageMenuItem.java index 316a06bfb18..a0b0ecc068b 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPageMenuItem.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/AnnotationPageMenuItem.java @@ -29,7 +29,6 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType; import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; -import de.tudarmstadt.ukp.clarin.webanno.security.model.User; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.config.AnnotationUIAutoConfiguration; import de.tudarmstadt.ukp.clarin.webanno.ui.core.menu.ProjectMenuItem; import de.tudarmstadt.ukp.inception.project.api.ProjectService; @@ -102,8 +101,8 @@ public boolean applies(Project aProject) } // Visible if the current user is an annotator - User user = userRepo.getCurrentUser(); - return projectService.hasRole(user, aProject, ANNOTATOR, CURATOR); + var sessionOwner = userRepo.getCurrentUser(); + return projectService.hasRole(sessionOwner, aProject, ANNOTATOR, CURATOR); } @Override diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/AnnotationDocumentOpenActionColumn.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/AnnotationDocumentOpenActionColumn.java index e73c8c4ed94..32cff44a5c2 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/AnnotationDocumentOpenActionColumn.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/AnnotationDocumentOpenActionColumn.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.clarin.webanno.ui.annotation.actionbar.open; +import static de.tudarmstadt.ukp.inception.support.lambda.HtmlElementEvents.CLICK; import static org.apache.wicket.event.Broadcast.BUBBLE; import org.apache.wicket.AttributeModifier; @@ -58,7 +59,7 @@ public void populateItem(Item> aItem, String var fragment = new Fragment(aComponentId, FID_OPEN_DOCUMENT_COLUMN, fragmentProvider); fragment.queue(new Label("name", aRowModel.map(AnnotationDocument::getName))); aItem.add(AttributeModifier.replace("role", "button")); - aItem.add(AjaxEventBehavior.onEvent("click", + aItem.add(AjaxEventBehavior.onEvent(CLICK, _target -> actionOpenDocument(_target, aItem, aRowModel.getObject()))); aItem.add(fragment); } diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/OpenDocumentDialogPanel.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/OpenDocumentDialogPanel.java index 878d08765b9..dc54cd40493 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/OpenDocumentDialogPanel.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/actionbar/open/OpenDocumentDialogPanel.java @@ -100,8 +100,8 @@ public OpenDocumentDialogPanel(String aId, IModel aState, private DropDownChoice> createUserListChoice() { - DecoratedObject currentUser = DecoratedObject.of(userRepository.getCurrentUser()); - DecoratedObject viewUser = DecoratedObject.of(state.getObject().getUser()); + var currentUser = DecoratedObject.of(userRepository.getCurrentUser()); + var viewUser = DecoratedObject.of(state.getObject().getUser()); var choice = new DropDownChoice<>("user", Model.of(), listUsers()); choice.setChoiceRenderer(new ChoiceRenderer>() @@ -111,8 +111,8 @@ private DropDownChoice> createUserListChoice() @Override public Object getDisplayValue(DecoratedObject aUser) { - User user = aUser.get(); - String username = defaultIfEmpty(aUser.getLabel(), user.getUiName()); + var user = aUser.get(); + var username = defaultIfEmpty(aUser.getLabel(), user.getUiName()); if (user.equals(currentUser.get())) { username += " (me)"; } @@ -149,21 +149,22 @@ private void actionSelectUser(AjaxRequestTarget aTarget) private List> listUsers() { - List> users = new ArrayList<>(); + var users = new ArrayList>(); + + var project = state.getObject().getProject(); + var currentUser = userRepository.getCurrentUser(); - Project project = state.getObject().getProject(); - User currentUser = userRepository.getCurrentUser(); // cannot select other user than themselves if curating or not admin if (state.getObject().getMode().equals(Mode.CURATION) || !projectService.hasRole(currentUser, project, MANAGER, CURATOR)) { - DecoratedObject du = DecoratedObject.of(currentUser); + var du = DecoratedObject.of(currentUser); du.setLabel(currentUser.getUiName()); users.add(du); return users; } - for (User user : projectService.listProjectUsersWithPermissions(project, ANNOTATOR)) { - DecoratedObject du = DecoratedObject.of(user); + for (var user : projectService.listProjectUsersWithPermissions(project, ANNOTATOR)) { + var du = DecoratedObject.of(user); du.setLabel(user.getUiName()); if (user.equals(currentUser)) { users.add(0, du); @@ -178,8 +179,8 @@ private List> listUsers() private List listDocuments() { - Project project = state.getObject().getProject(); - User user = userListChoice.getModel().map(DecoratedObject::get).orElse(null).getObject(); + var project = state.getObject().getProject(); + var user = userListChoice.getModel().map(DecoratedObject::get).orElse(null).getObject(); if (project == null || user == null) { return new ArrayList<>(); @@ -216,7 +217,7 @@ private void actionCancel(AjaxRequestTarget aTarget) // location. if (state.getObject().getProject() == null || state.getObject().getDocument() == null) { try { - ProjectPageBase ppb = findParent(ProjectPageBase.class); + var ppb = findParent(ProjectPageBase.class); if (ppb != null) { ((ProjectPageBase) ppb).backToProjectPage(); } diff --git a/inception/inception-ui-core/pom.xml b/inception/inception-ui-core/pom.xml index 5b49e81790d..09aed1c97b2 100644 --- a/inception/inception-ui-core/pom.xml +++ b/inception/inception-ui-core/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-ui-core INCEpTION - UI - Core diff --git a/inception/inception-ui-curation/pom.xml b/inception/inception-ui-curation/pom.xml index 5afc0ed2b03..0faaad12a12 100644 --- a/inception/inception-ui-curation/pom.xml +++ b/inception/inception-ui-curation/pom.xml @@ -20,12 +20,16 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-ui-curation INCEpTION - UI - Curation jar + + javax.persistence + javax.persistence-api + org.apache.commons commons-lang3 diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/component/AnnotatorsPanel.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/component/AnnotatorsPanel.java index 83674e0c723..df5da78f273 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/component/AnnotatorsPanel.java +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/component/AnnotatorsPanel.java @@ -17,7 +17,7 @@ */ package de.tudarmstadt.ukp.clarin.webanno.ui.curation.component; -import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiffSingle; +import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.doDiff; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.getDiffAdapters; import static de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.LinkCompareBehavior.LINK_ROLE_AS_LABEL; import static de.tudarmstadt.ukp.clarin.webanno.model.SourceDocumentState.CURATION_FINISHED; @@ -68,8 +68,6 @@ import de.tudarmstadt.ukp.clarin.webanno.brat.schema.BratSchemaGenerator; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.Configuration; import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.ConfigurationSet; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiff.DiffResult; -import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.api.DiffAdapter; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; import de.tudarmstadt.ukp.clarin.webanno.model.Project; @@ -457,9 +455,9 @@ private Map getCasses(SourceDocument aDocument) throws IOException private Map> calculateAnnotationStates(AnnotatorState aState, Map aCasses) { - List adapters = getDiffAdapters(schemaService, aState.getAnnotationLayers()); - DiffResult diff = doDiffSingle(adapters, LINK_ROLE_AS_LABEL, aCasses, - aState.getWindowBeginOffset(), aState.getWindowEndOffset()).toResult(); + var adapters = getDiffAdapters(schemaService, aState.getAnnotationLayers()); + var diff = doDiff(adapters, LINK_ROLE_AS_LABEL, aCasses, aState.getWindowBeginOffset(), + aState.getWindowEndOffset()).toResult(); var differingSets = diff.getDifferingConfigurationSetsWithExceptions(CURATION_USER) .values(); @@ -467,12 +465,12 @@ private Map> calculateAnnotationStates(Annotat .values(); differingSets.removeAll(incompleteSets); - List completeAgreementSets = new ArrayList<>(); + var completeAgreementSets = new ArrayList(); completeAgreementSets.addAll(diff.getConfigurationSets()); completeAgreementSets.removeAll(differingSets); completeAgreementSets.removeAll(incompleteSets); - Map> annoStates = new HashMap<>(); + var annoStates = new HashMap>(); for (String casGroupId : aCasses.keySet()) { annoStates.put(casGroupId, new HashMap<>()); } diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnit.java b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnit.java index d6e4b32b333..54b06b5b6f6 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnit.java +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/overview/CurationUnit.java @@ -19,9 +19,11 @@ import java.io.Serializable; +import de.tudarmstadt.ukp.clarin.webanno.curation.casdiff.CasDiffSummaryState; + /** * A model comprises of Curation Segments comprising of the begin and end of the unit, - * {@link CurationUnitState} unit number + * {@link CasDiffSummaryState} unit number */ public class CurationUnit implements Serializable @@ -33,7 +35,7 @@ public class CurationUnit private final int end; private final int unitIndex; - private CurationUnitState state; + private CasDiffSummaryState state; private boolean isCurrentUnit; @@ -54,12 +56,12 @@ public Integer getEnd() return end; } - public CurationUnitState getState() + public CasDiffSummaryState getState() { return state; } - public void setState(CurationUnitState sentenceState) + public void setState(CasDiffSummaryState sentenceState) { this.state = sentenceState; } diff --git a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/page/CurationPage.html b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/page/CurationPage.html index 053d20a3068..9ca5aed83d7 100644 --- a/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/page/CurationPage.html +++ b/inception/inception-ui-curation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/curation/page/CurationPage.html @@ -106,12 +106,12 @@
    + + + + + + diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/SparqlPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/SparqlPanel.java new file mode 100644 index 00000000000..995e6475814 --- /dev/null +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/SparqlPanel.java @@ -0,0 +1,115 @@ +/* + * 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.ui.kb; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.ajax.markup.html.modal.ModalDialog; +import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackHeadersToolbar; +import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxNavigationToolbar; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.LambdaColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.TextArea; +import org.apache.wicket.markup.html.panel.GenericPanel; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.spring.injection.annot.SpringBean; + +import de.tudarmstadt.ukp.inception.kb.KnowledgeBaseService; +import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; +import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink; +import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxSubmitLink; + +public class SparqlPanel + extends GenericPanel +{ + private static final long serialVersionUID = -8435410869046010021L; + + private @SpringBean KnowledgeBaseService knowledgeBaseService; + + private WebMarkupContainer resultsContainer; + private DataTable, Void> results; + + public SparqlPanel(String aId, IModel aModel) + { + super(aId, aModel); + + queue(new Form("form", new CompoundPropertyModel<>(new QueryModel()))); + queue(new TextArea<>("query")); + queue(new LambdaAjaxSubmitLink("runQuery", this::actionQuery)); + queue(new LambdaAjaxLink("closeDialog", this::actionCancel)); + resultsContainer = new WebMarkupContainer("resultsContainer"); + resultsContainer.setOutputMarkupId(true); + queue(resultsContainer); + results = new DataTable<>("results", Collections.emptyList(), + new TupleQueryResultDataProvider(), 10); + queue(results); + } + + private void actionQuery(AjaxRequestTarget aTarget, Form aForm) + { + var model = aForm.getModelObject(); + + var columns = new ArrayList, Void>>(); + + var dataProvider = knowledgeBaseService.read(getModelObject(), conn -> { + var tupleQuery = conn.prepareTupleQuery(model.query); + + var tupleResult = tupleQuery.evaluate(); + + for (var bindingName : tupleResult.getBindingNames()) { + columns.add(new LambdaColumn<>(Model.of(bindingName), $ -> $.get(bindingName))); + } + + return new TupleQueryResultDataProvider(tupleResult); + }); + + var table = new DataTable<>("results", columns, dataProvider, 10); + table.setOutputMarkupId(true); + table.addTopToolbar(new AjaxNavigationToolbar(table)); + table.addTopToolbar(new AjaxFallbackHeadersToolbar(table, dataProvider)); + results = (DataTable, Void>) results.replaceWith(table); + + aTarget.add(resultsContainer); + } + + protected void actionCancel(AjaxRequestTarget aTarget) + { + findParent(ModalDialog.class).close(aTarget); + } + + private static class QueryModel + implements Serializable + { + private static final long serialVersionUID = -1407131006099282400L; + + String query = """ + SELECT ?s ?p ?o WHERE { + ?s ?p ?o . + } + """; + } +} diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/TupleQueryResultDataProvider.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/TupleQueryResultDataProvider.java new file mode 100644 index 00000000000..40546b5583c --- /dev/null +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/TupleQueryResultDataProvider.java @@ -0,0 +1,78 @@ +/* + * 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.ui.kb; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.eclipse.rdf4j.query.TupleQueryResult; + +public class TupleQueryResultDataProvider + extends SortableDataProvider, Void> + implements Serializable +{ + private static final long serialVersionUID = -4546564722894830886L; + + private List> data; + + public TupleQueryResultDataProvider() + { + data = Collections.emptyList(); + } + + public TupleQueryResultDataProvider(TupleQueryResult aTupleResult) + { + data = new ArrayList>(); + + for (var bindingSet : aTupleResult) { + var map = new LinkedHashMap(); + + for (var binding : bindingSet) { + map.put(binding.getName(), String.valueOf(binding.getValue())); + } + + data.add(map); + } + } + + @Override + public Iterator> iterator(long aFirst, long aCount) + { + return data.subList((int) aFirst, (int) (aFirst + aCount)).iterator(); + } + + @Override + public long size() + { + return data.size(); + } + + @Override + public IModel> model(Map aObject) + { + return Model.ofMap(aObject); + } +} diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.html b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.html index 37652ce444b..01fa6c779dc 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.html +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.html @@ -22,25 +22,32 @@
    + + + deprecated - - -
    -
    -
    - -
    - - -
    -
    - +
    +
      +
      +
      + +
      + + +
      +
    +
      +
      + +
      +
    diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.java index 0e3cdd58518..83582225658 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureEditor.java @@ -83,6 +83,7 @@ public class ConceptFeatureEditor private @SpringBean KnowledgeBaseService knowledgeBaseService; private AutoCompleteField focusComponent; + private WebMarkupContainer deprecationMarker; private WebMarkupContainer descriptionContainer; private Label description; private IriInfoBadge iriBadge; @@ -110,8 +111,12 @@ public ConceptFeatureEditor(String aId, MarkupContainer aItem, IModel isNotBlank(iriBadge.getModelObject()))); add(openIriLink); + deprecationMarker = new WebMarkupContainer("deprecationMarker"); + deprecationMarker.setOutputMarkupPlaceholderTag(true); + deprecationMarker.add(visibleWhen(this::isDeprecated)); + add(deprecationMarker); + descriptionContainer = new WebMarkupContainer("descriptionContainer"); - descriptionContainer.setOutputMarkupPlaceholderTag(true); descriptionContainer.add(visibleWhen( () -> getLabelComponent().isVisible() && getModelObject().getValue() != null)); add(descriptionContainer); @@ -162,12 +167,22 @@ private void actionOpenBrowseDialog(AjaxRequestTarget aTarget) dialog.open(content, aTarget); } - private String descriptionValue() + private boolean isDeprecated() { return getModel().map(FeatureState::getValue)// .map(value -> (KBHandle) value)// - .map(KBHandle::getDescription)// - .map(value -> StringUtils.abbreviate(value, 130))// + .map(KBHandle::isDeprecated) // + .orElse(false)// + .getObject(); + + } + + private String descriptionValue() + { + return getModel().map(FeatureState::getValue) // + .map(value -> (KBHandle) value) // + .map(KBHandle::getDescription) // + .map(value -> StringUtils.abbreviate(value, 1000)) // .orElse("no description")// .getObject(); } @@ -184,7 +199,7 @@ private String iriTooltipValue() @OnEvent public void onFeatureEditorValueChanged(FeatureEditorValueChangedEvent aEvent) { - aEvent.getTarget().add(descriptionContainer, iriBadge, openIriLink); + aEvent.getTarget().add(this); } @OnEvent @@ -203,7 +218,6 @@ private void selectItem(AjaxRequestTarget aTarget, KBObject aKBObject) { getModelObject().value = aKBObject; dialog.close(aTarget); - aTarget.add(focusComponent, descriptionContainer, iriBadge, openIriLink); send(focusComponent, BUBBLE, new FeatureEditorValueChangedEvent(ConceptFeatureEditor.this, aTarget)); } diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureSupport.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureSupport.java index 52ee199ab06..800e842c9ab 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureSupport.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/ConceptFeatureSupport.java @@ -96,8 +96,15 @@ public Optional getFeatureType(AnnotationFeature aFeature) return Optional.empty(); } - return Optional.of(new FeatureType(aFeature.getType(), - aFeature.getType().substring(PREFIX.length()), featureSupportId)); + var traits = readTraits(aFeature); + traits.getAllowedValueType(); + + var uiName = "KB: " + traits.getAllowedValueType(); + if (!TYPE_ANY_OBJECT.equals(aFeature.getType())) { + uiName += " (" + aFeature.getType().substring(PREFIX.length()) + ")"; + } + + return Optional.of(new FeatureType(aFeature.getType(), uiName, featureSupportId)); } @Override @@ -168,7 +175,8 @@ public KBHandle wrapFeatureValue(AnnotationFeature aFeature, CAS aCAS, Object aV String identifier = (String) aValue; ConceptFeatureTraits traits = readTraits(aFeature); KBHandle chbk = getConceptHandle(aFeature, identifier, traits); - var clone = new KBHandle(identifier, chbk.getUiLabel(), chbk.getDescription()); + // Clone the cached original so we can override the KB + var clone = new KBHandle(chbk); clone.setKB(chbk.getKB()); return clone; } diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/KBHandleTemplate.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/KBHandleTemplate.java index a5376359325..5772238a808 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/KBHandleTemplate.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/feature/KBHandleTemplate.java @@ -34,10 +34,14 @@ final class KBHandleTemplate @Override public String getText() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); sb.append(""); // We cannot use && here because that causes an XML parse error in the browser - so we nest // the if clauses... + sb.append(" # if (data.deprecated != 'false') { #"); + sb.append( + " deprecated"); + sb.append(" # } #"); sb.append(" # if (data.rank) { if (data.rank != '0') { #"); sb.append(" [${ data.rank }]"); sb.append(" # } } #"); @@ -65,10 +69,11 @@ public String getText() @Override public List getTextProperties() { - List properties = new ArrayList<>(); + var properties = new ArrayList(); properties.add("identifier"); properties.add("description"); properties.add("rank"); + properties.add("deprecated"); properties.add("queryBestMatchTerm"); if (DEVELOPMENT.equals(Application.get().getConfigurationType())) { properties.add("debugInfo"); diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.java index f4444804353..8daef06b15b 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.java @@ -21,8 +21,12 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Optional; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.request.resource.ResourceReference; import org.springframework.beans.factory.annotation.Autowired; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; @@ -34,6 +38,7 @@ import de.tudarmstadt.ukp.dkpro.core.api.ner.type.NamedEntity; import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; +import de.tudarmstadt.ukp.inception.support.wicket.resource.Strings; import de.tudarmstadt.ukp.inception.ui.kb.config.KnowledgeBaseServiceUIAutoConfiguration; import jakarta.persistence.NoResultException; @@ -47,6 +52,9 @@ public class NamedEntityIdentifierFeatureInitializer implements LayerInitializer { + private static final PackageResourceReference THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "NamedEntityIdentifierFeatureInitializer.svg"); + private final AnnotationSchemaService annotationSchemaService; @Autowired @@ -61,6 +69,18 @@ public String getName() return "Named entity linking"; } + @Override + public Optional getDescription() + { + return Optional.of(Strings.getString("entity-linking-layer.description")); + } + + @Override + public Optional getThumbnail() + { + return Optional.of(THUMBNAIL); + } + @Override public List> getDependencies() { diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.svg b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.svg new file mode 100644 index 00000000000..a677a798a9b --- /dev/null +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/initializers/NamedEntityIdentifierFeatureInitializer.svg @@ -0,0 +1,1241 @@ + + + + + +PersonPERMr. Smith diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseDetailsPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseDetailsPanel.java index c877366ee2a..77a99497ed9 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseDetailsPanel.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseDetailsPanel.java @@ -23,11 +23,8 @@ import static java.util.Collections.emptyMap; import static org.apache.commons.lang3.exception.ExceptionUtils.getRootCauseMessage; -import java.io.File; import java.io.FileInputStream; -import java.io.InputStream; -import org.apache.commons.lang3.tuple.Pair; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.feedback.IFeedback; import org.apache.wicket.markup.html.form.Form; @@ -147,7 +144,7 @@ private void actionSave(AjaxRequestTarget aTarget, Form aF aTarget.add(this); try { - KnowledgeBaseWrapper kbw = kbwModel.getObject(); + var kbw = kbwModel.getObject(); // if dealing with a remote repository and a non-empty URL, get a new // RepositoryImplConfig for the new URL; otherwise keep using the existing config @@ -167,8 +164,8 @@ private void actionSave(AjaxRequestTarget aTarget, Form aF if (kb.getType() == LOCAL) { kbService.defineBaseProperties(kb); - for (Pair f : kbw.getFiles()) { - try (InputStream is = new FileInputStream(f.getValue())) { + for (var f : kbw.getFiles()) { + try (var is = new FileInputStream(f.getValue())) { kbService.importData(kb, f.getValue().getName(), is); success("Imported: " + f.getKey()); } diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.html b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.html index 2d6c82b200f..66d5a2e22a7 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.html +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.html @@ -39,11 +39,23 @@
    -
    +
    - +
    + + +
    +
    +
    + +
    + + +
    diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.java index 9556dccdb71..36808f4590d 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseInfoPanel.java @@ -19,6 +19,7 @@ import static de.tudarmstadt.ukp.inception.support.lambda.LambdaBehavior.visibleWhen; +import org.apache.commons.lang3.StringUtils; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.html.panel.Panel; @@ -36,13 +37,26 @@ public KnowledgeBaseInfoPanel(String aId, CompoundPropertyModel aModel.getObject() != null))); - queue(new Label("hostInstitutionName", aModel.bind("hostInstitutionName")) - .add(visibleWhen(() -> aModel.getObject() != null))); - queue(new Label("authorName", aModel.bind("authorName")) - .add(visibleWhen(() -> aModel.getObject() != null))); - queue(new ExternalLink("websiteURL", aModel.bind("websiteURL"), aModel.bind("websiteURL")) - .add(visibleWhen(() -> aModel.getObject() != null))); + var description = aModel.map(KnowledgeBaseInfo::getDescription); + queue(new MarkdownLabel("description", description) + .add(visibleWhen(description.map(StringUtils::isNotBlank)))); + + var hostInstitutionName = aModel.map(KnowledgeBaseInfo::getHostInstitutionName); + queue(new Label("hostInstitutionName", hostInstitutionName) + .add(visibleWhen(hostInstitutionName.map(StringUtils::isNotBlank)))); + + var authorName = aModel.map(KnowledgeBaseInfo::getAuthorName); + queue(new Label("authorName", authorName) + .add(visibleWhen(authorName.map(StringUtils::isNotBlank)))); + + var websiteURL = aModel.map(KnowledgeBaseInfo::getWebsiteUrl); + queue(new ExternalLink("websiteUrl", websiteURL, websiteURL) + .add(visibleWhen(websiteURL.map(StringUtils::isNotBlank)))); + + var licenseUrl = aModel.map(KnowledgeBaseInfo::getLicenseUrl); + var licenseName = aModel.map(KnowledgeBaseInfo::getLicenseName) + .orElseGet(licenseUrl::getObject); + queue(new ExternalLink("licenseUrl", licenseUrl, licenseName) + .add(visibleWhen(licenseUrl.map(StringUtils::isNotBlank)))); } } diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.html b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.html index ba79ed4f637..203b9cb63f5 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.html +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.html @@ -51,6 +51,7 @@
    @@ -60,6 +61,7 @@
    @@ -69,6 +71,7 @@
    @@ -78,19 +81,21 @@
    - +
    - +
    @@ -101,6 +106,7 @@
    @@ -110,6 +116,7 @@
    @@ -119,6 +126,7 @@
    @@ -128,12 +136,23 @@
    + +
    + +
    + +
    +
    diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.java index 05401664333..2d862591485 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/KnowledgeBaseIriPanel.java @@ -26,7 +26,6 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; -import java.util.Arrays; import java.util.List; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -65,44 +64,45 @@ public KnowledgeBaseIriPanel(String id, CompoundPropertyModel reificationChoice = selectReificationStrategy("reification", - "kb.reification"); + var reificationChoice = selectReificationStrategy("reification", "kb.reification"); add(reificationChoice); // The Kendo comboboxes do not redraw properly when added directly to an // AjaxRequestTarget (for each combobox, a text field and a dropdown will be shown). // Instead, wrap all of them in a WMC and redraw that. - WebMarkupContainer comboBoxWrapper = new WebMarkupContainer("comboBoxWrapper"); + var comboBoxWrapper = new WebMarkupContainer("comboBoxWrapper"); comboBoxWrapper.setOutputMarkupId(true); add(comboBoxWrapper); // Add comboboxes for classIri, subclassIri, typeIri and descriptionIri - ComboBox classField = buildComboBox("classIri", kbModel.bind("kb.classIri"), + var classField = buildComboBox("classIri", kbModel.bind("kb.classIri"), IriConstants.CLASS_IRIS); - ComboBox subclassField = buildComboBox("subclassIri", - kbModel.bind("kb.subclassIri"), IriConstants.SUBCLASS_IRIS); - ComboBox typeField = buildComboBox("typeIri", kbModel.bind("kb.typeIri"), + var subclassField = buildComboBox("subclassIri", kbModel.bind("kb.subclassIri"), + IriConstants.SUBCLASS_IRIS); + var typeField = buildComboBox("typeIri", kbModel.bind("kb.typeIri"), IriConstants.TYPE_IRIS); - ComboBox subPropertyField = buildComboBox("subPropertyIri", - kbModel.bind("kb.subPropertyIri"), IriConstants.SUBPROPERTY_IRIS); - ComboBox descriptionField = buildComboBox("descriptionIri", - kbModel.bind("kb.descriptionIri"), IriConstants.DESCRIPTION_IRIS); - ComboBox labelField = buildComboBox("labelIri", kbModel.bind("kb.labelIri"), + var subPropertyField = buildComboBox("subPropertyIri", kbModel.bind("kb.subPropertyIri"), + IriConstants.SUBPROPERTY_IRIS); + var descriptionField = buildComboBox("descriptionIri", kbModel.bind("kb.descriptionIri"), + IriConstants.DESCRIPTION_IRIS); + var labelField = buildComboBox("labelIri", kbModel.bind("kb.labelIri"), IriConstants.LABEL_IRIS); - ComboBox propertyTypeField = buildComboBox("propertyTypeIri", - kbModel.bind("kb.propertyTypeIri"), IriConstants.PROPERTY_TYPE_IRIS); - ComboBox propertyLabelField = buildComboBox("propertyLabelIri", + var propertyTypeField = buildComboBox("propertyTypeIri", kbModel.bind("kb.propertyTypeIri"), + IriConstants.PROPERTY_TYPE_IRIS); + var propertyLabelField = buildComboBox("propertyLabelIri", kbModel.bind("kb.propertyLabelIri"), IriConstants.PROPERTY_LABEL_IRIS); - ComboBox propertyDescriptionField = buildComboBox("propertyDescriptionIri", + var propertyDescriptionField = buildComboBox("propertyDescriptionIri", kbModel.bind("kb.propertyDescriptionIri"), IriConstants.PROPERTY_DESCRIPTION_IRIS); + var deprecationPropertyField = buildComboBox("deprecationPropertyIri", + kbModel.bind("kb.deprecationPropertyIri"), IriConstants.DEPRECATION_PROPERTY_IRIS); + comboBoxWrapper.add(classField, subclassField, typeField, subPropertyField, descriptionField, labelField, propertyTypeField, propertyLabelField, - propertyDescriptionField); + propertyDescriptionField, deprecationPropertyField); // RadioGroup to select the IriSchemaType - DropDownChoice iriSchemaChoice = new DropDownChoice( - "iriSchema", selectedSchemaProfile, Arrays.asList(SchemaProfile.values()), - new EnumChoiceRenderer<>(this)) + var iriSchemaChoice = new DropDownChoice("iriSchema", selectedSchemaProfile, + asList(SchemaProfile.values()), new EnumChoiceRenderer<>(this)) { private static final long serialVersionUID = 3863260896285332033L; @@ -133,6 +133,7 @@ protected void onInitialize() propertyTypeField.setModelObject(profile.getPropertyTypeIri()); propertyLabelField.setModelObject(profile.getPropertyLabelIri()); propertyDescriptionField.setModelObject(profile.getPropertyDescriptionIri()); + deprecationPropertyField.setModelObject(profile.getDeprecationPropertyIri()); } _target.add(comboBoxWrapper, iriSchemaChoice, reificationChoice); })); diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/ProjectKnowledgeBasePanel.properties b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/ProjectKnowledgeBasePanel.properties index c15a4c1508b..227eafa0ee6 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/ProjectKnowledgeBasePanel.properties +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/ProjectKnowledgeBasePanel.properties @@ -36,6 +36,7 @@ kb.iri.labelIri=Label IRI kb.iri.propertyTypeIri=Property type IRI kb.iri.propertyLabelIri=Property label IRI kb.iri.propertyDescriptionIri=Property description IRI +kb.iri.deprecationPropertyIri=Deprecation property IRI kb.iri.fullTextSearchIri=Full text search mode fullTextSearchIri.nullValid=No full text search support kb.iri.basePrefix=Base Prefix @@ -45,7 +46,7 @@ kb.language=Language kb.supportConceptLinking=Supports Concept Linking kb.queryLimit=Result Limit for SPARQL queries -kb.local.fileupload.supported.list=Supported: RDF (XML/JSON/Binary), JSON-LD, N-Triples, N-Quads, TriG, TriX, Turtle (optionally gzipped) +kb.local.fileupload.supported.list=Supported: RDF (XML/JSON/Binary), OWL (XML, Functional Syntax), OBO, JSON-LD, N-Triples, N-Quads, TriG, TriX, Turtle (optionally gzipped) kb.wizard.steps.type.language=Language kb.wizard.steps.type.name=Name @@ -60,6 +61,7 @@ kb.wizard.steps.accessSpecific.description=Description kb.wizard.steps.accessSpecific.hostInstitutionName=Host Institution kb.wizard.steps.accessSpecific.authorName=Author(s) kb.wizard.steps.accessSpecific.homepage=Homepage +kb.wizard.steps.accessSpecific.license=License kb.wizard.title=New Knowledge Base kb.settings.general = General Settings @@ -102,7 +104,7 @@ kb.export.nt=N-Triples RepositoryType.LOCAL=Local RepositoryType.REMOTE=Remote (SPARQL) -kb.reification = Reification +kb.reification = Reification SchemaProfile.RDFSCHEMA=RDF SchemaProfile.OWLSCHEMA=OWL diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/local/LocalRepositorySettingsPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/local/LocalRepositorySettingsPanel.java index f5a71e4b135..d3da3cdd8d0 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/local/LocalRepositorySettingsPanel.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/local/LocalRepositorySettingsPanel.java @@ -389,25 +389,28 @@ private void actionDownloadKbAndSetIRIs(AjaxRequestTarget aTarget) { try { if (selectedKnowledgeBaseProfile != null) { - String accessUrl = selectedKnowledgeBaseProfile.getAccess().getAccessUrl(); + var accessUrl = selectedKnowledgeBaseProfile.getAccess().getAccessUrl(); - FileUploadDownloadHelper fileUploadDownloadHelper = new FileUploadDownloadHelper( - getApplication()); + var fileUploadDownloadHelper = new FileUploadDownloadHelper(getApplication()); - if (!accessUrl.startsWith(CLASSPATH_PREFIX)) { - File tmpFile = fileUploadDownloadHelper - .writeFileDownloadToTemporaryFile(accessUrl, getModel()); - getModel().getObject().putFile(selectedKnowledgeBaseProfile.getName(), tmpFile); + if (accessUrl == null) { + // Nothing to do } - else { + else if (accessUrl.startsWith(CLASSPATH_PREFIX)) { // import from classpath - File kbFile = fileUploadDownloadHelper + var kbFile = fileUploadDownloadHelper .writeClasspathResourceToTemporaryFile(accessUrl, getModel()); getModel().getObject().putFile(selectedKnowledgeBaseProfile.getName(), kbFile); } + else { + var tmpFile = fileUploadDownloadHelper + .writeFileDownloadToTemporaryFile(accessUrl, getModel()); + getModel().getObject().putFile(selectedKnowledgeBaseProfile.getName(), tmpFile); + } - KnowledgeBase kb = getModel().getObject().getKb(); + var kb = getModel().getObject().getKb(); kb.applyRootConcepts(selectedKnowledgeBaseProfile); + kb.applyAdditionalMatchingProperties(selectedKnowledgeBaseProfile); kb.applyMapping(selectedKnowledgeBaseProfile.getMapping()); kb.setFullTextSearchIri( selectedKnowledgeBaseProfile.getAccess().getFullTextSearchIri()); @@ -429,11 +432,14 @@ private void actionDownloadKbAndSetIRIs(AjaxRequestTarget aTarget) private String getAccessTypeLabel(KnowledgeBaseProfile aProfile) { + if (aProfile.getAccess().getAccessUrl() == null) { + return "MANUAL"; + } + if (aProfile.getAccess().getAccessUrl().startsWith(CLASSPATH_PREFIX)) { return "CLASSPATH"; } - else { - return "DOWNLOAD"; - } + + return "DOWNLOAD"; } } diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/remote/RemoteRepositorySettingsPanel.java b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/remote/RemoteRepositorySettingsPanel.java index d4f93f7f2fa..1344038f758 100644 --- a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/remote/RemoteRepositorySettingsPanel.java +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/project/remote/RemoteRepositorySettingsPanel.java @@ -42,7 +42,6 @@ import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; -import de.tudarmstadt.ukp.inception.kb.model.KnowledgeBase; import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseInfo; import de.tudarmstadt.ukp.inception.kb.yaml.KnowledgeBaseProfile; import de.tudarmstadt.ukp.inception.security.client.auth.AuthenticationTraitsEditor; @@ -219,11 +218,11 @@ protected void populateItem(ListItem item) private void actionPopulate(AjaxRequestTarget aTarget, KnowledgeBaseProfile aProfile) { // set all the fields according to the chosen profile - KnowledgeBaseWrapper kbw = getModel().getObject(); + var kbw = getModel().getObject(); kbw.setUrl(aProfile.getAccess().getAccessUrl()); // sets root concepts list - if null then an empty list otherwise change the // values to IRI and populate the list - KnowledgeBase kb = kbw.getKb(); + var kb = kbw.getKb(); kb.applyRootConcepts(aProfile); kb.applyAdditionalMatchingProperties(aProfile); kb.applyMapping(aProfile.getMapping()); diff --git a/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/wicket-package.properties b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/wicket-package.properties new file mode 100644 index 00000000000..3364e1513ea --- /dev/null +++ b/inception/inception-ui-kb/src/main/java/de/tudarmstadt/ukp/inception/ui/kb/wicket-package.properties @@ -0,0 +1,18 @@ +# Licensed to the Technische Universitt Darmstadt under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The Technische Universitt 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. +entity-linking-layer.description=Entity Linking, also known as Entity Disambiguation identifies entities such as \ + objects, ideas, individuals, locations and associates them with a reference dataset such as a terminology or \ + knowledge base. diff --git a/inception/inception-ui-kb/src/main/resources/META-INF/asciidoc/user-guide/projects_knowledge-base.adoc b/inception/inception-ui-kb/src/main/resources/META-INF/asciidoc/user-guide/projects_knowledge-base.adoc index dcbdd2aa6bc..2aeaf81a097 100644 --- a/inception/inception-ui-kb/src/main/resources/META-INF/asciidoc/user-guide/projects_knowledge-base.adoc +++ b/inception/inception-ui-kb/src/main/resources/META-INF/asciidoc/user-guide/projects_knowledge-base.adoc @@ -80,10 +80,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. [source,turtle,role="small"] ---- @prefix rdf: . -@prefix owl: . +@prefix owl: . - rdf:type owl:Class. + rdf:type owl:Class . ---- ==== @@ -97,10 +97,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. [source,turtle,role="small"] ---- @prefix rdf: . -@prefix rdfs: . +@prefix rdfs: . - rdfs:subClassOf . + rdfs:subClassOf . ---- ==== @@ -113,10 +113,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` is an instance of `http://my-bb/bar` [source,turtle,role="small"] ---- -@prefix rdf: . +@prefix rdf: . - rdf:type . + rdf:type . ---- ==== @@ -129,11 +129,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` has a name [source,turtle,role="small"] ---- -@prefix rdf: . -@prefix rdfs: . +@prefix rdfs: . - rdfs:label "Foo". + rdfs:label "Foo" . ---- ==== @@ -146,11 +145,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` has a description [source,turtle,role="small"] ---- -@prefix rdf: . -@prefix rdfs: . +@prefix rdfs: . - rdfs:comment "This entry describes a Foo". + rdfs:comment "This entry describes a Foo" . ---- ==== @@ -163,11 +161,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` is marked as being a property [source,turtle,role="small"] ---- -@prefix rdf: . -@prefix rdfs: . +@prefix rdf: . - rdf:type rdf:Property. + rdf:type rdf:Property . ---- ==== @@ -180,8 +177,7 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` is a sub-property of `http://my-bb/bar` [source,turtle,role="small"] ---- -@prefix rdf: . -@prefix rdfs: . +@prefix rdf: . rdf:subPropertyOf . @@ -197,11 +193,10 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` has a name [source,turtle,role="small"] ---- -@prefix rdf: . -@prefix rdfs: . +@prefix rdfs: . - rdfs:label "Foo". + rdfs:label "Foo" . ---- ==== @@ -214,11 +209,25 @@ user can choose one of the pre-configured mapping or provide a custom mapping. .`http://my-kb/foo` has a description [source,turtle,role="small"] ---- -@prefix rdf: . @prefix rdfs: . - rdfs:comment "This entry describes a Foo". + rdfs:comment "This entry describes a Foo" . +---- +==== +| Deprecation property IRI (property) +| Description of the property. The marked item is not deprecated if the property value is `false` or `0`. +| +`http://www.w3.org/2002/07/owl#deprecated` +[%collapsible] +==== +.`http://my-kb/foo` is deprecated +[source,turtle,role="small"] +---- +@prefix owl: . + + + owl:deprecated true . ---- ==== |==== @@ -235,7 +244,7 @@ NOTE: This setting currently affects **only class tree in the knowledge base bro subtree, use the **Scope** setting in the <>. -=== Additional Matching Properties +=== Additional Matching Properties (Synonyms) When searching for a concept e.g. in the annotation editor, by default the search terms are matched only against the concept name (label). There should only be one label for each concept (although there can be multiple label entries for a concept in the knowledge base, but theses @@ -254,6 +263,7 @@ Full text search in knowledge bases enables searching for entities by their text * `http://www.openrdf.org/contrib/lucenesail#matches`: use with local knowledge bases or possibly with remote knowledge bases using the link:https://rdf4j.org/documentation/programming/lucene/[RDF4J Lucene SAIL]. * `bif:contains`: use with remote link:https://virtuoso.openlinksw.com[Virtuoso SPARQL] endpoints. +* `http://www.bigdata.com/rdf/search#search`: use with remote link:https://blazegraph.com[Blazegraph] endpoints. * `text:query`: use with remote link:https://jena.apache.org/documentation/fuseki2/[Apache Jena Fuseki] SPARQL endpoints. * `tag:stardog:api:search:textMatch`: use with link:https://www.stardog.com[Stardog]. * `https://www.mediawiki.org/ontology#API/`: use with the link:https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service/queries[official Wikidata SPARQL endpoint]. @@ -317,15 +327,21 @@ KBs can be populated by importing RDF files. Several formats are supported. The |==== | Format | Extension -| RDF +| RDF (XML) | `.rdf` -| RDF Schema +| RDF Schema (XML) | `.rdfs` -| OWL +| OBO +| `.obo` + +| OWL (XML) | `.owl` +| OWL Functional Syntax +| `.ofn` + | N-Triples | `.nt` diff --git a/inception/inception-ui-kb/src/main/ts_template/package-lock.json b/inception/inception-ui-kb/src/main/ts_template/package-lock.json index f9aaec53401..870df0cc208 100644 --- a/inception/inception-ui-kb/src/main/ts_template/package-lock.json +++ b/inception/inception-ui-kb/src/main/ts_template/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "devDependencies": { "@inception-project/inception-support-bootstrap": "${semver}", - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "sass": "~1.68.0" } }, @@ -20,7 +20,7 @@ "dev": true, "license": "Apache-2.0", "devDependencies": { - "bootstrap": "5.3.2", + "bootstrap": "5.3.3", "cross-env": "^7.0.3", "esbuild": "~0.19.5", "esbuild-sass-plugin": "~2.16.0", @@ -28,7 +28,7 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.8", + "version": "0.19.12", "cpu": [ "arm64" ], @@ -73,7 +73,7 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/bootstrap": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "funding": [ { @@ -102,14 +102,8 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/chokidar": { - "version": "3.5.3", + "version": "3.6.0", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -123,6 +117,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -158,7 +155,7 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/esbuild": { - "version": "0.19.8", + "version": "0.19.12", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -169,32 +166,33 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.19.8", - "@esbuild/android-arm64": "0.19.8", - "@esbuild/android-x64": "0.19.8", - "@esbuild/darwin-arm64": "0.19.8", - "@esbuild/darwin-x64": "0.19.8", - "@esbuild/freebsd-arm64": "0.19.8", - "@esbuild/freebsd-x64": "0.19.8", - "@esbuild/linux-arm": "0.19.8", - "@esbuild/linux-arm64": "0.19.8", - "@esbuild/linux-ia32": "0.19.8", - "@esbuild/linux-loong64": "0.19.8", - "@esbuild/linux-mips64el": "0.19.8", - "@esbuild/linux-ppc64": "0.19.8", - "@esbuild/linux-riscv64": "0.19.8", - "@esbuild/linux-s390x": "0.19.8", - "@esbuild/linux-x64": "0.19.8", - "@esbuild/netbsd-x64": "0.19.8", - "@esbuild/openbsd-x64": "0.19.8", - "@esbuild/sunos-x64": "0.19.8", - "@esbuild/win32-arm64": "0.19.8", - "@esbuild/win32-ia32": "0.19.8", - "@esbuild/win32-x64": "0.19.8" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/esbuild-sass-plugin": { - "version": "2.16.0", + "version": "2.16.1", "dev": true, "license": "MIT", "dependencies": { @@ -248,7 +246,7 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "license": "MIT", "dependencies": { @@ -259,7 +257,7 @@ } }, "../../../../inception-support-bootstrap/src/main/ts/node_modules/immutable": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "license": "MIT" }, @@ -490,7 +488,7 @@ } }, "node_modules/bootstrap": { - "version": "5.3.2", + "version": "5.3.3", "dev": true, "funding": [ { @@ -519,14 +517,8 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", + "version": "3.6.0", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -540,6 +532,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -579,7 +574,7 @@ } }, "node_modules/immutable": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "license": "MIT" }, diff --git a/inception/inception-ui-project/pom.xml b/inception/inception-ui-project/pom.xml index 4f08e70bb0b..2e5445abc3c 100644 --- a/inception/inception-ui-project/pom.xml +++ b/inception/inception-ui-project/pom.xml @@ -20,7 +20,7 @@ de.tudarmstadt.ukp.inception.app inception-app - 31.0-SNAPSHOT + 32.0-SNAPSHOT inception-ui-project INCEpTION - UI - Project diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/FeatureDetailForm.java b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/FeatureDetailForm.java index 7f255178916..b417dffa087 100644 --- a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/FeatureDetailForm.java +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/FeatureDetailForm.java @@ -33,6 +33,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNumeric; +import java.io.IOException; + import org.apache.uima.cas.CAS; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -57,7 +59,6 @@ import de.tudarmstadt.ukp.clarin.webanno.api.casstorage.CasStorageService; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationFeature; import de.tudarmstadt.ukp.clarin.webanno.model.AnnotationLayer; -import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.bootstrap.dialog.ChallengeResponseDialog; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; @@ -173,7 +174,7 @@ protected void onModelChanged() else { featureType.setEnabled(false); featureType.setChoices( - () -> featureSupportRegistry.getAllTypes(getModelObject().getLayer())); + asList(featureSupportRegistry.getFeatureType(getModelObject()))); } })); featureType.add(new AjaxFormComponentUpdatingBehavior("change") @@ -235,23 +236,25 @@ private void actionDelete(AjaxRequestTarget aTarget, Form aForm { confirmationDialog.setMessageModel(new ResourceModel("DeleteFeatureDialog.text")); confirmationDialog.setExpectedResponseModel(getModel().map(AnnotationFeature::getName)); + confirmationDialog.setConfirmAction(this::actionDeleteConfirmed); confirmationDialog.show(aTarget); + } - confirmationDialog.setConfirmAction((_target) -> { - annotationService.removeFeature(getModelObject()); + private void actionDeleteConfirmed(AjaxRequestTarget aTarget) throws IOException + { + annotationService.removeFeature(getModelObject()); - Project project = getModelObject().getProject(); + var project = getModelObject().getProject(); - setModelObject(null); + setModelObject(null); - documentService.upgradeAllAnnotationDocuments(project); + documentService.upgradeAllAnnotationDocuments(project); - // Trigger LayerConfigurationChangedEvent - applicationEventPublisherHolder.get() - .publishEvent(new LayerConfigurationChangedEvent(this, project)); + // Trigger LayerConfigurationChangedEvent + applicationEventPublisherHolder.get() + .publishEvent(new LayerConfigurationChangedEvent(this, project)); - _target.add(getPage()); - }); + aTarget.add(getPage()); } private void actionSave(AjaxRequestTarget aTarget, Form aForm) diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectedEvent.java b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectedEvent.java new file mode 100644 index 00000000000..94e3d362207 --- /dev/null +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectedEvent.java @@ -0,0 +1,41 @@ +/* + * 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.clarin.webanno.ui.project.layers; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.wicketstuff.event.annotation.AbstractAjaxAwareEvent; + +import de.tudarmstadt.ukp.clarin.webanno.project.initializers.LayerInitializer; + +public class LayerTemplateSelectedEvent + extends AbstractAjaxAwareEvent +{ + private final LayerInitializer layerInitializer; + + public LayerTemplateSelectedEvent(AjaxRequestTarget aTarget, LayerInitializer aLayerInitializer) + { + super(aTarget); + + layerInitializer = aLayerInitializer; + } + + public LayerInitializer getLayerInitializer() + { + return layerInitializer; + } +} diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.html b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.html new file mode 100644 index 00000000000..2bcbf2a0cb4 --- /dev/null +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.html @@ -0,0 +1,65 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.java b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.java new file mode 100644 index 00000000000..f20aaf75b33 --- /dev/null +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/LayerTemplateSelectionDialogPanel.java @@ -0,0 +1,104 @@ +/* + * 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.clarin.webanno.ui.project.layers; + +import java.lang.invoke.MethodHandles; +import java.util.List; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.extensions.ajax.markup.html.modal.ModalDialog; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.image.Image; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.GenericPanel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.request.resource.PackageResourceReference; +import org.apache.wicket.spring.injection.annot.SpringBean; + +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.clarin.webanno.project.initializers.LayerInitializer; +import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; +import de.tudarmstadt.ukp.inception.project.api.ProjectInitializer; +import de.tudarmstadt.ukp.inception.project.api.ProjectService; +import de.tudarmstadt.ukp.inception.schema.api.config.AnnotationSchemaProperties; +import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink; +import de.tudarmstadt.ukp.inception.support.markdown.MarkdownLabel; +import de.tudarmstadt.ukp.inception.support.spring.ApplicationEventPublisherHolder; + +public class LayerTemplateSelectionDialogPanel + extends GenericPanel +{ + private static final long serialVersionUID = 2112018755924139726L; + + private static final PackageResourceReference NO_THUMBNAIL = new PackageResourceReference( + MethodHandles.lookup().lookupClass(), "no-thumbnail.svg"); + + private @SpringBean ProjectService projectService; + private @SpringBean UserDao userRepository; + private @SpringBean AnnotationSchemaProperties annotationEditorProperties; + private @SpringBean ApplicationEventPublisherHolder applicationEventPublisherHolder; + + private LambdaAjaxLink closeDialogButton; + + public LayerTemplateSelectionDialogPanel(String aId, IModel aProjectModel, + IModel> aInitializers) + { + super(aId, aProjectModel); + + var initializers = new ListView("templates", aInitializers) + { + private static final long serialVersionUID = 1L; + + @Override + protected void populateItem(ListItem aItem) + { + aItem.queue(new LambdaAjaxLink("create", + _target -> actionCreateLayer(_target, aItem.getModelObject()))); + aItem.queue(new Label("name", aItem.getModel().map(ProjectInitializer::getName))); + aItem.queue(new MarkdownLabel("description", + aItem.getModel().map(LayerInitializer::getDescription) + .map($ -> $.orElse("No description")))); + aItem.queue(new Image("thumbnail", aItem.getModel() + .map(LayerInitializer::getThumbnail).map($ -> $.orElse(NO_THUMBNAIL)))); + } + }; + queue(initializers); + + var container = new WebMarkupContainer("container"); + container.setOutputMarkupId(true); + queue(container); + + closeDialogButton = new LambdaAjaxLink("closeDialog", this::actionCancel); + closeDialogButton.setOutputMarkupId(true); + queue(closeDialogButton); + } + + private void actionCreateLayer(AjaxRequestTarget aTarget, LayerInitializer aInitializer) + { + send(this, Broadcast.BUBBLE, new LayerTemplateSelectedEvent(aTarget, aInitializer)); + findParent(ModalDialog.class).close(aTarget); + } + + protected void actionCancel(AjaxRequestTarget aTarget) + { + findParent(ModalDialog.class).close(aTarget); + } +} diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/ProjectLayersPanel.html b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/ProjectLayersPanel.html index bfea7411d8b..dce95eb88ef 100644 --- a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/ProjectLayersPanel.html +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/layers/ProjectLayersPanel.html @@ -23,6 +23,7 @@
    -
    +
    -
    +