/*
* Licensed under the Apache License, Version 2.0 (the "License");
*
* You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* 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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC §105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.model.logic;
//~--- JDK imports ------------------------------------------------------------
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.IntStream;
import java.util.stream.Stream;
//~--- non-JDK imports --------------------------------------------------------
import sh.isaac.api.Get;
import sh.isaac.api.collections.SequenceSet;
import sh.isaac.api.logic.IsomorphicResults;
import sh.isaac.api.logic.LogicNode;
import sh.isaac.api.logic.LogicalExpression;
import sh.isaac.api.logic.NodeSemantic;
import sh.isaac.api.tree.TreeNodeVisitData;
//~--- classes ----------------------------------------------------------------
/**
* The Class IsomorphicResultsBottomUp.
*
* @author kec
*/
public class IsomorphicResultsBottomUp
implements IsomorphicResults {
/**
* Nodes that are relationship roots in the referenceExpression.
*/
private final Map<RelationshipKey, Integer> referenceRelationshipNodesMap = new TreeMap<>();
/**
* Nodes that are relationship roots in the comparisonExpression.
*/
private final Map<RelationshipKey, Integer> comparisonRelationshipNodesMap = new TreeMap<>();
/** The comparison deletion roots. */
SequenceSet<?> comparisonDeletionRoots = new SequenceSet<>();
/** The reference addition roots. */
SequenceSet<?> referenceAdditionRoots = new SequenceSet<>();
/** The comparison expression. */
LogicalExpressionOchreImpl comparisonExpression;
/** The reference expression. */
LogicalExpressionOchreImpl referenceExpression;
/** The isomorphic expression. */
LogicalExpressionOchreImpl isomorphicExpression;
/** The merged expression. */
LogicalExpressionOchreImpl mergedExpression;
/** The isomorphic solution. */
/*
* isomorphicSolution is a mapping from logicNodes in the referenceExpression to logicNodes
* in the comparisonExpression. The index of the isomorphicSolution is the nodeId
* in the referenceExpression, the value of the array at that index is the
* nodeId in the comparisonExpression: isomorphicSolution[nodeIdInReference] == nodeIdInComparison
* If the nodeIdInComparison == -1, then there is no corresponding node in the
* comparisonExpression as part of the isomorphicSolution.
*/
IsomorphicSolution isomorphicSolution;
/** The reference visit data. */
TreeNodeVisitData referenceVisitData;
/** The comparison visit data. */
TreeNodeVisitData comparisonVisitData;
/** The reference expression to merged node id map. */
int[] referenceExpressionToMergedNodeIdMap;
/** The comparison expression to reference node id map. */
int[] comparisonExpressionToReferenceNodeIdMap;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new isomorphic results bottom up.
*
* @param referenceExpression the reference expression
* @param comparisonExpression the comparison expression
*/
public IsomorphicResultsBottomUp(LogicalExpression referenceExpression, LogicalExpression comparisonExpression) {
this.referenceExpression = (LogicalExpressionOchreImpl) referenceExpression;
this.comparisonExpression = (LogicalExpressionOchreImpl) comparisonExpression;
this.referenceVisitData = new TreeNodeVisitData(referenceExpression.getNodeCount());
this.referenceExpression.depthFirstVisit(null, this.referenceExpression.getRoot(), this.referenceVisitData, 0);
this.comparisonVisitData = new TreeNodeVisitData(comparisonExpression.getNodeCount());
this.comparisonExpression.depthFirstVisit(null, comparisonExpression.getRoot(), this.comparisonVisitData, 0);
this.referenceExpressionToMergedNodeIdMap = new int[referenceExpression.getNodeCount()];
Arrays.fill(this.referenceExpressionToMergedNodeIdMap, -1);
this.comparisonExpressionToReferenceNodeIdMap = new int[comparisonExpression.getNodeCount()];
Arrays.fill(this.comparisonExpressionToReferenceNodeIdMap, -1);
this.isomorphicSolution = isomorphicAnalysis();
for (int referenceNodeId = 0; referenceNodeId < this.isomorphicSolution.solution.length; referenceNodeId++) {
if (this.isomorphicSolution.solution[referenceNodeId] > -1) {
this.comparisonExpressionToReferenceNodeIdMap[this.isomorphicSolution.solution[referenceNodeId]] =
referenceNodeId;
}
}
this.isomorphicExpression = new LogicalExpressionOchreImpl(this.referenceExpression,
this.isomorphicSolution.solution);
this.referenceVisitData.getNodeIdsForDepth(3).stream().forEach((nodeId) -> {
this.referenceRelationshipNodesMap.put(
new RelationshipKey(nodeId, this.referenceExpression), nodeId);
});
this.comparisonVisitData.getNodeIdsForDepth(3).stream().forEach((nodeId) -> {
this.comparisonRelationshipNodesMap.put(
new RelationshipKey(nodeId, this.comparisonExpression), nodeId);
});
computeAdditions();
computeDeletions();
final int[] identityMap = new int[this.referenceExpression.getNodeCount()];
for (int i = 0; i < identityMap.length; i++) {
identityMap[i] = i;
}
this.mergedExpression = new LogicalExpressionOchreImpl(this.referenceExpression,
identityMap,
this.referenceExpressionToMergedNodeIdMap);
// make a node mapping from comparison expression to the merged expression
final int[] comparisonToMergedMap = new int[comparisonExpression.getNodeCount()];
Arrays.fill(comparisonToMergedMap, -1);
for (int referenceNodeId = 0; referenceNodeId < this.isomorphicSolution.solution.length; referenceNodeId++) {
if (this.isomorphicSolution.solution[referenceNodeId] >= 0) {
comparisonToMergedMap[this.isomorphicSolution.solution[referenceNodeId]] = referenceNodeId;
}
}
// Add the deletions
getDeletedRelationshipRoots().forEach((deletionRoot) -> {
// deleted relationships roots come from the comparison expression.
final int rootToAddParentSequence =
this.referenceExpressionToMergedNodeIdMap[this.comparisonExpressionToReferenceNodeIdMap[this.comparisonVisitData.getPredecessorSequence(deletionRoot.getNodeIndex())]];
addFragment(deletionRoot, this.comparisonExpression, rootToAddParentSequence);
});
}
//~--- methods -------------------------------------------------------------
/**
* Generate possible solutions.
*
* @param incomingPossibleSolutions the incoming set of solutions, to seed
* the generation for this depth
* @param possibleSolutionMap The set of possible logicNodes to consider for the
* next depth of the tree.
* @return A set of possible solutions
*/
public Set<IsomorphicSolution> generatePossibleSolutions(Set<IsomorphicSolution> incomingPossibleSolutions,
Map<Integer, SortedSet<IsomorphicSearchBottomUpNode>> possibleSolutionMap) {
Set<IsomorphicSolution> possibleSolutions = new HashSet<>();
possibleSolutions.addAll(incomingPossibleSolutions);
for (final Map.Entry<Integer, SortedSet<IsomorphicSearchBottomUpNode>> entry: possibleSolutionMap.entrySet()) {
possibleSolutions = generatePossibleSolutionsForNode(entry.getKey(), entry.getValue(), possibleSolutions);
}
if (possibleSolutions.isEmpty()) {
return incomingPossibleSolutions;
}
final HashMap<Integer, HashSet<IsomorphicSolution>> scoreSolutionMap = new HashMap<>();
int maxScore = 0;
for (final IsomorphicSolution solution: possibleSolutions) {
if (solution.getScore() >= maxScore) {
maxScore = solution.getScore();
HashSet<IsomorphicSolution> maxScoreSet = scoreSolutionMap.get(maxScore);
if (maxScoreSet == null) {
maxScoreSet = new HashSet<>();
scoreSolutionMap.put(maxScore, maxScoreSet);
}
maxScoreSet.add(solution);
}
}
return scoreSolutionMap.get(maxScore);
}
/**
* To string.
*
* @return the string
*/
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Isomorphic Analysis for:")
.append(Get.conceptDescriptionText(this.referenceExpression.conceptSequence))
.append("\n ")
.append(Get.identifierService()
.getUuidPrimordialFromConceptId(this.referenceExpression.conceptSequence))
.append("\n\n");
builder.append("Reference expression:\n\n ");
builder.append(this.referenceExpression.toString("r"));
builder.append("\nComparison expression:\n\n ");
builder.append(this.comparisonExpression.toString("c"));
if (this.isomorphicExpression != null) {
builder.append("\nIsomorphic expression:\n\n ");
builder.append(this.isomorphicExpression.toString("i"));
}
if (this.isomorphicSolution != null) {
builder.append("\nIsomorphic solution: \n");
String formatString = "[%2d";
String nullString = " ∅ ";
if (this.isomorphicSolution.getSolution().length < 10) {
formatString = "[%d";
nullString = " ∅ ";
}
if (this.isomorphicSolution.getSolution().length > 99) {
formatString = "[%3d";
nullString = " ∅ ";
}
for (int i = 0; i < this.isomorphicSolution.getSolution().length; i++) {
builder.append(" ");
builder.append(String.format(formatString, i));
builder.append("r] ➞ ");
if (this.isomorphicSolution.getSolution()[i] == -1) {
builder.append(nullString);
} else {
builder.append(String.format(formatString, this.isomorphicSolution.getSolution()[i]));
}
if (this.isomorphicSolution.getSolution()[i] < 0) {
builder.append("\n");
} else if (i != this.isomorphicSolution.getSolution()[i]) {
builder.append("c]* ");
builder.append(this.referenceExpression.getNode(i)
.toString("r"));
builder.append("\n");
} else {
builder.append("c] ");
builder.append(this.referenceExpression.getNode(i)
.toString("r"));
builder.append("\n");
}
}
builder.append("\nAdditions: \n\n");
getAdditionalNodeRoots().forEach((additionRoot) -> {
builder.append(" ")
.append(additionRoot.fragmentToString("r"));
builder.append("\n");
});
builder.append("\nDeletions: \n\n");
getDeletedNodeRoots().forEach((deletionRoot) -> {
builder.append(" ")
.append(deletionRoot.fragmentToString("c"));
builder.append("\n");
});
builder.append("\nShared relationship roots: \n\n");
getSharedRelationshipRoots().forEach((sharedRelRoot) -> {
builder.append(" ")
.append(sharedRelRoot.fragmentToString());
builder.append("\n");
});
builder.append("\nNew relationship roots: \n\n");
getAddedRelationshipRoots().forEach((addedRelRoot) -> {
builder.append(" ")
.append(addedRelRoot.fragmentToString());
builder.append("\n");
});
builder.append("\nDeleted relationship roots: \n\n");
getDeletedRelationshipRoots().forEach((deletedRelRoot) -> {
builder.append(" ")
.append(deletedRelRoot.fragmentToString());
builder.append("\n");
});
builder.append("\nMerged expression: \n\n");
if (this.mergedExpression != null) {
builder.append(this.mergedExpression.toString("m"));
} else {
builder.append("null");
}
builder.append("\n");
}
return builder.toString();
}
/**
* Adds the fragment.
*
* @param rootToAdd the root to add
* @param originExpression the origin expression
* @param rootToAddParentSequence the root to add parent sequence
*/
private void addFragment(LogicNode rootToAdd,
LogicalExpressionOchreImpl originExpression,
int rootToAddParentSequence) {
final LogicNode[] descendents = rootToAdd.getDescendents();
int mergedExpressionIndex = this.mergedExpression.getNodeCount();
final int[] additionSolution = new int[originExpression.getNodeCount()];
Arrays.fill(additionSolution, -1);
additionSolution[rootToAdd.getNodeIndex()] = mergedExpressionIndex++;
for (final LogicNode descendent: descendents) {
additionSolution[descendent.getNodeIndex()] = mergedExpressionIndex++;
}
final LogicNode[] addedNodes = this.mergedExpression.addNodes(originExpression,
additionSolution,
rootToAdd.getNodeIndex());
// Need convert rootToAddParentSequence from originExpression nodeId to mergedExpression nodeId.
this.mergedExpression.getNode(rootToAddParentSequence)
.addChildren(addedNodes[0]);
// TODO make sure all children are added.
}
/**
* Compute additions.
*/
private void computeAdditions() {
final SequenceSet<?> nodesInSolution = new SequenceSet<>();
final SequenceSet<?> nodesNotInSolution = new SequenceSet<>();
for (int i = 0; i < this.isomorphicSolution.getSolution().length; i++) {
if (this.isomorphicSolution.getSolution()[i] >= 0) {
nodesInSolution.add(i);
} else {
nodesNotInSolution.add(i);
}
}
nodesNotInSolution.stream().forEach((additionNode) -> {
int additionRoot = additionNode;
while (
nodesNotInSolution.contains(
this.referenceVisitData.getPredecessorSequence(additionRoot))) {
additionRoot = this.referenceVisitData.getPredecessorSequence(additionRoot);
}
this.referenceAdditionRoots.add(additionRoot);
});
}
/**
* Compute deletions.
*/
private void computeDeletions() {
final SequenceSet<?> comparisonNodesInSolution = new SequenceSet<>();
Arrays.stream(this.isomorphicSolution.getSolution()).forEach((nodeId) -> {
if (nodeId >= 0) {
comparisonNodesInSolution.add(nodeId);
}
});
final SequenceSet<?> comparisonNodesNotInSolution = new SequenceSet<>();
IntStream.range(0, this.comparisonVisitData.getNodesVisited())
.forEach((nodeId) -> {
if (!comparisonNodesInSolution.contains(nodeId)) {
comparisonNodesNotInSolution.add(nodeId);
}
});
comparisonNodesNotInSolution.stream().forEach((deletedNode) -> {
int deletedRoot = deletedNode;
while (
comparisonNodesNotInSolution.contains(this.comparisonVisitData.getPredecessorSequence(deletedRoot))) {
deletedRoot = this.comparisonVisitData.getPredecessorSequence(deletedRoot);
}
this.comparisonDeletionRoots.add(deletedRoot);
});
}
/**
* Generate possible solutions for node.
*
* @param solutionNodeId the solution node id
* @param incomingPossibleNodes the incoming possible nodes
* @param possibleSolutions the possible solutions
* @return the set
*/
private Set<IsomorphicSolution> generatePossibleSolutionsForNode(int solutionNodeId,
Set<IsomorphicSearchBottomUpNode> incomingPossibleNodes,
Set<IsomorphicSolution> possibleSolutions) {
// Using a set to eliminate duplicate solutions.
final Set<IsomorphicSolution> outgoingPossibleNodes = new HashSet<>();
possibleSolutions.forEach((incomingPossibleSolution) -> {
incomingPossibleNodes.forEach((isomorphicSearchNode) -> {
if (this.comparisonExpression.getNode(isomorphicSearchNode.nodeId)
.equals(this.referenceExpression.getNode(solutionNodeId))) {
final int[] generatedPossibleSolution = new int[incomingPossibleSolution.getSolution().length];
System.arraycopy(incomingPossibleSolution.getSolution(),
0,
generatedPossibleSolution,
0,
generatedPossibleSolution.length);
generatedPossibleSolution[solutionNodeId] = isomorphicSearchNode.nodeId;
final IsomorphicSolution isomorphicSolution = new IsomorphicSolution(generatedPossibleSolution,
this.referenceVisitData,
this.comparisonVisitData);
if (isomorphicSolution.legal) {
outgoingPossibleNodes.add(isomorphicSolution);
}
}
});
});
if (outgoingPossibleNodes.isEmpty()) {
outgoingPossibleNodes.addAll(possibleSolutions);
}
return outgoingPossibleNodes;
}
/**
* Isomorphic analysis.
*
* @return the isomorphic solution
*/
// ? score based on number or leafs included, with higher score for smaller number of intermediate logicNodes.
private IsomorphicSolution isomorphicAnalysis() {
final TreeSet<IsomorphicSearchBottomUpNode> comparisonSearchNodeSet = new TreeSet<>();
for (int i = 0; i < this.comparisonVisitData.getNodesVisited(); i++) {
final LogicNode logicNode = this.comparisonExpression.getNode(i);
final LogicNode[] children = logicNode.getChildren();
if (children.length == 0) {
comparisonSearchNodeSet.add(new IsomorphicSearchBottomUpNode(logicNode.getNodeSemantic(),
this.comparisonVisitData.getConceptsReferencedAtNodeOrAbove(i),
-1,
i));
} else {
for (final LogicNode child: children) {
comparisonSearchNodeSet.add(new IsomorphicSearchBottomUpNode(logicNode.getNodeSemantic(),
this.comparisonVisitData.getConceptsReferencedAtNodeOrAbove(i),
child.getNodeIndex(),
i));
}
}
}
final SequenceSet<?> nodesProcessed = new SequenceSet<>();
final Set<IsomorphicSolution> possibleSolutions = new HashSet<>();
final int[] seedSolution = new int[this.referenceExpression.getNodeCount()];
Arrays.fill(seedSolution, -1);
seedSolution[this.referenceExpression.getRoot().getNodeIndex()] = this.comparisonExpression.getRoot()
.getNodeIndex();
nodesProcessed.add(this.referenceExpression.getRoot()
.getNodeIndex());
// Test for second level matches... Need to do so to make intermediate logicNodes (necessary set/sufficient set)
// are included in the solution, even if there are no matching leaf logicNodes.
this.referenceExpression.getRoot().getChildStream().forEach((referenceRootChild) -> {
this.comparisonExpression.getRoot().getChildStream().forEach((comparisonRootChild) -> {
// Necessary/sufficient set logicNodes.
if (referenceRootChild.equals(comparisonRootChild)) {
seedSolution[referenceRootChild.getNodeIndex()] = comparisonRootChild.getNodeIndex();
nodesProcessed.add(referenceRootChild.getNodeIndex());
// And node below Necessary/sufficient set logicNodes
referenceRootChild.getChildStream().forEach((referenceAndNode) -> {
assert referenceAndNode.getNodeSemantic() == NodeSemantic.AND:
"Expecting reference AND, Found node semantic instead: " +
referenceAndNode.getNodeSemantic();
comparisonRootChild.getChildStream().forEach((comparisonAndNode) -> {
assert comparisonAndNode.getNodeSemantic() == NodeSemantic.AND:
"Expecting comparison AND, Found node semantic instead: " +
comparisonAndNode.getNodeSemantic();
nodesProcessed.add(referenceAndNode.getNodeIndex());
seedSolution[referenceAndNode.getNodeIndex()] = comparisonAndNode.getNodeIndex();
});
});
}
});
});
possibleSolutions.add(new IsomorphicSolution(seedSolution, this.referenceVisitData, this.comparisonVisitData));
final Map<Integer, SortedSet<IsomorphicSearchBottomUpNode>> possibleMatches = new TreeMap<>();
SequenceSet<?> nodesToTry = this.referenceVisitData.getLeafNodes();
while (!nodesToTry.isEmpty()) {
possibleMatches.clear();
final SequenceSet<?> nextSetToTry = new SequenceSet<>();
nodesToTry.stream().forEach((referenceNodeId) -> {
final int predecessorSequence = this.referenceVisitData.getPredecessorSequence(
referenceNodeId); // only add if the node matches. ?
if (predecessorSequence >= 0) {
if (!nodesProcessed.contains(predecessorSequence)) {
nextSetToTry.add(this.referenceVisitData.getPredecessorSequence(referenceNodeId));
nodesProcessed.add(predecessorSequence);
}
}
final LogicNode referenceLogicNode = this.referenceExpression.getNode(referenceNodeId);
if (referenceLogicNode.getChildren().length == 0) {
final IsomorphicSearchBottomUpNode from =
new IsomorphicSearchBottomUpNode(referenceLogicNode.getNodeSemantic(),
this.referenceVisitData.getConceptsReferencedAtNodeOrAbove(
referenceNodeId),
-1,
Integer.MIN_VALUE);
final IsomorphicSearchBottomUpNode to =
new IsomorphicSearchBottomUpNode(referenceLogicNode.getNodeSemantic(),
this.referenceVisitData.getConceptsReferencedAtNodeOrAbove(
referenceNodeId),
-1,
Integer.MAX_VALUE);
final SortedSet<IsomorphicSearchBottomUpNode> searchNodesForReferenceNode =
comparisonSearchNodeSet.subSet(from,
to);
if (!searchNodesForReferenceNode.isEmpty()) {
if (!possibleMatches.containsKey(referenceNodeId)) {
possibleMatches.put(referenceNodeId, new TreeSet<>());
}
possibleMatches.get(referenceNodeId)
.addAll(searchNodesForReferenceNode);
}
} else {
for (final LogicNode child: referenceLogicNode.getChildren()) {
possibleSolutions.stream().map((possibleSolution) -> {
final IsomorphicSearchBottomUpNode from =
new IsomorphicSearchBottomUpNode(
referenceLogicNode.getNodeSemantic(),
this.referenceVisitData.getConceptsReferencedAtNodeOrAbove(
referenceNodeId),
possibleSolution.getSolution()[child.getNodeIndex()],
Integer.MIN_VALUE);
final IsomorphicSearchBottomUpNode to =
new IsomorphicSearchBottomUpNode(
referenceLogicNode.getNodeSemantic(),
this.referenceVisitData.getConceptsReferencedAtNodeOrAbove(
referenceNodeId),
possibleSolution.getSolution()[child.getNodeIndex()],
Integer.MAX_VALUE);
final SortedSet<IsomorphicSearchBottomUpNode> searchNodesForReferenceNode =
comparisonSearchNodeSet.subSet(from,
to);
return searchNodesForReferenceNode;
}).filter((searchNodesForReferenceNode) -> (!searchNodesForReferenceNode.isEmpty())).forEach((searchNodesForReferenceNode) -> {
if (!possibleMatches.containsKey(referenceNodeId)) {
possibleMatches.put(referenceNodeId, new TreeSet<>());
}
possibleMatches.get(referenceNodeId)
.addAll(searchNodesForReferenceNode);
});
}
}
nodesProcessed.add(referenceNodeId);
});
// Introducing tempPossibleSolutions secondary to limitation with lambdas, requiring a final object...
final Set<IsomorphicSolution> tempPossibleSolutions = new HashSet<>();
tempPossibleSolutions.addAll(possibleSolutions);
possibleSolutions.clear();
possibleSolutions.addAll(generatePossibleSolutions(tempPossibleSolutions, possibleMatches));
nodesToTry = nextSetToTry;
}
return possibleSolutions.stream()
.max((IsomorphicSolution o1,
IsomorphicSolution o2) -> Integer.compare(o1.getScore(), o2.getScore()))
.get();
}
/**
* Scoring algorithm to determine if it is possible that a
* isomorphicSolution based on the possibleSolution may score >= the current
* maximum isomorphicSolution. Used to trim the search space of unnecessary
* permutations.
*
* @param solution the solution
* @return the int
*/
private int scoreSolution(int[] solution) {
int score = 0;
for (final int solutionArrayValue: solution) {
if (solutionArrayValue >= 0) {
score++;
}
}
return score;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the added relationship roots.
*
* @return the added relationship roots
*/
@Override
public final Stream<LogicNode> getAddedRelationshipRoots() {
final TreeSet<RelationshipKey> addedRelationshipRoots =
new TreeSet<>(this.referenceRelationshipNodesMap.keySet());
addedRelationshipRoots.removeAll(this.comparisonRelationshipNodesMap.keySet());
return addedRelationshipRoots.stream()
.map((
RelationshipKey key) -> this.referenceExpression.getNode(
this.referenceRelationshipNodesMap.get(key)));
}
/**
* Gets the additional node roots.
*
* @return the additional node roots
*/
@Override
public Stream<LogicNode> getAdditionalNodeRoots() {
return this.referenceAdditionRoots.stream()
.mapToObj((nodeId) -> this.referenceExpression.getNode(nodeId));
}
/**
* Gets the comparison expression.
*
* @return the comparison expression
*/
@Override
public LogicalExpressionOchreImpl getComparisonExpression() {
return this.comparisonExpression;
}
/**
* Gets the deleted node roots.
*
* @return the deleted node roots
*/
@Override
public Stream<LogicNode> getDeletedNodeRoots() {
return this.comparisonDeletionRoots.stream()
.mapToObj((nodeId) -> this.comparisonExpression.getNode(nodeId));
}
/**
* Gets the deleted relationship roots.
*
* @return the deleted relationship roots
*/
@Override
public final Stream<LogicNode> getDeletedRelationshipRoots() {
final TreeSet<RelationshipKey> deletedRelationshipRoots =
new TreeSet<>(this.comparisonRelationshipNodesMap.keySet());
deletedRelationshipRoots.removeAll(this.referenceRelationshipNodesMap.keySet());
return deletedRelationshipRoots.stream()
.map((
RelationshipKey key) -> this.comparisonExpression.getNode(
this.comparisonRelationshipNodesMap.get(key)));
}
/**
* Gets the isomorphic expression.
*
* @return the isomorphic expression
*/
@Override
public LogicalExpression getIsomorphicExpression() {
return this.isomorphicExpression;
}
/**
* Gets the merged expression.
*
* @return the merged expression
*/
@Override
public LogicalExpression getMergedExpression() {
return this.mergedExpression;
}
/**
* Gets the reference expression.
*
* @return the reference expression
*/
@Override
public LogicalExpressionOchreImpl getReferenceExpression() {
return this.referenceExpression;
}
/**
* Gets the shared relationship roots.
*
* @return the shared relationship roots
*/
@Override
public Stream<LogicNode> getSharedRelationshipRoots() {
final TreeSet<RelationshipKey> sharedRelationshipRoots =
new TreeSet<>(this.referenceRelationshipNodesMap.keySet());
sharedRelationshipRoots.retainAll(this.comparisonRelationshipNodesMap.keySet());
return sharedRelationshipRoots.stream()
.map((
RelationshipKey key) -> this.referenceExpression.getNode(
this.referenceRelationshipNodesMap.get(key)));
}
}