/* * 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.provider.logic; //~--- JDK imports ------------------------------------------------------------ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; //~--- non-JDK imports -------------------------------------------------------- import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.glassfish.hk2.runlevel.RunLevel; import org.jvnet.hk2.annotations.Service; import sh.isaac.api.DataSource; import sh.isaac.api.Get; import sh.isaac.api.chronicle.LatestVersion; import sh.isaac.api.classifier.ClassifierService; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.sememe.SememeChronology; import sh.isaac.api.component.sememe.SememeSnapshotService; import sh.isaac.api.component.sememe.version.LogicGraphSememe; import sh.isaac.api.component.sememe.version.SememeVersion; import sh.isaac.api.coordinate.EditCoordinate; import sh.isaac.api.coordinate.LogicCoordinate; import sh.isaac.api.coordinate.PremiseType; import sh.isaac.api.coordinate.StampCoordinate; import sh.isaac.api.dag.Graph; import sh.isaac.api.dag.Node; import sh.isaac.api.logic.IsomorphicResults; import sh.isaac.api.logic.LogicNode; import sh.isaac.api.logic.LogicService; import sh.isaac.api.logic.LogicalExpression; import sh.isaac.api.logic.NodeSemantic; import sh.isaac.api.relationship.RelationshipAdaptorChronicleKey; import sh.isaac.api.relationship.RelationshipVersionAdaptor; import sh.isaac.MetaData; import sh.isaac.model.configuration.LogicCoordinates; import sh.isaac.model.logic.LogicalExpressionOchreImpl; import sh.isaac.model.logic.node.AndNode; import sh.isaac.model.logic.node.internal.ConceptNodeWithSequences; import sh.isaac.model.logic.node.internal.RoleNodeSomeWithSequences; import sh.isaac.model.relationship.RelationshipAdaptorChronicleKeyImpl; import sh.isaac.model.relationship.RelationshipAdaptorChronologyImpl; import sh.isaac.model.relationship.RelationshipVersionAdaptorImpl; import sh.isaac.model.sememe.version.LogicGraphSememeImpl; import sh.isaac.provider.logic.csiro.classify.ClassifierProvider; //~--- classes ---------------------------------------------------------------- /** * The Class LogicProvider. * * @author kec */ @Service(name = "logic provider") @RunLevel(value = 2) public class LogicProvider implements LogicService { /** The Constant log. */ private static final Logger log = LogManager.getLogger(); /** The Constant classifierServiceMap. */ private static final Map<ClassifierServiceKey, ClassifierService> classifierServiceMap = new ConcurrentHashMap<>(); //~--- constructors -------------------------------------------------------- /** * Instantiates a new logic provider. */ private LogicProvider() { // For HK2 log.info("logic provider constructed"); } //~--- methods ------------------------------------------------------------- /** * Creates the isa rel. * * @param originSequence the origin sequence * @param destinationNode the destination node * @param stampSequence the stamp sequence * @param premiseType the premise type * @return the relationship version adaptor impl */ private RelationshipVersionAdaptorImpl createIsaRel(int originSequence, ConceptNodeWithSequences destinationNode, int stampSequence, PremiseType premiseType) { final int destinationSequence = Get.identifierService() .getConceptSequence(destinationNode.getConceptSequence()); final int typeSequence = MetaData.IS_A.getConceptSequence(); final int group = 0; final RelationshipAdaptorChronicleKeyImpl key = new RelationshipAdaptorChronicleKeyImpl(originSequence, destinationSequence, typeSequence, group, premiseType, destinationNode.getNodeIndex()); return new RelationshipVersionAdaptorImpl(key, stampSequence); } /** * Creates the some role. * * @param originSequence the origin sequence * @param someNode the some node * @param stampSequence the stamp sequence * @param premiseType the premise type * @param roleGroup the role group * @return the stream */ private Stream<RelationshipVersionAdaptorImpl> createSomeRole(int originSequence, RoleNodeSomeWithSequences someNode, int stampSequence, PremiseType premiseType, int roleGroup) { final Stream.Builder<RelationshipVersionAdaptorImpl> roleStream = Stream.builder(); if (someNode.getTypeConceptSequence() == MetaData.ROLE_GROUP.getConceptSequence()) { final AndNode andNode = (AndNode) someNode.getOnlyChild(); andNode.getChildStream().forEach((roleGroupSomeNode) -> { if (roleGroupSomeNode instanceof RoleNodeSomeWithSequences) { createSomeRole(originSequence, (RoleNodeSomeWithSequences) roleGroupSomeNode, stampSequence, premiseType, someNode.getNodeIndex()).forEach((adaptor) -> { roleStream.add(adaptor); }); } else { // TODO Keith - not sure what to do here... getting a FeatureNodeWithSequences } }); } else { final LogicNode restriction = someNode.getOnlyChild(); int destinationSequence; if (restriction.getNodeSemantic() == NodeSemantic.CONCEPT) { final ConceptNodeWithSequences restrictionNode = (ConceptNodeWithSequences) someNode.getOnlyChild(); destinationSequence = Get.identifierService() .getConceptSequence(restrictionNode.getConceptSequence()); } else { destinationSequence = MetaData.ANONYMOUS_CONCEPT.getConceptSequence(); } final int typeSequence = Get.identifierService() .getConceptSequence(someNode.getTypeConceptSequence()); final RelationshipAdaptorChronicleKeyImpl key = new RelationshipAdaptorChronicleKeyImpl(originSequence, destinationSequence, typeSequence, roleGroup, premiseType, someNode.getNodeIndex()); roleStream.accept(new RelationshipVersionAdaptorImpl(key, stampSequence)); } return roleStream.build(); } /** * Extract relationship adaptors. * * @param logicGraphChronology the logic graph chronology * @param premiseType the premise type * @return the stream */ private Stream<RelationshipVersionAdaptorImpl> extractRelationshipAdaptors( SememeChronology<LogicGraphSememe<?>> logicGraphChronology, PremiseType premiseType) { final Stream.Builder<RelationshipVersionAdaptorImpl> streamBuilder = Stream.builder(); // one graph for each origin... Usually only one. for (final Graph<? extends LogicGraphSememe<?>> versionGraph: logicGraphChronology.getVersionGraphList()) { final Node<? extends LogicGraphSememe<?>> node = versionGraph.getRoot(); processNode(node, null, streamBuilder, premiseType); } final int originConceptSequence = Get.identifierService() .getConceptSequence(logicGraphChronology.getReferencedComponentNid()); logicGraphChronology.getVersionList().forEach((logicVersion) -> { final LogicalExpressionOchreImpl expression = new LogicalExpressionOchreImpl(logicVersion.getGraphData(), DataSource.INTERNAL, originConceptSequence); }); return streamBuilder.build(); } /** * Generate rel adaptor chronicles. * * @param logicalDef the logical def * @param conceptOriginRelationshipMap the concept origin relationship map * @param premiseType the premise type */ private void generateRelAdaptorChronicles(SememeChronology<? extends SememeVersion> logicalDef, HashMap<RelationshipAdaptorChronicleKey, RelationshipAdaptorChronologyImpl> conceptOriginRelationshipMap, PremiseType premiseType) { generateRelAdaptorChronicles(Integer.MAX_VALUE, logicalDef, conceptOriginRelationshipMap, premiseType); } /** * Generate rel adaptor chronicles. * * @param conceptDestinationSequence the concept destination sequence * @param logicalDef the logical def * @param conceptOriginRelationshipMap the concept origin relationship map * @param premiseType the premise type */ private void generateRelAdaptorChronicles(int conceptDestinationSequence, SememeChronology<? extends SememeVersion> logicalDef, HashMap<RelationshipAdaptorChronicleKey, RelationshipAdaptorChronologyImpl> conceptOriginRelationshipMap, PremiseType premiseType) { extractRelationshipAdaptors((SememeChronology<LogicGraphSememe<?>>) logicalDef, premiseType).forEach((relAdaptor) -> { if ((conceptDestinationSequence == Integer.MAX_VALUE) || (conceptDestinationSequence == relAdaptor.getDestinationSequence())) { RelationshipAdaptorChronologyImpl chronicle = conceptOriginRelationshipMap.get(relAdaptor.getChronicleKey()); if (chronicle == null) { // compute nid, combine the sememe sequence + the node sequence from which final int topBits = relAdaptor.getNodeSequence() << 24; final int adaptorNid = logicalDef.getSememeSequence() + topBits; chronicle = new RelationshipAdaptorChronologyImpl(adaptorNid, logicalDef.getNid()); conceptOriginRelationshipMap.put(relAdaptor.getChronicleKey(), chronicle); } relAdaptor.setChronology(chronicle); chronicle.getVersionList() .add(relAdaptor); } }); } /** * Process node. * * @param node the node * @param previousExpression the previous expression * @param streamBuilder the stream builder * @param premiseType the premise type */ private void processNode(Node<? extends LogicGraphSememe<?>> node, LogicalExpression previousExpression, Stream.Builder<RelationshipVersionAdaptorImpl> streamBuilder, PremiseType premiseType) { final LogicalExpression newExpression = node.getData() .getLogicalExpression(); final int stampSequence = node.getData() .getStampSequence(); final int inactiveStampSequence = Get.stampService() .getRetiredStampSequence(stampSequence); if (previousExpression == null) { processRootExpression(newExpression, streamBuilder, stampSequence, premiseType); } else { final IsomorphicResults comparison = newExpression.findIsomorphisms(previousExpression); comparison.getAddedRelationshipRoots() .forEach((addedRelRoot) -> processRelNode(addedRelRoot, streamBuilder, newExpression, stampSequence, premiseType)); comparison.getDeletedRelationshipRoots() .forEach((addedRelRoot) -> processRelNode(addedRelRoot, streamBuilder, newExpression, inactiveStampSequence, premiseType)); } for (final Node<? extends LogicGraphSememe<?>> child: node.getChildren()) { processNode(child, newExpression, streamBuilder, premiseType); } } /** * Process rel node. * * @param aLogicNode the a logic node * @param streamBuilder the stream builder * @param expression the expression * @param stampSequence the stamp sequence * @param premiseType the premise type * @throws UnsupportedOperationException the unsupported operation exception */ private void processRelNode(LogicNode aLogicNode, Stream.Builder<RelationshipVersionAdaptorImpl> streamBuilder, LogicalExpression expression, int stampSequence, PremiseType premiseType) throws UnsupportedOperationException { switch (aLogicNode.getNodeSemantic()) { case CONCEPT: streamBuilder.accept(createIsaRel(expression.getConceptSequence(), (ConceptNodeWithSequences) aLogicNode, stampSequence, premiseType)); break; case ROLE_SOME: createSomeRole(expression.getConceptSequence(), (RoleNodeSomeWithSequences) aLogicNode, stampSequence, premiseType, 0).forEach((someRelAdaptor) -> { streamBuilder.accept(someRelAdaptor); }); break; case FEATURE: break; // TODO Keith, not sure how this should be handled default: throw new UnsupportedOperationException("Can't handle: " + aLogicNode.getNodeSemantic()); } } /** * Process root expression. * * @param expression the expression * @param streamBuilder the stream builder * @param stampSequence the stamp sequence * @param premiseType the premise type */ private void processRootExpression(LogicalExpression expression, Stream.Builder<RelationshipVersionAdaptorImpl> streamBuilder, int stampSequence, PremiseType premiseType) { expression.getRoot().getChildStream().forEach((necessaryOrSufficientSet) -> { necessaryOrSufficientSet.getChildStream() .forEach((LogicNode andOrOrLogicNode) -> andOrOrLogicNode.getChildStream() .forEach((LogicNode aLogicNode) -> { processRelNode(aLogicNode, streamBuilder, expression, stampSequence, premiseType); })); }); } /** * Start me. */ @PostConstruct private void startMe() { log.info("Starting LogicProvider."); } /** * Stop me. */ @PreDestroy private void stopMe() { log.info("Stopping LogicProvider."); } //~--- get methods --------------------------------------------------------- /** * Gets the classifier service. * * @param stampCoordinate the stamp coordinate * @param logicCoordinate the logic coordinate * @param editCoordinate the edit coordinate * @return the classifier service */ @Override public ClassifierService getClassifierService(StampCoordinate stampCoordinate, LogicCoordinate logicCoordinate, EditCoordinate editCoordinate) { final ClassifierServiceKey key = new ClassifierServiceKey(stampCoordinate, logicCoordinate, editCoordinate); if (!classifierServiceMap.containsKey(key)) { classifierServiceMap.putIfAbsent(key, new ClassifierProvider(stampCoordinate, logicCoordinate, editCoordinate)); } return classifierServiceMap.get(key); } /** * Gets the logical expression. * * @param conceptId the concept id * @param logicAssemblageId the logic assemblage id * @param stampCoordinate the stamp coordinate * @return the logical expression */ @Override public Optional<LatestVersion<? extends LogicalExpression>> getLogicalExpression(int conceptId, int logicAssemblageId, StampCoordinate stampCoordinate) { final SememeSnapshotService<LogicGraphSememeImpl> ssp = Get.sememeService() .getSnapshot(LogicGraphSememeImpl.class, stampCoordinate); final List<LatestVersion<LogicalExpressionOchreImpl>> latestVersions = ssp.getLatestSememeVersionsForComponentFromAssemblage(conceptId, logicAssemblageId) .map((LatestVersion<LogicGraphSememeImpl> lgs) -> { final LogicalExpressionOchreImpl expressionValue = new LogicalExpressionOchreImpl(lgs.value().getGraphData(), DataSource.INTERNAL, lgs.value().getReferencedComponentNid()); final LatestVersion<LogicalExpressionOchreImpl> latestExpressionValue = new LatestVersion<>(expressionValue); if (lgs.contradictions() .isPresent()) { lgs.contradictions().get().forEach((LogicGraphSememeImpl contradiction) -> { final LogicalExpressionOchreImpl contradictionValue = new LogicalExpressionOchreImpl(contradiction.getGraphData(), DataSource.INTERNAL, contradiction.getReferencedComponentNid()); latestExpressionValue.addLatest(contradictionValue); }); } return latestExpressionValue; }) .collect(Collectors.toList()); if (latestVersions.isEmpty()) { return Optional.empty(); } if (latestVersions.size() > 1) { throw new IllegalStateException("More than one LogicGraphSememeImpl for concept in assemblage: " + latestVersions); } return Optional.of(latestVersions.get(0)); } /** * Gets the relationship adaptors originating with concept. * * @param conceptChronology the concept chronology * @return the relationship adaptors originating with concept */ @Override public Stream<? extends SememeChronology<? extends RelationshipVersionAdaptor<?>>> getRelationshipAdaptorsOriginatingWithConcept( ConceptChronology<?> conceptChronology) { return getRelationshipAdaptorsOriginatingWithConcept(conceptChronology, LogicCoordinates.getStandardElProfile()); } /** * Gets the relationship adaptors originating with concept. * * @param conceptChronology the concept chronology * @param logicCoordinate the logic coordinate * @return the relationship adaptors originating with concept */ @Override public Stream<? extends SememeChronology<? extends RelationshipVersionAdaptor<?>>> getRelationshipAdaptorsOriginatingWithConcept( ConceptChronology<?> conceptChronology, LogicCoordinate logicCoordinate) { final Stream.Builder<RelationshipAdaptorChronologyImpl> streamBuilder = Stream.builder(); final HashMap<RelationshipAdaptorChronicleKey, RelationshipAdaptorChronologyImpl> conceptOriginRelationshipMap = new HashMap<>(); final List<SememeChronology<? extends SememeVersion>> statedDefinitions = Get.sememeService() .getSememesForComponentFromAssemblage( conceptChronology.getNid(), logicCoordinate.getStatedAssemblageSequence()) .collect(Collectors.toList()); final List<SememeChronology<? extends SememeVersion>> inferredDefinitions = Get.sememeService() .getSememesForComponentFromAssemblage( conceptChronology.getNid(), logicCoordinate.getInferredAssemblageSequence()) .collect(Collectors.toList()); statedDefinitions.forEach((statedDef) -> { generateRelAdaptorChronicles(statedDef, conceptOriginRelationshipMap, PremiseType.STATED); }); inferredDefinitions.forEach((inferredDef) -> { generateRelAdaptorChronicles(inferredDef, conceptOriginRelationshipMap, PremiseType.INFERRED); }); conceptOriginRelationshipMap.values().stream().forEach((relAdaptor) -> { streamBuilder.accept(relAdaptor); }); return streamBuilder.build(); } /** * Gets the relationship adaptors with concept as destination. * * @param conceptChronology the concept chronology * @return the relationship adaptors with concept as destination */ @Override public Stream<? extends SememeChronology<? extends RelationshipVersionAdaptor<?>>> getRelationshipAdaptorsWithConceptAsDestination( ConceptChronology<?> conceptChronology) { return getRelationshipAdaptorsWithConceptAsDestination(conceptChronology, LogicCoordinates.getStandardElProfile()); } /** * Gets the relationship adaptors with concept as destination. * * @param conceptChronology the concept chronology * @param logicCoordinate the logic coordinate * @return the relationship adaptors with concept as destination */ @Override public Stream<? extends SememeChronology<? extends RelationshipVersionAdaptor<?>>> getRelationshipAdaptorsWithConceptAsDestination( ConceptChronology<?> conceptChronology, LogicCoordinate logicCoordinate) { final List<SememeChronology<? extends SememeVersion>> statedDefinitions = new ArrayList<>(); final List<SememeChronology<? extends SememeVersion>> inferredDefinitions = new ArrayList<>(); final Stream.Builder<RelationshipAdaptorChronologyImpl> streamBuilder = Stream.builder(); final HashMap<RelationshipAdaptorChronicleKey, RelationshipAdaptorChronologyImpl> conceptDestinationRelationshipMap = new HashMap<>(); Get.taxonomyService() .getAllRelationshipOriginSequences(conceptChronology.getConceptSequence()) .forEach((originConceptSequence) -> { statedDefinitions.addAll(Get.sememeService() .getSememesForComponentFromAssemblage(originConceptSequence, logicCoordinate.getStatedAssemblageSequence()) .collect(Collectors.toList())); inferredDefinitions.addAll(Get.sememeService() .getSememesForComponentFromAssemblage(originConceptSequence, logicCoordinate.getInferredAssemblageSequence()) .collect(Collectors.toList())); }); statedDefinitions.forEach((statedDef) -> { generateRelAdaptorChronicles(conceptChronology.getConceptSequence(), statedDef, conceptDestinationRelationshipMap, PremiseType.STATED); }); inferredDefinitions.forEach((inferredDef) -> { generateRelAdaptorChronicles(conceptChronology.getConceptSequence(), inferredDef, conceptDestinationRelationshipMap, PremiseType.INFERRED); }); conceptDestinationRelationshipMap.values().stream().forEach((relAdaptor) -> { streamBuilder.accept(relAdaptor); }); return streamBuilder.build(); } //~--- inner classes ------------------------------------------------------- /** * The Class ClassifierServiceKey. */ private static class ClassifierServiceKey { /** The stamp coordinate. */ StampCoordinate stampCoordinate; /** The logic coordinate. */ LogicCoordinate logicCoordinate; /** The edit coordinate. */ EditCoordinate editCoordinate; //~--- constructors ----------------------------------------------------- /** * Instantiates a new classifier service key. * * @param stampCoordinate the stamp coordinate * @param logicCoordinate the logic coordinate * @param editCoordinate the edit coordinate */ public ClassifierServiceKey(StampCoordinate stampCoordinate, LogicCoordinate logicCoordinate, EditCoordinate editCoordinate) { this.stampCoordinate = stampCoordinate; this.logicCoordinate = logicCoordinate; this.editCoordinate = editCoordinate; } //~--- methods ---------------------------------------------------------- /** * 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 ClassifierServiceKey other = (ClassifierServiceKey) obj; if (!Objects.equals(this.stampCoordinate, other.stampCoordinate)) { return false; } if (!Objects.equals(this.logicCoordinate, other.logicCoordinate)) { return false; } return Objects.equals(this.editCoordinate, other.editCoordinate); } /** * Hash code. * * @return the int */ @Override public int hashCode() { int hash = 3; hash = 59 * hash + Objects.hashCode(this.logicCoordinate); return hash; } } }