diff --git a/pom.xml b/pom.xml index d342647..10e6c40 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,12 @@ 4.0.0 - io.github.ardoco + + io.github.ardoco + parent + 2.0.0-SNAPSHOT + + named-architecture-entity-recognition 1.0.0-SNAPSHOT @@ -28,7 +33,6 @@ GMT+1 - scm:git:git://github.com/ArDoCo/NamedArchitectureEntityRecognition.git scm:git:ssh://github.com:ArDoCo/NamedArchitectureEntityRecognition.git @@ -50,16 +54,6 @@ UTF-8 UTF-8 UTF-8 - 21 - ${java.version} - ${java.version} - ${java.version} - - 1.1.0 - 2.0.14 - 2.43.0 - 5.11.0 - 2.17.2 @@ -75,11 +69,9 @@ - com.fasterxml.jackson.core jackson-databind - ${jackson.version} dev.langchain4j @@ -89,204 +81,57 @@ dev.langchain4j langchain4j-open-ai - io.github.ardoco metrics - 0.1.1 + ${metrics.version} - io.github.ardoco.core common ${ardoco.version} - - - org.apache.commons - commons-text - 1.13.1 - - io.github.cdimascio dotenv-java 3.2.0 - - org.junit.jupiter - junit-jupiter - ${junit.version} - test - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - org.slf4j - slf4j-simple - ${slf4j.version} - test + org.apache.commons + commons-text + 1.13.1 + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + false + + + true + + mavenSnapshot + https://central.sonatype.com/repository/maven-snapshots/ + + - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - ${java.version} - ${java.version} - ${java.version} - UTF-8 - true - 128m - 512m - - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.2.5 - - - - integration-test - verify - - integration-test - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.2.4 - - - org.apache.maven.plugins - maven-install-plugin - 3.1.2 - - - org.apache.maven.plugins - maven-jar-plugin - 3.4.2 - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - org.sonatype.central - central-publishing-maven-plugin - 0.7.0 - true - - central - true - published - ardoco-naer - - - - com.diffplug.spotless spotless-maven-plugin - ${spotless.version} - - - - - *.md - .gitignore - - - - - true - 4 - - - - - - - - ${maven.multiModuleProjectDirectory}/formatter.xml - - - - - ${maven.multiModuleProjectDirectory}/license-header - - - - ${maven.multiModuleProjectDirectory}/spotless.importorder - - - - - UTF-8 - true - false - 2 - false - true - groupId,artifactId - groupId,artifactId - groupId,artifactId - groupId,artifactId - false - true - true - recommended_2008_06 - - - origin/main - org.apache.maven.plugins maven-javadoc-plugin - 3.7.0 - - - attach-javadocs - - jar - - - org.apache.maven.plugins maven-source-plugin - 3.3.1 - - - attach-sources - - jar-no-fork - - - org.sonatype.central @@ -330,5 +175,4 @@ - diff --git a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/model/NamedEntity.java b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/model/NamedEntity.java index 483d3f3..4e293f5 100644 --- a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/model/NamedEntity.java +++ b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/model/NamedEntity.java @@ -1,10 +1,7 @@ /* Licensed under MIT 2025. */ package edu.kit.kastel.mcse.ardoco.naer.model; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -20,11 +17,11 @@ public class NamedEntity { /** * alternative names of the entity, e.g., if the name is ambiguous */ - private final Set alternativeNames; + private final SortedSet alternativeNames; /** * all occurrences of the entity in the {@link #sourceText} */ - private final Set occurrences; + private final SortedSet occurrences; private String name; /** * the software architecture documentation (text) in which the named entity has been recognized @@ -46,8 +43,8 @@ private NamedEntity(@JsonProperty("name") String name, @JsonProperty("type") Nam @JsonProperty("alternativeNames") List alternativeNames, @JsonProperty("occurrences") List occurrences) { this.name = name; this.type = type; - this.alternativeNames = new HashSet<>(alternativeNames); - this.occurrences = new HashSet<>(occurrences); + this.alternativeNames = new TreeSet<>(alternativeNames); + this.occurrences = new TreeSet<>(occurrences); } /** @@ -60,8 +57,8 @@ private NamedEntity(@JsonProperty("name") String name, @JsonProperty("type") Nam public NamedEntity(String name, NamedEntityType type) { this.name = name; this.type = type; - this.alternativeNames = new HashSet<>(); - this.occurrences = new HashSet<>(); + this.alternativeNames = new TreeSet<>(); + this.occurrences = new TreeSet<>(); } @Nullable @@ -101,7 +98,7 @@ public NamedEntityType getType() { * * @return a set of strings representing alternative names for this entity */ - public Set getAlternativeNames() { + public SortedSet getAlternativeNames() { return alternativeNames; } @@ -122,7 +119,7 @@ public void makeAllNamesLowerCase() { this.name = this.name.toLowerCase(); } - Set lowercasedAlternativeNames = new HashSet<>(); + SortedSet lowercasedAlternativeNames = new TreeSet<>(); for (String alternativeName : this.alternativeNames) { if (alternativeName != null) { lowercasedAlternativeNames.add(alternativeName.toLowerCase()); @@ -138,8 +135,8 @@ public void makeAllNamesLowerCase() { * * @return a set of unique integers representing the line/sentence numbers in which the entity is mentioned (line numbers are 1-indexed). */ - public Set getOccurrenceLines() { - Set result = new HashSet<>(); + public SortedSet getOccurrenceLines() { + SortedSet result = new TreeSet<>(); for (Occurrence occurrence : occurrences) { result.add(occurrence.sentenceNumber); } @@ -184,7 +181,7 @@ public String toString() { * @param sentenceNumber starting at {@code 1} * @param referenceType type of how the entity is referenced */ - private record Occurrence(int sentenceNumber, NamedEntityReferenceType referenceType) { + private record Occurrence(int sentenceNumber, NamedEntityReferenceType referenceType) implements Comparable { @JsonCreator private Occurrence(@JsonProperty("line") int sentenceNumber, @JsonProperty("referenceType") NamedEntityReferenceType referenceType) { this.sentenceNumber = sentenceNumber; @@ -196,5 +193,14 @@ private Occurrence(@JsonProperty("line") int sentenceNumber, @JsonProperty("refe public String toString() { return sentenceNumber + ":" + referenceType; } + + @Override + public int compareTo(@NotNull Occurrence o) { + int cmp = Integer.compare(this.sentenceNumber, o.sentenceNumber); + if (cmp != 0) { + return cmp; + } + return this.referenceType.compareTo(o.referenceType); + } } } diff --git a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityDeserializer.java b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityDeserializer.java index 4d9d2f6..778ec94 100644 --- a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityDeserializer.java +++ b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityDeserializer.java @@ -2,9 +2,7 @@ package edu.kit.kastel.mcse.ardoco.naer.serialization; import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; @@ -50,6 +48,6 @@ public Set deserialize(JsonParser p, DeserializationContext ctxt) t for (NamedEntity entity : entities) { entity.setSourceText(softwareArchitectureDocumentation); } - return new HashSet<>(entities); + return new LinkedHashSet<>(entities); } } diff --git a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityParser.java b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityParser.java index bf6a510..4730642 100644 --- a/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityParser.java +++ b/src/main/java/edu/kit/kastel/mcse/ardoco/naer/serialization/NamedEntityParser.java @@ -2,10 +2,7 @@ package edu.kit.kastel.mcse.ardoco.naer.serialization; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,7 +39,7 @@ private NamedEntityParser() { public static Set fromJson(String json, SoftwareArchitectureDocumentation sad) throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonNode rootNode = mapper.readTree(json); - Set entities = new HashSet<>(); + Set entities = new LinkedHashSet<>(); for (JsonNode entityNode : rootNode) { String name = entityNode.get("name").asText(); @@ -79,8 +76,8 @@ public static Set fromJson(String json, SoftwareArchitectureDocumen * @throws IOException if the input string is invalid or cannot be parsed */ public static Set fromString(String str, SoftwareArchitectureDocumentation softwareArchitectureDocumentation) throws IOException { - Map entityMap = new HashMap<>(); - Map> entityOccurencesMap = new HashMap<>(); //needed to determine reference types of the occurrences after information about alternative names is saved + Map entityMap = new LinkedHashMap<>(); + Map> entityOccurencesMap = new LinkedHashMap<>(); //needed to determine reference types of the occurrences after information about alternative names is saved String[] lines = str.split("\\R"); for (int i = 0; i < lines.length; i++) { lines[i] = lines[i].trim(); @@ -98,7 +95,7 @@ public static Set fromString(String str, SoftwareArchitectureDocume } } - return new HashSet<>(entityMap.values()); + return new LinkedHashSet<>(entityMap.values()); } private static void processLines(SoftwareArchitectureDocumentation softwareArchitectureDocumentation, String[] lines, boolean parsingAlternativeNames, @@ -157,7 +154,7 @@ private static void parseEntityOccurrence(SoftwareArchitectureDocumentation soft entity = new NamedEntity(name, currentEntityType); entity.setSourceText(softwareArchitectureDocumentation); entityMap.put(name, entity); - entityOccurencesMap.put(name, new HashSet<>()); + entityOccurencesMap.put(name, new LinkedHashSet<>()); } entityOccurencesMap.get(name).add(lineNumber); } diff --git a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/ArchitectureTest.java b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/ArchitectureTest.java new file mode 100644 index 0000000..480db23 --- /dev/null +++ b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/ArchitectureTest.java @@ -0,0 +1,211 @@ +/* Licensed under MIT 2025. */ +package edu.kit.kastel.mcse.ardoco.naer.recognizer; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.eclipse.collections.api.map.sorted.ImmutableSortedMap; +import org.eclipse.collections.api.map.sorted.MutableSortedMap; +import org.eclipse.collections.api.set.sorted.ImmutableSortedSet; +import org.eclipse.collections.api.set.sorted.MutableSortedSet; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.*; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; + +@AnalyzeClasses(packages = "edu.kit.kastel.mcse.ardoco.naer") +class ArchitectureTest { + /** + * Rule that enforces functional programming practices. + *

