diff --git a/pom.xml b/pom.xml
index ed3a423..1473b4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,144 +1,143 @@
- 4.0.0
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
- edu.baylor.ecs.cloudhubs
- boundedcontext
- 1.0-SNAPSHOT
+ edu.baylor.ecs.cloudhubs
+ boundedcontext
+ 1.0-SNAPSHOT
- bounded-context
+ bounded-context
-
- UTF-8
- 1.8
- 1.8
-
+
+ UTF-8
+ 1.8
+ 1.8
+
-
-
-
- org.junit.jupiter
- junit-jupiter-api
- 5.3.2
- test
-
-
-
- org.junit.jupiter
- junit-jupiter-params
- 5.3.2
- test
-
-
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.3.2
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.3.2
+ test
+
+
-
-
- org.apache.commons
- commons-lang3
- 3.9
-
+
+
+ org.apache.commons
+ commons-lang3
+ 3.9
+
-
-
-
-
+
+
+
+
- com.github.cloudhubs
- ws4j
- main-SNAPSHOT
+ com.github.cloudhubs
+ ws4j
+ main-SNAPSHOT
-
-
-
-
- com.github.cloudhubs
- prophet-dto
- expansion-base-SNAPSHOT
+
+
+
+
+ com.github.cloudhubs
+ prophet-dto
+ expansion-base-SNAPSHOT
-
- com.google.code.gson
- gson
- 2.8.6
-
+
+ com.google.code.gson
+ gson
+ 2.8.6
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.20
+ provided
+
+
-
-
-
+
- jitpack.io
- https://jitpack.io
+ jitpack.io
+ https://jitpack.io
-
-
-
-
-
- maven-clean-plugin
- 3.1.0
-
-
-
- maven-resources-plugin
- 3.0.2
-
-
- maven-compiler-plugin
- 3.8.0
-
-
- maven-surefire-plugin
- 2.22.1
-
-
- maven-jar-plugin
- 3.0.2
-
-
- maven-install-plugin
- 2.5.2
-
-
- maven-deploy-plugin
- 2.8.2
-
-
-
- maven-site-plugin
- 3.7.1
-
-
- maven-project-info-reports-plugin
- 3.0.0
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- 8
- 8
-
-
-
-
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/DatabseRepository.java b/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/DatabseRepository.java
deleted file mode 100644
index b82b7fe..0000000
--- a/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/DatabseRepository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright 2019, Cloud Innovation Labs, All rights reserved
- * Version: 1.0
- */
-
-package edu.baylor.ecs.prophet.bounded.context.repository;
-
-import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.BoundedContext;
-import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.SystemContext;
-
-/**
- * Interface to communicate with neo4j database
- */
-public interface DatabseRepository {
-
- /**
- * get all context models from the database based on system name
- * @param systemName the name of the system
- * @return the system context
- */
- SystemContext getAllEntityClassesInSystem(String systemName);
-
- /**
- * persist data to the DB
- * @param boundedContext the bounded context to persist
- * @return the bounded context that was persisted
- */
- BoundedContext createBoundedContext(BoundedContext boundedContext);
-
-}
diff --git a/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/impl/DatabseRepositoryImpl.java b/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/impl/DatabseRepositoryImpl.java
deleted file mode 100644
index 9b197fe..0000000
--- a/src/main/java/edu/baylor/ecs/prophet/bounded/context/repository/impl/DatabseRepositoryImpl.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright 2019, Cloud Innovation Labs, All rights reserved
- * Version: 1.0
- */
-
-package edu.baylor.ecs.prophet.bounded.context.repository.impl;
-
- // import edu.baylor.ecs.cloudhubs.prophet.metamodel.service.BoundedContextDatabaseService;
-import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.BoundedContext;
-import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.SystemContext;
-import edu.baylor.ecs.prophet.bounded.context.repository.DatabseRepository;
-
-/**
- * @author Ian Laird
- * @see DatabseRepository
- */
-public class DatabseRepositoryImpl implements DatabseRepository {
-
- /**
- * gets {@link SystemContext} from a system name
- * @param systemName the name of the system
- * @return the system context
- */
- @Override
- public SystemContext getAllEntityClassesInSystem(String systemName) {
- return null;
- //return BoundedContextDatabaseService.getAllEntityClassesInSystem(systemName);
- }
-
- /**
- * saves {@link BoundedContext} to the DB
- * @param boundedContext the bounded context to persist
- * @return the saved bounded context
- */
- @Override
- public BoundedContext createBoundedContext(BoundedContext boundedContext) {
- return null;
- //return BoundedContextDatabaseService.createBoundedContext(boundedContext);
- }
-}
diff --git a/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/BoundedContextUtilsImpl.java b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/BoundedContextUtilsImpl.java
index e41e66f..fee9e16 100644
--- a/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/BoundedContextUtilsImpl.java
+++ b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/BoundedContextUtilsImpl.java
@@ -5,268 +5,205 @@
package edu.baylor.ecs.prophet.bounded.context.utils.impl;
-
import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.*;
import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Module;
import edu.baylor.ecs.prophet.bounded.context.exception.FieldMappingException;
import edu.baylor.ecs.prophet.bounded.context.utils.BoundedContextUtils;
import edu.baylor.ecs.prophet.bounded.context.utils.SimilarityUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
+import edu.baylor.ecs.prophet.bounded.context.utils.impl.util.EntityCollection;
import java.util.*;
import java.util.stream.Collectors;
/**
* methods for creating a {@link BoundedContext} from a {@link SystemContext}
+ *
* @author Ian laird
*/
public class BoundedContextUtilsImpl implements BoundedContextUtils {
- // tools used for finding similarities
- private SimilarityUtils similarityUtils = new SimilarityUtilsImpl();
-
- public static double ENTITY_SIMILARITY_CUTOFF = 0.9;
-
- /**
- * creates a bounded context for the system context
- * @param systemContext the system
- * @return the bounded context
- */
- @Override
- public BoundedContext createBoundedContext(SystemContext systemContext, boolean useWuPalmer) {
-
- // sanitize all of the name in the systemContext
- NameStripper.sanitizeSystemContext(systemContext);
-
- Set modules = systemContext.getModules();
- Stack moduleStack = new Stack<>();
- for (Module m: modules) {
- moduleStack.add(m.clone());
- }
-// moduleStack.addAll(modules);
- while(moduleStack.size() > 1) {
- Module m1 = moduleStack.pop();
- Module m2 = moduleStack.pop();
- Module result = mergeModules(m1, m2, useWuPalmer);
- if (result.getEntities().size() > 0) {
- moduleStack.push(result);
- }
- }
-
- return new BoundedContext(systemContext.getSystemName(), moduleStack.size() > 0 ? moduleStack.get(0).getEntities() : null);
-
- }
-
- /**
- * merges two modules into one module
- * @param moduleOne one of the modules
- * @param moduleTwo the other module
- * @return a new module comprised of the other two
- */
- @Override
- public Module mergeModules(Module moduleOne, Module moduleTwo, boolean useWuPalmer){
-
- // for each entity find the similarity it has to other entities
- Map>>>
- entitySimilarity = new HashMap<>();
-
- // shows which entities in module two are encountered
- Set mappedInTwo = new HashSet<>();
-
- // get all entities in module one
- moduleOne.getEntities()
-
- // for each entity in entity one add to entity similarity
- .forEach(x -> entitySimilarity.put(
-
- // the current entity of module one
- x,
-
- // create stream of entity two entities
- moduleTwo.getEntities().stream()
-
- // create map
- .collect(Collectors.toMap(
-
- // similarity of entity from module one and entity from module two
- y -> similarityUtils.globalFieldSimilarity(x, y, useWuPalmer).getLeft(),
-
- // tuple of entity from module two
- y -> new ImmutablePair<>(y,
- //and the field mapping
- similarityUtils.globalFieldSimilarity(x, y, useWuPalmer).getRight()),
- (oldValue,newValue) -> newValue,
- TreeMap::new
- ))
- ));
-
- Module newModule = new Module(moduleOne.getName().getName());
-
- newModule.setEntities(new HashSet<>());
-
- // sets the entities of the new module
- newModule.getEntities().addAll(
-
- // stream of all entries in entity similarity
- entitySimilarity.entrySet().stream()
-
- // if the similarity is strong enough
- .filter(x -> {
- // if it is not mapped to anything, no merging needs to be performed
- if(x.getValue().isEmpty()){
- newModule.getEntities().add(x.getKey().clone());
- return false;
- }
-
- Map.Entry>> val = x.getValue().lastEntry();
- double similarity = val.getKey();
-
- // if the two entities should be merged
- if (similarity > ENTITY_SIMILARITY_CUTOFF) {
- // add the one mapped to
- mappedInTwo.add(val.getValue().getLeft());
-
- return true;
- } else {
- newModule.getEntities().add(x.getKey().copyWithNamePreface(moduleOne.getName() + "::"));
- //val.getValue().getLeft().getEntityName().setFullName(moduleTwo.getName() + val.getValue().getLeft().getEntityName().getFullName() + "::");
- //newModule.getEntities().add(val.getValue().getLeft().copyWithNamePreface(moduleTwo.getName() + "::"));
-
- return false;
- }
-
- })
-
- // map each mapping between entities to a new merged entity
- .map(x -> mergeEntities(x.getKey(), x.getValue().lastEntry().getValue().getLeft(), x.getValue().lastEntry().getValue().getRight()))
-
- // collect as a list
- .collect(Collectors.toList())
- );
-
- // now add all of the entities in module two that were not mapped to
- for(Entity e : moduleTwo.getEntities()){
- if(!mappedInTwo.contains(e)){
- newModule.getEntities().add(e);
- }
- }
-
- return newModule;
- }
-
- /**
- * merges two entities together using the field mapping
- * @param one the first entity to merge
- * @param two the second entity to merge
- * @param fieldMapping the mapping between the fields of the entities
- * @return the newly created merged entity
- */
- @Override
- public Entity mergeEntities(Entity one, Entity two, Map fieldMapping) {
-
- // the entity that is to be returned
- Entity newEntity = new Entity(one.getEntityName());
-
- // get the fields of the second entity
- Set entityTwoFields = new HashSet<>(two.getFields());
- Field toAdd = null;
-
- if(Objects.isNull(fieldMapping)){
- fieldMapping = new HashMap<>();
- }
-
- Set alreadyEncountered = new HashSet<>();
-
- // make sure that all fields in the field mapping are also in f1 and that no two map to the same value
- for(final Map.Entry f : fieldMapping.entrySet()){
- if(Objects.isNull(f.getValue())){
- continue;
- }
- // make sure that the key exists
- if(!one.getFields().contains(f.getKey())){
- throw new FieldMappingException();
- }
- //make sure that the value exists
- if(!entityTwoFields.contains(f.getValue())){
- throw new FieldMappingException();
- }
- // if the second has already been mapped too
- if(!alreadyEncountered.add(f.getValue())){
- throw new FieldMappingException();
- }
- }
-
- // for each field in entity one
- for (Field f1 : one.getFields()){
-
- // get the field that this field in entity one maps to
- Field f2 = fieldMapping.get(f1);
- toAdd = f1;
-
- String preface = one.getEntityName() + "::";
-
- if (f2 != null) {
-
- //make sure that mapped to field is present in entity 2
- entityTwoFields.remove(f2);
-
- if (f1.isReference() && f2.isReference() && !f1.equals(f2)) {
- Field twoCopy = f2.clone();
- String newName = two.getEntityName().getName() + "::" + twoCopy.getName().getName();
- twoCopy.getName().setFullName(newName);
- newEntity.getFields().add(twoCopy);
- toAdd = f1;
- }
-
- // merge the fields into one field
- else {
- preface = "";
- toAdd = mergeFields(f1, f2);
- }
- }
-
- // add the field
- // TODO what if a field of this name already exists?
- Field newField = toAdd.clone();
- newField.getName().setFullName(preface + newField.getName().getName());
- newEntity.getFields().add(newField);
- }
-
- // add all of the remaining fields in entity 2
- // make a copy of all of the field in entity 2
- Set entityTwoFieldsMapped = entityTwoFields.stream().map(x -> {
- Field fieldCopy = x.clone();
- fieldCopy.getName().setFullName(two.getEntityName().getName() + "::" + fieldCopy.getName().getName());
- return fieldCopy;
- }).collect(Collectors.toSet());
- newEntity.getFields().addAll(entityTwoFieldsMapped);
-
- return newEntity;
- }
-
- /**
- * merges two fields into one field
- * @param one the first field to merge
- * @param two the second field to merge
- * @return the new field
- */
- @Override
- public Field mergeFields(Field one, Field two) {
- String name = one.getName().getName();
- String type = Type.get(one.getType()).ordinal() < Type.get(two.getType()).ordinal() ? two.getType() : one.getType();
-
- Field toReturn = new Field(type, name);
-
- // set isCollection
- toReturn.setCollection(one.isCollection() || two.isCollection());
-
- // set the annotations
- toReturn.setAnnotations(one.getAnnotations());
- toReturn.getAnnotations().addAll(two.getAnnotations());
-
- toReturn.setReference(one.isReference() | two.isReference());
-
- return toReturn;
- }
+ // tools used for finding similarities
+ private SimilarityUtils similarityUtils = new SimilarityUtilsImpl();
+
+ public static final double ENTITY_SIMILARITY_CUTOFF = 0.9;
+
+ /**
+ * creates a bounded context for the system context
+ *
+ * @param systemContext the system
+ * @return the bounded context
+ */
+ @Override
+ public BoundedContext createBoundedContext(SystemContext systemContext, boolean useWuPalmer) {
+
+ // sanitize all of the name in the systemContext
+ NameStripper.sanitizeSystemContext(systemContext);
+
+ Set modules = systemContext.getModules();
+ Stack moduleStack = new Stack<>();
+ for (Module m : modules) {
+ moduleStack.add(m.clone());
+ }
+
+ while (moduleStack.size() > 1) {
+ Module m1 = moduleStack.pop();
+ Module m2 = moduleStack.pop();
+ Module result = mergeModules(m1, m2, useWuPalmer);
+ if (result.getEntities().size() > 0) {
+ moduleStack.push(result);
+ }
+ }
+
+ return new BoundedContext(systemContext.getSystemName(),
+ moduleStack.size() > 0 ? moduleStack.get(0).getEntities() : null);
+
+ }
+
+ /**
+ * merges two modules into one module
+ *
+ * @param moduleOne one of the modules
+ * @param moduleTwo the other module
+ * @return a new module comprised of the other two
+ */
+ @Override
+ public Module mergeModules(Module moduleOne, Module moduleTwo, boolean useWuPalmer) {
+
+ // for each entity find the similarity it has to other entities
+ EntityCollection entitySimilarity = new EntityCollection(moduleOne, moduleTwo, ENTITY_SIMILARITY_CUTOFF,
+ (e1, e2) -> similarityUtils.globalFieldSimilarity(e1, e2, useWuPalmer));
+
+ // Generate blank new module
+ Module newModule = new Module(moduleOne.getName().getName());
+ newModule.setEntities(entitySimilarity.getDistinctEntities());
+
+ // Merge all duplicate entities and add to the new module
+ newModule.getEntities().addAll(entitySimilarity.getSimilarEntities().stream()
+
+ // Map entity/duplicate mappings to a list of merged entities
+ .map(x -> mergeEntities(x.getEntity(), x.getSimilarEntity(), x.getFieldMap()))
+ .collect(Collectors.toList()));
+
+ return newModule;
+ }
+
+ /**
+ * merges two entities together using the field mapping
+ *
+ * @param one the first entity to merge
+ * @param two the second entity to merge
+ * @param fieldMapping the mapping between the fields of the entities
+ * @return the newly created merged entity
+ */
+ @Override
+ public Entity mergeEntities(Entity one, Entity two, Map fieldMapping) {
+
+ // the entity that is to be returned
+ Entity newEntity = new Entity(one.getEntityName());
+
+ // get the fields of the second entity
+ Set entityTwoFields = new HashSet<>(two.getFields());
+ Field toAdd = null;
+
+ if (Objects.isNull(fieldMapping)) {
+ fieldMapping = new HashMap<>();
+ }
+
+ Set alreadyEncountered = new HashSet<>();
+
+ // make sure that all fields in the field mapping exist in their respective
+ // entities, and that no two map to the same value
+ for (final Map.Entry f : fieldMapping.entrySet()) {
+ if (Objects.isNull(f.getValue())) {
+ continue;
+ }
+
+ // Make sure mapped fields exist in their respective entities
+ if (!one.getFields().contains(f.getKey())) {
+ throw new FieldMappingException();
+ }
+ if (!entityTwoFields.contains(f.getValue())) {
+ throw new FieldMappingException();
+ }
+
+ // if the second has already been mapped too
+ if (!alreadyEncountered.add(f.getValue())) {
+ throw new FieldMappingException();
+ }
+ }
+
+ // for each field in entity one
+ for (Field f1 : one.getFields()) {
+
+ // get the field that this field in entity one maps to
+ Field f2 = fieldMapping.get(f1);
+ toAdd = f1;
+ String preface = one.getEntityName() + "::";
+
+ if (f2 != null) {
+
+ // make sure that mapped to field is present in entity 2
+ entityTwoFields.remove(f2);
+
+ if (f1.isReference() && f2.isReference() && !f1.equals(f2)) {
+ Field twoCopy = f2.clone();
+ String newName = two.getEntityName().getName() + "::" + twoCopy.getName().getName();
+ twoCopy.getName().setFullName(newName);
+ newEntity.getFields().add(twoCopy);
+ toAdd = f1;
+ }
+
+ // merge the fields into one field
+ else {
+ preface = "";
+ toAdd = mergeFields(f1, f2);
+ }
+ }
+
+ // add the field
+ // TODO what if a field of this name already exists?
+ Field newField = toAdd.clone();
+ newField.getName().setFullName(preface + newField.getName().getName());
+ newEntity.getFields().add(newField);
+ }
+
+ // add all of the remaining fields in entity 2
+ // make a copy of all of the field in entity 2
+ Set entityTwoFieldsMapped = entityTwoFields.stream().map(x -> {
+ Field fieldCopy = x.clone();
+ fieldCopy.getName().setFullName(two.getEntityName().getName() + "::" + fieldCopy.getName().getName());
+ return fieldCopy;
+ }).collect(Collectors.toSet());
+ newEntity.getFields().addAll(entityTwoFieldsMapped);
+
+ return newEntity;
+ }
+
+ /**
+ * merges two fields into one field
+ *
+ * @param one the first field to merge
+ * @param two the second field to merge
+ * @return the new field
+ */
+ @Override
+ public Field mergeFields(Field one, Field two) {
+ String name = one.getName().getName();
+ String type = Type.get(one.getType()).ordinal() < Type.get(two.getType()).ordinal() ? two.getType()
+ : one.getType();
+
+ Field toReturn = new Field(type, name);
+
+ // set isCollection
+ toReturn.setCollection(one.isCollection() || two.isCollection());
+
+ // set the annotations
+ toReturn.setAnnotations(one.getAnnotations());
+ toReturn.getAnnotations().addAll(two.getAnnotations());
+
+ toReturn.setReference(one.isReference() || two.isReference());
+
+ return toReturn;
+ }
}
diff --git a/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/EntityCollection.java b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/EntityCollection.java
new file mode 100644
index 0000000..eb51490
--- /dev/null
+++ b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/EntityCollection.java
@@ -0,0 +1,99 @@
+package edu.baylor.ecs.prophet.bounded.context.utils.impl.util;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+
+import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Entity;
+import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Field;
+import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Module;
+import lombok.Getter;
+import lombok.var;
+
+/**
+ * An container that takes two modules and stores all entities within the first
+ * based on whether they have a likely duplicate in another specified module.
+ *
+ * @author Micah
+ */
+@Getter
+public class EntityCollection {
+
+ /** Mapping of entity */
+ private final Set similarEntities = new HashSet<>();
+
+ /** Entities with no similar entities to map to */
+ private final Set distinctEntities = new HashSet<>();
+
+ /**
+ * Construct a collection that partitions the merged
+ *
+ * @param mod1
+ * @param mod2
+ * @param matchThreshhold
+ * @param computeSimilarity
+ */
+ public EntityCollection(Module mod1, Module mod2, double matchThreshhold,
+ BiFunction>> computeSimilarity) {
+ // Validate input
+ Objects.requireNonNull(mod1, "mod1 cannot be null");
+ Objects.requireNonNull(mod2, "mod2 cannot be null");
+
+ // Split entities into two sets: those with a match, and those without
+ var splitEntityRecords = mod1.getEntities().stream()
+ .map(mod1Entity -> findMostSimilar(mod1Entity, mod2, computeSimilarity))
+ .collect(Collectors.partitioningBy(Optional::isPresent));
+
+ // All entities with no match are distinct; put them in the distinct list
+ splitEntityRecords.get(false).stream().map(entry -> entry.get().getRight().getEntity().clone())
+ .forEach(distinctEntities::add);
+
+ // Use similarity score to find which matches are false positives
+ Set mappedInTwo = new HashSet<>(); // Index for which entities already handled
+ for (var similarityRecord : splitEntityRecords.get(true)) {
+ // Unwrap the optional/tuple
+ ImmutablePair record = similarityRecord.get();
+ Entity entity = record.getRight().getEntity();
+
+ // If the similarity threshhold is exceeded, this is a true match; otherwise,
+ // record as a distinct entity.
+ if (record.getLeft() > matchThreshhold) {
+ mappedInTwo.add(entity);
+ similarEntities.add(record.getRight());
+ } else {
+ distinctEntities.add(entity.copyWithNamePreface(mod1.getName() + "::"));
+ }
+ }
+
+ // Take all fields from module 2 not already handled, and record them
+ mod2.getEntities().stream().filter(e -> !mappedInTwo.contains(e)).forEach(distinctEntities::add);
+ }
+
+ /**
+ * Find the highest similarity entity within the provided module
+ *
+ * @param entity Entity to compare
+ * @param mod2 Module to search
+ * @param computeSimilarity Callback to compute similarity between entities
+ * @return tuple (similarity, entity, field_mapping)
+ */
+ private Optional> findMostSimilar(Entity entity, Module mod2,
+ BiFunction>> computeSimilarity) {
+ return mod2.getEntities().stream().map(mod2Entity -> {
+ // Convert into a (similarity, entity, field_mappings) tuple
+ var simil = computeSimilarity.apply(entity, mod2Entity);
+ return ImmutablePair.of(simil.getLeft(), new SimilarityRecord(entity, mod2Entity, simil.getRight()));
+ })
+ // Sort by similarity score, highest to lowest
+ .sorted((entity1, entity2) -> entity2.getLeft().compareTo(entity1.getLeft()))
+
+ // Take highest score
+ .findFirst();
+ }
+}
diff --git a/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/SimilarityRecord.java b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/SimilarityRecord.java
new file mode 100644
index 0000000..41d08cd
--- /dev/null
+++ b/src/main/java/edu/baylor/ecs/prophet/bounded/context/utils/impl/util/SimilarityRecord.java
@@ -0,0 +1,23 @@
+package edu.baylor.ecs.prophet.bounded.context.utils.impl.util;
+
+import java.util.Map;
+import java.util.Objects;
+
+import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Entity;
+import edu.baylor.ecs.cloudhubs.prophetdto.systemcontext.Field;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NonNull;
+
+/**
+ * Wraps an entity, an entity that has been identified as "similar", and the
+ * mapping between which entity fields are similar.
+ *
+ * @author Micah
+ */
+@Data
+public class SimilarityRecord {
+ @NonNull Entity entity;
+ @NonNull Entity similarEntity;
+ @NonNull Map fieldMap;
+}