/*
* 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.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.mahout.math.list.IntArrayList;
import sh.isaac.api.DataSource;
import sh.isaac.api.DataTarget;
import sh.isaac.api.Get;
import sh.isaac.api.collections.ConceptSequenceSet;
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.logic.assertions.substitution.SubstitutionFieldSpecification;
import sh.isaac.api.tree.TreeNodeVisitData;
import sh.isaac.model.logic.node.AbstractLogicNode;
import sh.isaac.model.logic.node.AndNode;
import sh.isaac.model.logic.node.ConnectorNode;
import sh.isaac.model.logic.node.DisjointWithNode;
import sh.isaac.model.logic.node.LiteralNode;
import sh.isaac.model.logic.node.LiteralNodeBoolean;
import sh.isaac.model.logic.node.LiteralNodeFloat;
import sh.isaac.model.logic.node.LiteralNodeInstant;
import sh.isaac.model.logic.node.LiteralNodeInteger;
import sh.isaac.model.logic.node.LiteralNodeString;
import sh.isaac.model.logic.node.NecessarySetNode;
import sh.isaac.model.logic.node.OrNode;
import sh.isaac.model.logic.node.RootNode;
import sh.isaac.model.logic.node.SubstitutionNode;
import sh.isaac.model.logic.node.SubstitutionNodeBoolean;
import sh.isaac.model.logic.node.SubstitutionNodeConcept;
import sh.isaac.model.logic.node.SubstitutionNodeFloat;
import sh.isaac.model.logic.node.SubstitutionNodeInstant;
import sh.isaac.model.logic.node.SubstitutionNodeInteger;
import sh.isaac.model.logic.node.SubstitutionNodeLiteral;
import sh.isaac.model.logic.node.SubstitutionNodeString;
import sh.isaac.model.logic.node.SufficientSetNode;
import sh.isaac.model.logic.node.external.ConceptNodeWithUuids;
import sh.isaac.model.logic.node.external.FeatureNodeWithUuids;
import sh.isaac.model.logic.node.external.RoleNodeAllWithUuids;
import sh.isaac.model.logic.node.external.RoleNodeSomeWithUuids;
import sh.isaac.model.logic.node.external.TemplateNodeWithUuids;
import sh.isaac.model.logic.node.internal.ConceptNodeWithSequences;
import sh.isaac.model.logic.node.internal.FeatureNodeWithSequences;
import sh.isaac.model.logic.node.internal.RoleNodeAllWithSequences;
import sh.isaac.model.logic.node.internal.RoleNodeSomeWithSequences;
import sh.isaac.model.logic.node.internal.TemplateNodeWithSequences;
import sh.isaac.model.logic.node.internal.TypedNodeWithSequences;
//~--- classes ----------------------------------------------------------------
/**
* Created by kec on 12/6/14.
*
* TODO need version of Pack that uses UUIDs for change sets
*
* TODO need unique way of identifying data columns for substitution: Use
* enumerations for now
*
* TODO Standard refset for never grouped roles
*
* TODO Standard refset for right identities
*/
public class LogicalExpressionOchreImpl
implements LogicalExpression {
/** The Constant NODE_SEMANTICS. */
private static final NodeSemantic[] NODE_SEMANTICS = NodeSemantic.values();
/** The Constant MEANINGFUL_NODE_SEMANTICS. */
private static final EnumSet<NodeSemantic> MEANINGFUL_NODE_SEMANTICS = EnumSet.of(NodeSemantic.CONCEPT,
NodeSemantic.SUBSTITUTION_CONCEPT);
/** The isa nid. */
protected static int isaNid = 0;
//~--- fields --------------------------------------------------------------
/** The concept sequence. */
transient int conceptSequence = -1;
/** The logic nodes. */
ArrayList<LogicNode> logicNodes = new ArrayList<>();
/** The root node index. */
int rootNodeIndex = -1;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new logical expression ochre impl.
*/
public LogicalExpressionOchreImpl() {}
/**
* Instantiates a new logical expression ochre impl.
*
* @param nodeDataArray the node data array
* @param dataSource the data source
*/
public LogicalExpressionOchreImpl(byte[][] nodeDataArray, DataSource dataSource) {
try {
this.logicNodes = new ArrayList<>(nodeDataArray.length);
for (final byte[] nodeDataArray1: nodeDataArray) {
final DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(nodeDataArray1));
final byte nodeSemanticIndex = dataInputStream.readByte();
final NodeSemantic nodeSemantic = NODE_SEMANTICS[nodeSemanticIndex];
switch (nodeSemantic) {
case DEFINITION_ROOT:
Root(dataInputStream);
break;
case NECESSARY_SET:
NecessarySet(dataInputStream);
break;
case SUFFICIENT_SET:
SufficientSet(dataInputStream);
break;
case AND:
And(dataInputStream);
break;
case OR:
Or(dataInputStream);
break;
case DISJOINT_WITH:
DisjointWith(dataInputStream);
break;
case ROLE_ALL:
switch (dataSource) {
case EXTERNAL:
AllRoleWithUuids(dataInputStream);
break;
case INTERNAL:
AllRole(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + dataSource);
}
break;
case ROLE_SOME:
switch (dataSource) {
case EXTERNAL:
SomeRoleWithUuids(dataInputStream);
break;
case INTERNAL:
SomeRole(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + dataSource);
}
break;
case FEATURE:
switch (dataSource) {
case EXTERNAL:
FeatureWithUuids(dataInputStream);
break;
case INTERNAL:
Feature(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + dataSource);
}
break;
case LITERAL_BOOLEAN:
BooleanLiteral(dataInputStream);
break;
case LITERAL_FLOAT:
FloatLiteral(dataInputStream);
break;
case LITERAL_INSTANT:
InstantLiteral(dataInputStream);
break;
case LITERAL_INTEGER:
IntegerLiteral(dataInputStream);
break;
case LITERAL_STRING:
StringLiteral(dataInputStream);
break;
case CONCEPT:
switch (dataSource) {
case EXTERNAL:
ConceptWithUuids(dataInputStream);
break;
case INTERNAL:
Concept(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + dataSource);
}
break;
case TEMPLATE:
switch (dataSource) {
case EXTERNAL:
TemplateWithUuids(dataInputStream);
break;
case INTERNAL:
Template(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + dataSource);
}
break;
case SUBSTITUTION_BOOLEAN:
BooleanSubstitution(dataInputStream);
break;
case SUBSTITUTION_CONCEPT:
ConceptSubstitution(dataInputStream);
break;
case SUBSTITUTION_FLOAT:
FloatSubstitution(dataInputStream);
break;
case SUBSTITUTION_INSTANT:
InstantSubstitution(dataInputStream);
break;
case SUBSTITUTION_INTEGER:
IntegerSubstitution(dataInputStream);
break;
case SUBSTITUTION_STRING:
StringSubstitution(dataInputStream);
break;
default:
throw new UnsupportedOperationException("Can't handle: " + nodeSemantic);
}
}
this.logicNodes.trimToSize();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* Called to generate an isomorphicExpression and a mergedExpression.
*
* @param another the logical expression to add nodes from.
* @param solution an array mapping from the nodeId in another to the nodeId
* in this expression. If the value of the solution element == -1, that node
* is not added to this logical expression, otherwise the value of the
* solution element is used for the nodeId in this logical expression.
*/
public LogicalExpressionOchreImpl(LogicalExpressionOchreImpl another, int[] solution) {
addNodesWithMap(another, solution, new int[another.getNodeCount()], another.rootNodeIndex);
this.logicNodes.trimToSize();
}
/**
* Instantiates a new logical expression ochre impl.
*
* @param nodeDataArray the node data array
* @param dataSource the data source
* @param conceptId Either a nid or sequence of a concept is acceptable.
*/
public LogicalExpressionOchreImpl(byte[][] nodeDataArray, DataSource dataSource, int conceptId) {
this(nodeDataArray, dataSource);
if (conceptId < 0) {
conceptId = Get.identifierService()
.getConceptSequence(conceptId);
}
this.conceptSequence = conceptId;
}
/**
* Called to generate an isomorphicExpression and a mergedExpression.
*
* @param another the logical expression to add nodes from.
* @param solution an array mapping from the nodeId in another to the nodeId
* in this expression. If the value of the solution element == -1, that node
* is not added to this logical expression, otherwise the value of the
* solution element is used for the nodeId in this logical expression.
* @param anotherToThisNodeIdMap contains a mapping from nodeId in another to nodeId in this constructed expression.
*/
public LogicalExpressionOchreImpl(LogicalExpressionOchreImpl another, int[] solution, int[] anotherToThisNodeIdMap) {
addNodesWithMap(another, solution, anotherToThisNodeIdMap, another.rootNodeIndex);
this.logicNodes.trimToSize();
}
//~--- methods -------------------------------------------------------------
/**
* All role.
*
* @param dataInputStream the data input stream
* @return the role node all with sequences
* @throws IOException Signals that an I/O exception has occurred.
*/
public final RoleNodeAllWithSequences AllRole(DataInputStream dataInputStream)
throws IOException {
return new RoleNodeAllWithSequences(this, dataInputStream);
}
/**
* All role.
*
* @param typeNid the type nid
* @param restriction the restriction
* @return the role node all with sequences
*/
public RoleNodeAllWithSequences AllRole(int typeNid, AbstractLogicNode restriction) {
return new RoleNodeAllWithSequences(this, typeNid, restriction);
}
/**
* All role with uuids.
*
* @param dataInputStream the data input stream
* @return the role node all with uuids
* @throws IOException Signals that an I/O exception has occurred.
*/
public final RoleNodeAllWithUuids AllRoleWithUuids(DataInputStream dataInputStream)
throws IOException {
return new RoleNodeAllWithUuids(this, dataInputStream);
}
/**
* And.
*
* @param children the children
* @return the and node
*/
public final AndNode And(AbstractLogicNode... children) {
return new AndNode(this, children);
}
/**
* And.
*
* @param dataInputStream the data input stream
* @return the and node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final AndNode And(DataInputStream dataInputStream)
throws IOException {
return new AndNode(this, dataInputStream);
}
/**
* Boolean literal.
*
* @param literalValue the literal value
* @return the literal node boolean
*/
public LiteralNodeBoolean BooleanLiteral(boolean literalValue) {
return new LiteralNodeBoolean(this, literalValue);
}
/**
* Boolean literal.
*
* @param dataInputStream the data input stream
* @return the literal node boolean
* @throws IOException Signals that an I/O exception has occurred.
*/
public final LiteralNodeBoolean BooleanLiteral(DataInputStream dataInputStream)
throws IOException {
return new LiteralNodeBoolean(this, dataInputStream);
}
/**
* Boolean substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node boolean
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeBoolean BooleanSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeBoolean(this, dataInputStream);
}
/**
* Boolean substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node boolean
*/
public SubstitutionNodeBoolean BooleanSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeBoolean(this, substitutionFieldSpecification);
}
/**
* Concept.
*
* @param dataInputStream the data input stream
* @return the concept node with sequences
* @throws IOException Signals that an I/O exception has occurred.
*/
public final ConceptNodeWithSequences Concept(DataInputStream dataInputStream)
throws IOException {
return new ConceptNodeWithSequences(this, dataInputStream);
}
/**
* Concept.
*
* @param conceptSequence the concept sequence
* @return the concept node with sequences
*/
public final ConceptNodeWithSequences Concept(int conceptSequence) {
return new ConceptNodeWithSequences(this, conceptSequence);
}
/**
* Concept substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node concept
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeConcept ConceptSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeConcept(this, dataInputStream);
}
/**
* Concept substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node concept
*/
public SubstitutionNodeConcept ConceptSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeConcept(this, substitutionFieldSpecification);
}
/**
* Concept with uuids.
*
* @param dataInputStream the data input stream
* @return the concept node with uuids
* @throws IOException Signals that an I/O exception has occurred.
*/
public final ConceptNodeWithUuids ConceptWithUuids(DataInputStream dataInputStream)
throws IOException {
return new ConceptNodeWithUuids(this, dataInputStream);
}
/**
* Disjoint with.
*
* @param children the children
* @return the disjoint with node
*/
public DisjointWithNode DisjointWith(AbstractLogicNode... children) {
return new DisjointWithNode(this, children);
}
/**
* Disjoint with.
*
* @param dataInputStream the data input stream
* @return the disjoint with node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final DisjointWithNode DisjointWith(DataInputStream dataInputStream)
throws IOException {
return new DisjointWithNode(this, dataInputStream);
}
/**
* Feature.
*
* @param dataInputStream the data input stream
* @return the feature node with sequences
* @throws IOException Signals that an I/O exception has occurred.
*/
public final FeatureNodeWithSequences Feature(DataInputStream dataInputStream)
throws IOException {
return new FeatureNodeWithSequences(this, dataInputStream);
}
/**
* Feature.
*
* @param typeNid the type nid
* @param literal the literal
* @return the feature node with sequences
*/
public FeatureNodeWithSequences Feature(int typeNid, AbstractLogicNode literal) {
// check for LiteralNode or SubstitutionNodeLiteral
if ((literal instanceof LiteralNode) || (literal instanceof SubstitutionNodeLiteral)) {
return new FeatureNodeWithSequences(this, typeNid, literal);
}
throw new IllegalStateException("LogicNode must be of type LiteralNode or SubstitutionNodeLiteral. Found: " +
literal);
}
/**
* Feature with uuids.
*
* @param dataInputStream the data input stream
* @return the feature node with uuids
* @throws IOException Signals that an I/O exception has occurred.
*/
public final FeatureNodeWithUuids FeatureWithUuids(DataInputStream dataInputStream)
throws IOException {
return new FeatureNodeWithUuids(this, dataInputStream);
}
/**
* Float literal.
*
* @param dataInputStream the data input stream
* @return the literal node float
* @throws IOException Signals that an I/O exception has occurred.
*/
public final LiteralNodeFloat FloatLiteral(DataInputStream dataInputStream)
throws IOException {
return new LiteralNodeFloat(this, dataInputStream);
}
/**
* Float literal.
*
* @param literalValue the literal value
* @return the literal node float
*/
public LiteralNodeFloat FloatLiteral(float literalValue) {
return new LiteralNodeFloat(this, literalValue);
}
/**
* Float substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node float
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeFloat FloatSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeFloat(this, dataInputStream);
}
/**
* Float substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node float
*/
public SubstitutionNodeFloat FloatSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeFloat(this, substitutionFieldSpecification);
}
/**
* Instant literal.
*
* @param dataInputStream the data input stream
* @return the literal node instant
* @throws IOException Signals that an I/O exception has occurred.
*/
public final LiteralNodeInstant InstantLiteral(DataInputStream dataInputStream)
throws IOException {
return new LiteralNodeInstant(this, dataInputStream);
}
/**
* Instant literal.
*
* @param literalValue the literal value
* @return the literal node instant
*/
public LiteralNodeInstant InstantLiteral(Instant literalValue) {
return new LiteralNodeInstant(this, literalValue);
}
/**
* Instant substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node instant
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeInstant InstantSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeInstant(this, dataInputStream);
}
/**
* Instant substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node instant
*/
public SubstitutionNodeInstant InstantSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeInstant(this, substitutionFieldSpecification);
}
/**
* Integer literal.
*
* @param dataInputStream the data input stream
* @return the literal node integer
* @throws IOException Signals that an I/O exception has occurred.
*/
public final LiteralNodeInteger IntegerLiteral(DataInputStream dataInputStream)
throws IOException {
return new LiteralNodeInteger(this, dataInputStream);
}
/**
* Integer literal.
*
* @param literalValue the literal value
* @return the literal node integer
*/
public LiteralNodeInteger IntegerLiteral(int literalValue) {
return new LiteralNodeInteger(this, literalValue);
}
/**
* Integer substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node integer
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeInteger IntegerSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeInteger(this, dataInputStream);
}
/**
* Integer substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node integer
*/
public SubstitutionNodeInteger IntegerSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeInteger(this, substitutionFieldSpecification);
}
/**
* Necessary set.
*
* @param children the children
* @return the necessary set node
*/
public final NecessarySetNode NecessarySet(AbstractLogicNode... children) {
return new NecessarySetNode(this, children);
}
/**
* Necessary set.
*
* @param dataInputStream the data input stream
* @return the necessary set node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final NecessarySetNode NecessarySet(DataInputStream dataInputStream)
throws IOException {
return new NecessarySetNode(this, dataInputStream);
}
/**
* Or.
*
* @param children the children
* @return the or node
*/
public OrNode Or(AbstractLogicNode... children) {
return new OrNode(this, children);
}
/**
* Or.
*
* @param dataInputStream the data input stream
* @return the or node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final OrNode Or(DataInputStream dataInputStream)
throws IOException {
return new OrNode(this, dataInputStream);
}
/**
* Root.
*
* @param children the children
* @return the root node
*/
public RootNode Root(ConnectorNode... children) {
final RootNode rootNode = new RootNode(this, children);
this.rootNodeIndex = rootNode.getNodeIndex();
return rootNode;
}
/**
* Root.
*
* @param dataInputStream the data input stream
* @return the root node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final RootNode Root(DataInputStream dataInputStream)
throws IOException {
final RootNode rootNode = new RootNode(this, dataInputStream);
this.rootNodeIndex = rootNode.getNodeIndex();
return rootNode;
}
/**
* Some role.
*
* @param dataInputStream the data input stream
* @return the role node some with sequences
* @throws IOException Signals that an I/O exception has occurred.
*/
public final RoleNodeSomeWithSequences SomeRole(DataInputStream dataInputStream)
throws IOException {
return new RoleNodeSomeWithSequences(this, dataInputStream);
}
/**
* Some role.
*
* @param typeNid the type nid
* @param restriction the restriction
* @return the role node some with sequences
*/
public final RoleNodeSomeWithSequences SomeRole(int typeNid, AbstractLogicNode restriction) {
return new RoleNodeSomeWithSequences(this, typeNid, restriction);
}
/**
* Some role with uuids.
*
* @param dataInputStream the data input stream
* @return the role node some with uuids
* @throws IOException Signals that an I/O exception has occurred.
*/
public final RoleNodeSomeWithUuids SomeRoleWithUuids(DataInputStream dataInputStream)
throws IOException {
return new RoleNodeSomeWithUuids(this, dataInputStream);
}
/**
* String literal.
*
* @param dataInputStream the data input stream
* @return the literal node string
* @throws IOException Signals that an I/O exception has occurred.
*/
public final LiteralNodeString StringLiteral(DataInputStream dataInputStream)
throws IOException {
return new LiteralNodeString(this, dataInputStream);
}
/**
* String literal.
*
* @param literalValue the literal value
* @return the literal node string
*/
public LiteralNodeString StringLiteral(String literalValue) {
return new LiteralNodeString(this, literalValue);
}
/**
* String substitution.
*
* @param dataInputStream the data input stream
* @return the substitution node string
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SubstitutionNodeString StringSubstitution(DataInputStream dataInputStream)
throws IOException {
return new SubstitutionNodeString(this, dataInputStream);
}
/**
* String substitution.
*
* @param substitutionFieldSpecification the substitution field specification
* @return the substitution node string
*/
public SubstitutionNodeString StringSubstitution(SubstitutionFieldSpecification substitutionFieldSpecification) {
return new SubstitutionNodeString(this, substitutionFieldSpecification);
}
/**
* Sufficient set.
*
* @param children the children
* @return the sufficient set node
*/
public final SufficientSetNode SufficientSet(AbstractLogicNode... children) {
return new SufficientSetNode(this, children);
}
/**
* Sufficient set.
*
* @param dataInputStream the data input stream
* @return the sufficient set node
* @throws IOException Signals that an I/O exception has occurred.
*/
public final SufficientSetNode SufficientSet(DataInputStream dataInputStream)
throws IOException {
return new SufficientSetNode(this, dataInputStream);
}
/**
* Template.
*
* @param dataInputStream the data input stream
* @return the template node with sequences
* @throws IOException Signals that an I/O exception has occurred.
*/
public final TemplateNodeWithSequences Template(DataInputStream dataInputStream)
throws IOException {
return new TemplateNodeWithSequences(this, dataInputStream);
}
/**
* Template.
*
* @param templateConceptId the template concept id
* @param assemblageConceptId the assemblage concept id
* @return the template node with sequences
*/
public TemplateNodeWithSequences Template(int templateConceptId, int assemblageConceptId) {
return new TemplateNodeWithSequences(this, templateConceptId, assemblageConceptId);
}
/**
* Template with uuids.
*
* @param dataInputStream the data input stream
* @return the template node with uuids
* @throws IOException Signals that an I/O exception has occurred.
*/
public final TemplateNodeWithUuids TemplateWithUuids(DataInputStream dataInputStream)
throws IOException {
return new TemplateNodeWithUuids(this, dataInputStream);
}
/**
* Adds the node.
*
* @param logicNode the logic node
*/
public void addNode(LogicNode logicNode) {
logicNode.setNodeIndex((short) this.logicNodes.size());
this.logicNodes.add(logicNode);
}
/**
* Adds the nodes.
*
* @param another the logical expression to add nodes from.
* @param solution an array mapping from the nodeId in another to the nodeId
* in this expression. If the value of the solution element == -1, that node
* is not added to this logical expression, otherwise the value of the
* solution element is used for the nodeId in this logical expression.
* @param oldIds the list of nodeIds in the provided logical expression
* (another) to add to this logical expression on this invocation. Note that
* children of the nodes indicated by oldIds may be added by recursive calls
* to this method, if the oldId index in the solution array is >= 0.
* @return the LogicNode elements added as a result of this instance of the
* call, not including any children LogicNode elements added by recursive
* calls. Those children LogicNode elements can be retrieved by recursively
* traversing the children of these returned LogicNode elements.
*/
public final LogicNode[] addNodes(LogicalExpressionOchreImpl another, int[] solution, int... oldIds) {
return this.addNodesWithMap(another, solution, null, oldIds);
}
/**
* Contains.
*
* @param semantic the semantic
* @return true, if successful
*/
@Override
public boolean contains(NodeSemantic semantic) {
return this.logicNodes.stream()
.anyMatch((node) -> (node.getNodeSemantic() == semantic));
}
/**
* Equals.
*
* @param obj the obj
* @return true, if successful
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LogicalExpressionOchreImpl other = (LogicalExpressionOchreImpl) obj;
if (this.logicNodes == other.logicNodes) {
return true;
}
if (this.logicNodes != null) {
if (this.logicNodes.size() != other.logicNodes.size()) {
return false;
}
final TreeNodeVisitData graphVisitData = new TreeNodeVisitData(this.logicNodes.size());
depthFirstVisit(null, getRoot(), graphVisitData, 0);
return graphsEqual(this.getRoot(), other.getRoot(), 0, graphVisitData.getMaxDepth());
}
return true;
}
/**
* Find isomorphisms.
*
* @param another the another
* @return the isomorphic results
*/
@Override
public IsomorphicResults findIsomorphisms(LogicalExpression another) {
return new IsomorphicResultsBottomUp(this, another);
}
/**
* Hash code.
*
* @return the int
*/
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + this.conceptSequence;
return hash;
}
/**
* Process depth first.
*
* @param consumer the consumer
*/
@Override
public void processDepthFirst(BiConsumer<LogicNode, TreeNodeVisitData> consumer) {
processDepthFirst(getRoot(), consumer);
}
/**
* Process the fragment starting at root in a depth first manner.
*
* @param fragmentRoot the fragment root
* @param consumer the consumer
*/
@Override
public void processDepthFirst(LogicNode fragmentRoot, BiConsumer<LogicNode, TreeNodeVisitData> consumer) {
init();
final TreeNodeVisitData graphVisitData = new TreeNodeVisitData(this.logicNodes.size());
depthFirstVisit(consumer, fragmentRoot, graphVisitData, 0);
}
/**
* Sort.
*/
public void sort() {
this.logicNodes.forEach((node) -> node.sort());
}
/**
* To string.
*
* @return the string
*/
@Override
public String toString() {
return toString("");
}
/**
* To string.
*
* @param nodeIdSuffix the node id suffix
* @return the string
*/
@Override
public String toString(String nodeIdSuffix) {
final StringBuilder builder = new StringBuilder();
processDepthFirst((LogicNode logicNode,
TreeNodeVisitData graphVisitData) -> {
for (int i = 0; i < graphVisitData.getDistance(logicNode.getNodeIndex()); i++) {
builder.append(" ");
}
builder.append(logicNode.toString(nodeIdSuffix));
builder.append("\n");
});
return builder.toString();
}
/**
* Depth first visit.
*
* @param consumer the consumer
* @param logicNode the logic node
* @param graphVisitData the graph visit data
* @param depth the depth
*/
protected void depthFirstVisit(BiConsumer<LogicNode, TreeNodeVisitData> consumer,
LogicNode logicNode,
TreeNodeVisitData graphVisitData,
int depth) {
if (depth > 100) {
// toString depends on this method, so we can't include this.toString() in the exception...
throw new RuntimeException("Depth limit exceeded for logicNode: " + logicNode); // + " in graph: " + this);
}
graphVisitData.startNodeVisit(logicNode.getNodeIndex(), depth);
final ConceptSequenceSet conceptsAtNodeOrAbove = new ConceptSequenceSet();
logicNode.addConceptsReferencedByNode(conceptsAtNodeOrAbove);
graphVisitData.getConceptsReferencedAtNodeOrAbove(logicNode.getNodeIndex());
logicNode.addConceptsReferencedByNode(
ConceptSequenceSet.of(graphVisitData.getConceptsReferencedAtNodeOrAbove(logicNode.getNodeIndex())));
conceptsAtNodeOrAbove.addAll(
graphVisitData.getConceptsReferencedAtNodeOrAbove(
graphVisitData.getPredecessorSequence(logicNode.getNodeIndex())));
graphVisitData.setConceptsReferencedAtNodeOrAbove(logicNode.getNodeIndex(), conceptsAtNodeOrAbove);
if (consumer != null) {
consumer.accept(logicNode, graphVisitData);
}
if (logicNode.getChildren().length == 0) {
graphVisitData.setLeafNode(logicNode.getNodeIndex());
} else {
int siblingGroupSequence;
switch (logicNode.getNodeSemantic()) {
case AND:
case OR:
case SUFFICIENT_SET:
case NECESSARY_SET:
case DISJOINT_WITH:
case DEFINITION_ROOT:
siblingGroupSequence = logicNode.getNodeIndex();
break;
default:
siblingGroupSequence = graphVisitData.getSiblingGroupForSequence(logicNode.getNodeIndex());
}
for (final LogicNode child: logicNode.getChildren()) {
graphVisitData.setSiblingGroupForSequence(child.getNodeIndex(), siblingGroupSequence);
graphVisitData.setPredecessorSequence(child.getNodeIndex(), logicNode.getNodeIndex());
depthFirstVisit(consumer, child, graphVisitData, depth + 1);
}
}
graphVisitData.endNodeVisit(logicNode.getNodeIndex());
}
/**
* Inits the.
*/
protected void init() {
this.logicNodes.trimToSize();
}
/**
* Adds the nodes with map.
*
* @param another the logical expression to add nodes from.
* @param solution an array mapping from the nodeId in another to the nodeId
* in this expression. If the value of the solution element == -1, that node
* is not added to this logical expression, otherwise the value of the
* solution element is used for the nodeId in this logical expression.
* @param anotherToThisNodeIdMap contains a mapping from nodeId in another to nodeId in this constructed expression.
* @param oldIds the list of nodeIds in the provided logical expression
* (another) to add to this logical expression on this invocation. Note that
* children of the nodes indicated by oldIds may be added by recursive calls
* to this method, if the oldId index in the solution array is >= 0.
* @return the LogicNode elements added as a result of this instance of the
* call, not including any children LogicNode elements added by recursive
* calls. Those children LogicNode elements can be retrieved by recursively
* traversing the children of these returned LogicNode elements.
*/
private LogicNode[] addNodesWithMap(LogicalExpressionOchreImpl another,
int[] solution,
int[] anotherToThisNodeIdMap,
int... oldIds) {
final AbstractLogicNode[] results = new AbstractLogicNode[oldIds.length];
for (int i = 0; i < oldIds.length; i++) {
final LogicNode oldLogicNode = another.getNode(oldIds[i]);
switch (oldLogicNode.getNodeSemantic()) {
case DEFINITION_ROOT:
results[i] = Root(Arrays.stream(addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter( // the int[] of oldIds to add to the expression
(oldChildNode) -> solution[oldChildNode.getNodeIndex()] >=
0 // if the solution element == -1, filter out
)
.mapToInt(
(oldChildNode) -> oldChildNode.getNodeIndex() // the nodeId in the original expression
)
.toArray() // create the oldIds passed into the addNodes recursive call
) // end of addNodes parameters; returns LogicNode[]
)
.map((LogicNode t) -> (ConnectorNode) t)
.toArray(
ConnectorNode[]::new) // convert LogicNode[] to ConnectorNode[] to pass into the Root method call.
);
this.rootNodeIndex = results[i].getNodeIndex();
break;
case NECESSARY_SET:
results[i] = NecessarySet((AbstractLogicNode[]) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray()));
break;
case SUFFICIENT_SET:
results[i] = SufficientSet((AbstractLogicNode[]) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray()));
break;
case AND:
results[i] = And((AbstractLogicNode[]) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray()));
break;
case OR:
results[i] = Or((AbstractLogicNode[]) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray()));
break;
case DISJOINT_WITH:
results[i] = DisjointWith((AbstractLogicNode[]) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray()));
break;
case ROLE_ALL:
results[i] = AllRole(((TypedNodeWithSequences) oldLogicNode).getTypeConceptSequence(),
(AbstractLogicNode) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray())[0]);
break;
case ROLE_SOME:
results[i] = SomeRole(((TypedNodeWithSequences) oldLogicNode).getTypeConceptSequence(),
(AbstractLogicNode) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray())[0]);
break;
case FEATURE:
results[i] = Feature(((TypedNodeWithSequences) oldLogicNode).getTypeConceptSequence(),
(AbstractLogicNode) addNodesWithMap(another,
solution,
anotherToThisNodeIdMap,
oldLogicNode.getChildStream()
.filter((oldChildNode) -> solution[oldChildNode.getNodeIndex()] >= 0)
.mapToInt((oldChildNode) -> oldChildNode.getNodeIndex())
.toArray())[0]);
break;
case LITERAL_BOOLEAN:
results[i] = BooleanLiteral(((LiteralNodeBoolean) oldLogicNode).getLiteralValue());
break;
case LITERAL_FLOAT:
results[i] = FloatLiteral(((LiteralNodeFloat) oldLogicNode).getLiteralValue());
break;
case LITERAL_INSTANT:
results[i] = InstantLiteral(((LiteralNodeInstant) oldLogicNode).getLiteralValue());
break;
case LITERAL_INTEGER:
results[i] = IntegerLiteral(((LiteralNodeInteger) oldLogicNode).getLiteralValue());
break;
case LITERAL_STRING:
results[i] = StringLiteral(((LiteralNodeString) oldLogicNode).getLiteralValue());
break;
case CONCEPT:
results[i] = Concept(((ConceptNodeWithSequences) oldLogicNode).getConceptSequence());
break;
case TEMPLATE:
results[i] = Template(((TemplateNodeWithSequences) oldLogicNode).getTemplateConceptSequence(),
((TemplateNodeWithSequences) oldLogicNode).getAssemblageConceptSequence());
break;
case SUBSTITUTION_BOOLEAN:
results[i] = BooleanSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
case SUBSTITUTION_CONCEPT:
results[i] = ConceptSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
case SUBSTITUTION_FLOAT:
results[i] = FloatSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
case SUBSTITUTION_INSTANT:
results[i] = InstantSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
case SUBSTITUTION_INTEGER:
results[i] = IntegerSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
case SUBSTITUTION_STRING:
results[i] = StringSubstitution(((SubstitutionNode) oldLogicNode).getSubstitutionFieldSpecification());
break;
default:
throw new UnsupportedOperationException("Can't handle: " + oldLogicNode.getNodeSemantic());
}
if (anotherToThisNodeIdMap != null) {
anotherToThisNodeIdMap[oldLogicNode.getNodeIndex()] = results[i].getNodeIndex();
}
}
return results;
}
/**
* Graphs equal.
*
* @param g1 the g 1
* @param g2 the g 2
* @param depth the depth
* @param maxDepth the max depth
* @return true, if successful
*/
private boolean graphsEqual(AbstractLogicNode g1, AbstractLogicNode g2, int depth, int maxDepth) {
if (g1.equals(g2)) {
final AbstractLogicNode[] g1children = g1.getChildren();
final AbstractLogicNode[] g2children = g2.getChildren();
if (g1children.length != g2children.length) {
return false;
}
if (g1children.length == 0) {
return true;
}
final HashMap<Set<UUID>, IntArrayList> uuidSetNodeListMap = new HashMap<>();
int depthToTest = 0;
while ((uuidSetNodeListMap.size() < g1children.length) && (depthToTest < maxDepth - depth)) {
depthToTest++;
uuidSetNodeListMap.clear();
for (final AbstractLogicNode child: g1children) {
final Set<UUID> nodeUuidSetForDepth = child.getNodeUuidSetForDepth(depthToTest);
if (!uuidSetNodeListMap.containsKey(nodeUuidSetForDepth)) {
final IntArrayList nodeList = new IntArrayList();
nodeList.add(child.getNodeIndex());
uuidSetNodeListMap.put(nodeUuidSetForDepth, nodeList);
} else {
uuidSetNodeListMap.get(nodeUuidSetForDepth)
.add(child.getNodeIndex());
}
}
}
// need to try all combinations
for (final AbstractLogicNode g2Child: g2children) {
final Set<UUID> nodeUuidSetForDepth = g2Child.getNodeUuidSetForDepth(depthToTest);
final IntArrayList possibleMatches = uuidSetNodeListMap.get(nodeUuidSetForDepth);
if (possibleMatches == null) {
return false;
}
int match = -1;
for (final int possibleMatchIndex: possibleMatches.elements()) {
if (graphsEqual((AbstractLogicNode) this.logicNodes.get(possibleMatchIndex),
g2Child,
depth + 1,
maxDepth)) {
match = possibleMatchIndex;
break;
}
}
if (match == -1) {
return false;
}
possibleMatches.delete(match);
}
return true;
}
return false;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the concept sequence.
*
* @return the concept sequence
*/
@Override
public int getConceptSequence() {
return this.conceptSequence;
}
/**
* Gets the data.
*
* @param dataTarget the data target
* @return the data
*/
@Override
public byte[][] getData(DataTarget dataTarget) {
init();
final byte[][] byteArrayArray = new byte[this.logicNodes.size()][];
for (int index = 0; index < byteArrayArray.length; index++) {
byteArrayArray[index] = this.logicNodes.get(index)
.getBytes(dataTarget);
}
return byteArrayArray;
}
/**
* Checks if meaningful.
*
* @return true, if meaningful
*/
@Override
public boolean isMeaningful() {
return this.logicNodes.stream()
.anyMatch((node) -> (MEANINGFUL_NODE_SEMANTICS.contains(node.getNodeSemantic())));
}
/**
* Gets the node.
*
* @param nodeIndex the node index
* @return the node
*/
@Override
public LogicNode getNode(int nodeIndex) {
return this.logicNodes.get(nodeIndex);
}
/**
* Gets the node count.
*
* @return the node count
*/
@Override
public int getNodeCount() {
return this.logicNodes.size();
}
/**
* Gets the nodes of type.
*
* @param semantic the semantic
* @return the nodes of type
*/
@Override
public Stream<LogicNode> getNodesOfType(NodeSemantic semantic) {
return this.logicNodes.stream()
.filter((node) -> (node.getNodeSemantic() == semantic));
}
/**
* Gets the root.
*
* @return the root
*/
@Override
public final RootNode getRoot() {
if (this.logicNodes.isEmpty()) {
return Root();
}
return (RootNode) this.logicNodes.get(this.rootNodeIndex);
}
}