+ * Discourages the use of {@code forEach} and {@code forEachOrdered} on streams and lists, + * as these are typically used for side effects. Prefer functional operations instead. + */ + @ArchTest + static final ArchRule noForEachInCollectionsOrStream = noClasses().should() + .callMethod(Stream.class, "forEach", Consumer.class) + .orShould() + .callMethod(Stream.class, "forEachOrdered", Consumer.class) + .orShould() + .callMethod(List.class, "forEach", Consumer.class) + .orShould() + .callMethod(List.class, "forEachOrdered", Consumer.class) + .because("Lambdas should be functional. ForEach is typically used for side-effects."); + + @ArchTest + public static final ArchRule forbidHashMapAndHashSetInFavorOfLinkedVersions = noClasses().that() + .doNotHaveFullyQualifiedName(ArchitectureTest.class.getName()) + .should() + .accessClassesThat() + .haveNameMatching(HashMap.class.getName() + "|" + HashSet.class.getName()) + .orShould() + .dependOnClassesThat() + .haveNameMatching(HashMap.class.getName() + "|" + HashSet.class.getName()); + + @ArchTest + public static final ArchRule ensureContractBetweenEqualsHashCodeAndCompareTo = classes().that(directlyImplement(Comparable.class)) + .and() + .areNotEnums() + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() // e.g., type references for jackson + .should(implementEqualsAndHashCode()); + + private static DescribedPredicate directlyImplement(Class targetClass) { + return new DescribedPredicate<>("directly implement " + targetClass.getName()) { + @Override + public boolean test(JavaClass javaClass) { + var directInterfaces = javaClass.getRawInterfaces(); + for (var di : directInterfaces) { + if (di.getName().equals(targetClass.getName())) { + return true; + } + } + return false; + } + }; + } + + private static ArchCondition implementEqualsAndHashCode() { + return new ArchCondition<>("implement equals or hashCode") { + @Override + public void check(JavaClass javaClass, ConditionEvents conditionEvents) { + var methods = javaClass.getAllMethods(); + boolean equals = false; + boolean hashCode = false; + for (var method : methods) { + if (!method.getFullName().contains(javaClass.getFullName())) + continue; + + if (method.getName().equals("hashCode")) { + hashCode = true; + } else if (method.getName().equals("equals")) { + equals = true; + } + } + + if (equals && hashCode) { + satisfied(conditionEvents, javaClass, "Class " + javaClass.getName() + " implements equals and hashCode"); + } else if (equals) { + violated(conditionEvents, javaClass, "Class " + javaClass.getName() + " implements equals but not hashCode"); + } else if (hashCode) { + violated(conditionEvents, javaClass, "Class " + javaClass.getName() + " implements hashCode but not equals"); + } else { + violated(conditionEvents, javaClass, "Class " + javaClass.getName() + " implements neither equals nor hashCode"); + } + } + }; + } + + @ArchTest + public static final ArchRule ensureSortedCollectionsOnlyForComparableTypes = fields().that() + .haveRawType(SortedMap.class) + .or() + .haveRawType(ImmutableSortedMap.class) + .or() + .haveRawType(MutableSortedMap.class) + .or() + .haveRawType(SortedSet.class) + .or() + .haveRawType(ImmutableSortedSet.class) + .or() + .haveRawType(MutableSortedSet.class) + .should(haveComparableGenericType()); + + @ArchTest + public static final ArchRule ensureSortedCollectionsOnlyForComparableTypesInReturn = methods().that() + .haveRawReturnType(SortedSet.class) + .or() + .haveRawReturnType(ImmutableSortedSet.class) + .or() + .haveRawReturnType(MutableSortedSet.class) + .should(haveComparableReturn()); + + @ArchTest + public static final ArchRule ensureSortedMapOnlyForComparableTypesInReturn = methods().that() + .haveRawReturnType(SortedMap.class) + .or() + .haveRawReturnType(ImmutableSortedMap.class) + .or() + .haveRawReturnType(MutableSortedMap.class) + .should(haveComparableReturn()) + .allowEmptyShould(true); + + private static ArchCondition haveComparableGenericType() { + return new ArchCondition<>("have Comparable generic type") { + @Override + public void check(JavaField javaField, ConditionEvents conditionEvents) { + var type = javaField.getType(); + if (type instanceof JavaParameterizedType parameterizedType) { + var typeParameter = parameterizedType.getActualTypeArguments().getFirst(); + if ((typeParameter instanceof JavaClass typeParameterClass) && typeParameterClass.getAllRawInterfaces() + .stream() + .anyMatch(i -> i.getFullName().equals(Comparable.class.getName()))) { + + satisfied(conditionEvents, javaField, "Field " + javaField.getFullName() + " has a Comparable generic type"); + } else { + violated(conditionEvents, javaField, "Field " + javaField.getFullName() + " has a non-Comparable generic type"); + } + } else if (type instanceof JavaClass) { + // Classes generated from lambdas cannot be checked :( + } else { + violated(conditionEvents, javaField, "Field " + javaField.getFullName() + " is not a parameterized type"); + } + } + }; + } + + private static ArchCondition haveComparableReturn() { + return new ArchCondition<>("have Comparable generic type") { + @Override + public void check(JavaMethod javaMethod, ConditionEvents conditionEvents) { + var type = javaMethod.getReturnType(); + if (!(type instanceof JavaParameterizedType parameterizedType)) { + violated(conditionEvents, javaMethod, "Method " + javaMethod.getFullName() + " is not a parameterized type"); + return; + } + + var typeParameter = parameterizedType.getActualTypeArguments().getFirst(); + if ((typeParameter instanceof JavaClass typeParameterClass) && typeParameterClass.getAllRawInterfaces() + .stream() + .anyMatch(i -> i.getFullName().equals(Comparable.class.getName()))) { + + satisfied(conditionEvents, javaMethod, "Method " + javaMethod.getFullName() + " has a Comparable generic type"); + } else if ((typeParameter instanceof JavaWildcardType typeParameterWildCard)) { + var upperBound = typeParameterWildCard.getUpperBounds().getFirst(); + + if (!(upperBound instanceof JavaClass upperBoundClass) || upperBoundClass.getAllRawInterfaces() + .stream() + .noneMatch(i -> i.getFullName().equals(Comparable.class.getName()))) { + violated(conditionEvents, javaMethod, "Method " + javaMethod.getFullName() + " has a non-Comparable generic type"); + return; + } + + satisfied(conditionEvents, javaMethod, "Method " + javaMethod.getFullName() + " has a Comparable generic type"); + } else { + violated(conditionEvents, javaMethod, "Method " + javaMethod.getFullName() + " has a non-Comparable generic type"); + } + } + }; + } + + private static void satisfied(ConditionEvents events, Object location, String message) { + var event = new SimpleConditionEvent(location, true, message); + events.add(event); + } + + private static void violated(ConditionEvents events, Object location, String message) { + var event = new SimpleConditionEvent(location, false, message); + events.add(event); + } + +} diff --git a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/GoldstandardParser.java b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/GoldstandardParser.java index 20d78cf..df834da 100644 --- a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/GoldstandardParser.java +++ b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/GoldstandardParser.java @@ -35,10 +35,10 @@ public static Set parse(Path goldstandardFilePath) throws IOExcepti goldstandardAsString = firstNewline >= 0 ? goldstandardAsString.substring(firstNewline + 1) : ""; if (goldstandardAsString.isBlank()) { - return new HashSet<>(); + return new LinkedHashSet<>(); } - Map entitiesMap = new HashMap<>(); //map name -> NamedEntity + Map entitiesMap = new LinkedHashMap<>(); //map name -> NamedEntity for (String line : goldstandardAsString.split("\n")) { String[] parts = line.split(","); @@ -53,7 +53,7 @@ public static Set parse(Path goldstandardFilePath) throws IOExcepti entitiesMap.put(name, newComponent); } } - return new HashSet<>(entitiesMap.values()); + return new LinkedHashSet<>(entitiesMap.values()); } /** diff --git a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/SimpleComponentOccurrence.java b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/SimpleComponentOccurrence.java index b1a9c9b..08d631f 100644 --- a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/SimpleComponentOccurrence.java +++ b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/SimpleComponentOccurrence.java @@ -1,7 +1,7 @@ /* Licensed under MIT 2025. */ package edu.kit.kastel.mcse.ardoco.naer.recognizer; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import edu.kit.kastel.mcse.ardoco.naer.model.NamedEntity; @@ -16,7 +16,7 @@ public record SimpleComponentOccurrence(String componentName, int sentenceNumber) implements Comparable { public static Set fromComponents(Set components) { - Set result = new HashSet<>(); + Set result = new LinkedHashSet<>(); for (NamedEntity component : components) { for (int lineNumber : component.getOccurrenceLines()) { diff --git a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/TestProjectEvaluator.java b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/TestProjectEvaluator.java index 3215154..3b1e554 100644 --- a/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/TestProjectEvaluator.java +++ b/src/test/java/edu/kit/kastel/mcse/ardoco/naer/recognizer/TestProjectEvaluator.java @@ -7,9 +7,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Stream; +import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,10 +68,10 @@ public void evaluate(ComponentRecognitionParameterizedTest.TestProject project) */ private void evaluateAll() { Path evalResourcesPath = getEvaluationResourcesPath(); - Stream testProjectDirs = assertDoesNotThrow(() -> Files.list(evalResourcesPath)); + var testProjectDirs = assertDoesNotThrow(() -> Files.list(evalResourcesPath)).filter(Files::isDirectory).toList(); int[] errorCounter = { 0 }; - testProjectDirs.filter(Files::isDirectory).forEach(dir -> { + for (Path dir : testProjectDirs) { try { evaluateProjectInDirectory(dir); } catch (Exception e) { @@ -81,7 +79,7 @@ private void evaluateAll() { logger.error("Evaluation failed for project: {}", dir.getFileName()); } logger.info(SEPARATOR); - }); + } assertEquals(0, errorCounter[0], "There were errors in " + errorCounter[0] + " test project(s) during evaluation. Please check the log for details."); } @@ -219,13 +217,13 @@ private void matchComponentNames(Set groundTruth, Set for (NamedEntity component : recognizedComponents) { //one of the possible names of the recognized component needs to match one of the possible names of a ground-truth-component for them to be "equivalent": boolean foundEquivalentComponent = false; - Set componentNamePool = new HashSet<>(component.getAlternativeNames()); + SortedSet componentNamePool = new TreeSet<>(component.getAlternativeNames()); componentNamePool.add(cleanComponentName(component.getName())); for (NamedEntity groundTruthComponent : groundTruth) { if (foundEquivalentComponent) { break; //because we assume that there is only one possible match to be found } - Set groundTruthComponentNamePool = new HashSet<>(groundTruthComponent.getAlternativeNames()); + SortedSet groundTruthComponentNamePool = new TreeSet<>(groundTruthComponent.getAlternativeNames()); groundTruthComponentNamePool.add(cleanComponentName(groundTruthComponent.getName())); for (String componentName : componentNamePool) { if (groundTruthComponentNamePool.contains(componentName)) {