/* * 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.taxonomy; //~--- JDK imports ------------------------------------------------------------ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.IntStream; //~--- non-JDK imports -------------------------------------------------------- import org.apache.mahout.math.function.IntObjectProcedure; import org.apache.mahout.math.list.IntArrayList; import org.apache.mahout.math.map.OpenIntObjectHashMap; import sh.isaac.api.Get; import sh.isaac.api.bootstrap.TermAux; import sh.isaac.api.collections.ConceptSequenceSet; import sh.isaac.api.coordinate.StampCoordinate; import sh.isaac.api.coordinate.TaxonomyCoordinate; import sh.isaac.api.snapshot.calculator.RelativePositionCalculator; import sh.isaac.provider.taxonomy.TypeStampTaxonomyRecords.TypeStampTaxonomyRecord; //~--- classes ---------------------------------------------------------------- /** * A {@code TaxonomyRecordUnpacked} is the value for a map where the key is the * concept sequence for a concept in the taxonomy, and the value is a map to * other concept sequences, and the associated stamps and taxonomy flags for * these other concept sequences. From the stamp value and the taxonomy flags, * all historic taxonomic associations (parent, child, stated, and inferred) can * be computed. * * * origin concept sequence [1 -> n] {destination concept sequence [1 -> n] stamp * + inferred + stated + parent + child} * <p> * <p> * Created by kec on 11/8/14. */ public class TaxonomyRecordUnpacked { /** key = origin concept sequence; value = TypeStampTaxonomyRecords. */ private final OpenIntObjectHashMap<TypeStampTaxonomyRecords> conceptSequenceRecordMap = new OpenIntObjectHashMap<>(11); //~--- constructors -------------------------------------------------------- /** * Instantiates a new taxonomy record unpacked. */ public TaxonomyRecordUnpacked() {} /** * Instantiates a new taxonomy record unpacked. * * @param recordArray the record array */ public TaxonomyRecordUnpacked(int[] recordArray) { if (recordArray != null) { int index = 0; while (index < recordArray.length) { final int conceptSequence = recordArray[index] & TaxonomyRecordPrimitive.SEQUENCE_BIT_MASK; final int length = recordArray[index] >>> 24; final TypeStampTaxonomyRecords record = new TypeStampTaxonomyRecords(recordArray, index); index += length; this.conceptSequenceRecordMap.put(conceptSequence, record); } } } //~--- methods ------------------------------------------------------------- /** * Adds the concept sequence stamp records. * * @param conceptSequence the concept sequence * @param newRecord the new record */ public void addConceptSequenceStampRecords(int conceptSequence, TypeStampTaxonomyRecords newRecord) { if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { final TypeStampTaxonomyRecords oldRecord = this.conceptSequenceRecordMap.get(conceptSequence); oldRecord.merge(newRecord); } else { this.conceptSequenceRecordMap.put(conceptSequence, newRecord); } } /** * Adds the stamp record. * * @param destinationSequence the destination sequence * @param typeSequence the type sequence * @param stamp the stamp * @param recordFlags the record flags */ public void addStampRecord(int destinationSequence, int typeSequence, int stamp, int recordFlags) { if (destinationSequence < 0) { destinationSequence = Get.identifierService() .getConceptSequence(destinationSequence); } if (typeSequence < 0) { typeSequence = Get.identifierService() .getConceptSequence(typeSequence); } TypeStampTaxonomyRecords conceptSequenceStampRecordsUnpacked; if (this.conceptSequenceRecordMap.containsKey(destinationSequence)) { conceptSequenceStampRecordsUnpacked = this.conceptSequenceRecordMap.get(destinationSequence); } else { conceptSequenceStampRecordsUnpacked = new TypeStampTaxonomyRecords(); this.conceptSequenceRecordMap.put(destinationSequence, conceptSequenceStampRecordsUnpacked); } conceptSequenceStampRecordsUnpacked.addStampRecord(typeSequence, stamp, recordFlags); } /** * Concept satisfies stamp. * * @param conceptSequence the concept sequence * @param stampCoordinate the stamp coordinate * @return true, if successful */ public boolean conceptSatisfiesStamp(int conceptSequence, StampCoordinate stampCoordinate) { final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(stampCoordinate); if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsConceptSequenceViaType(Integer.MAX_VALUE, TaxonomyFlags.CONCEPT_STATUS.bits, computer); } return false; } /** * Conection count. * * @return the int */ public int conectionCount() { return this.conceptSequenceRecordMap.size(); } /** * Contains concept sequence via type. * * @param conceptSequence the concept sequence * @param typeSequenceSet the type sequence set * @param flags the flags * @return true, if successful */ public boolean containsConceptSequenceViaType(int conceptSequence, ConceptSequenceSet typeSequenceSet, int flags) { if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .isPresent(typeSequenceSet, flags); } return false; } /** * Contains concept sequence via type. * * @param conceptSequence the concept sequence * @param typeSequenceSet the type sequence set * @param tc the tc * @return true, if successful */ public boolean containsConceptSequenceViaType(int conceptSequence, ConceptSequenceSet typeSequenceSet, TaxonomyCoordinate tc) { final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsConceptSequenceViaType(typeSequenceSet, tc, computer); } return false; } /** * Contains concept sequence via type. * * @param conceptSequence the concept sequence * @param typeSequence the type sequence * @param tc the tc * @return true, if successful */ public boolean containsConceptSequenceViaType(int conceptSequence, int typeSequence, TaxonomyCoordinate tc) { final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsConceptSequenceViaType(typeSequence, tc, computer); } return false; } /** * Contains concept sequence via type. * * @param conceptSequence the concept sequence * @param typeSequenceSet the type sequence set * @param tc the tc * @param flags the flags * @return true, if successful */ public boolean containsConceptSequenceViaType(int conceptSequence, ConceptSequenceSet typeSequenceSet, TaxonomyCoordinate tc, int flags) { final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsConceptSequenceViaType(typeSequenceSet, flags, computer); } return false; } /** * Contains concept sequence via type. * * @param conceptSequence the concept sequence * @param typeSequence the type sequence * @param tc the tc * @param flags the flags * @return true, if successful */ public boolean containsConceptSequenceViaType(int conceptSequence, int typeSequence, TaxonomyCoordinate tc, int flags) { final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsConceptSequenceViaType(typeSequence, flags, computer); } return false; } /** * Contains sequence via type with flags. * * @param conceptSequence the concept sequence * @param typeSequence the type sequence * @param flags the flags * @return true, if successful */ public boolean containsSequenceViaTypeWithFlags(int conceptSequence, int typeSequence, int flags) { if (this.conceptSequenceRecordMap.containsKey(conceptSequence)) { return this.conceptSequenceRecordMap.get(conceptSequence) .containsStampOfTypeWithFlags(typeSequence, flags); } return false; } /** * 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 TaxonomyRecordUnpacked other = (TaxonomyRecordUnpacked) obj; return Objects.equals(this.conceptSequenceRecordMap, other.conceptSequenceRecordMap); } /** * Hash code. * * @return the int */ @Override public int hashCode() { throw new UnsupportedOperationException("May change values, can't put in a tree or set"); } /** * Length. * * @return the int */ public int length() { int length = 0; length = this.conceptSequenceRecordMap.values() .stream() .map((record) -> record.length()) .reduce(length, Integer::sum); return length; } /** * Merge. * * @param newRecord the new record */ public void merge(TaxonomyRecordUnpacked newRecord) { newRecord.conceptSequenceRecordMap.forEachPair((int key, TypeStampTaxonomyRecords value) -> { if (this.conceptSequenceRecordMap.containsKey(key)) { this.conceptSequenceRecordMap.get(key) .merge(value); } else { this.conceptSequenceRecordMap.put(key, value); } return true; }); } /** * Pack. * * @return the int[] */ public int[] pack() { final PackConceptSequenceStampRecords packer = new PackConceptSequenceStampRecords(); this.conceptSequenceRecordMap.forEachPair(packer); return packer.taxonomyRecordArray; } /** * To string. * * @return the string */ @Override public String toString() { final IntArrayList theKeys = this.conceptSequenceRecordMap.keys(); theKeys.sort(); final StringBuilder buf = new StringBuilder(); buf.append("["); final int maxIndex = theKeys.size() - 1; for (int i = 0; i <= maxIndex; i++) { final int conceptSequence = theKeys.get(i); if (i > 0) { buf.append("\n "); } buf.append(Get.conceptDescriptionText(conceptSequence)); buf.append(" <"); buf.append(conceptSequence); buf.append("> <-"); final TypeStampTaxonomyRecords value = this.conceptSequenceRecordMap.get(conceptSequence); value.stream().forEach((TypeStampTaxonomyRecord record) -> { buf.append("\n "); buf.append(record.toString()); }); } buf.append(']'); return buf.toString(); } //~--- get methods --------------------------------------------------------- /** * Gets the concept sequence stamp records. * * @param conceptSequence the concept sequence * @return the concept sequence stamp records */ public Optional<TypeStampTaxonomyRecords> getConceptSequenceStampRecords(int conceptSequence) { return Optional.ofNullable(this.conceptSequenceRecordMap.get(conceptSequence)); } /** * Gets the concept sequences for type. * * @param typeSequence typeSequence to match, or Integer.MAX_VALUE if a * wildcard. * @return active concepts identified by their sequence value. */ public IntStream getConceptSequencesForType(int typeSequence) { final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int possibleParentSequence, TypeStampTaxonomyRecords stampRecords) -> { final IntStream.Builder stampsForConceptIntStream = IntStream.builder(); stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { final TypeStampTaxonomyRecord record = new TypeStampTaxonomyRecord(typeStampFlag); if (typeSequence == Integer.MAX_VALUE) { stampsForConceptIntStream.add(record.getStampSequence()); } else if (typeSequence == record.getTypeSequence()) { stampsForConceptIntStream.add(record.getStampSequence()); } }); conceptSequenceIntStream.accept(possibleParentSequence); return true; }); return conceptSequenceIntStream.build(); } /** * Gets the concept sequences for type. * * @param typeSequence typeSequence to match, or Integer.MAX_VALUE if a * wildcard. * @param tc used to determine if a concept is active. * @return active concepts identified by their sequence value. */ public IntStream getConceptSequencesForType(int typeSequence, TaxonomyCoordinate tc) { final int flags = TaxonomyFlags.getFlagsFromTaxonomyCoordinate(tc); final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int possibleParentSequence, TypeStampTaxonomyRecords stampRecords) -> { final IntStream.Builder stampsForConceptIntStream = IntStream.builder(); stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { final TypeStampTaxonomyRecord record = new TypeStampTaxonomyRecord(typeStampFlag); if ((record.getTaxonomyFlags() & flags) == flags) { if (typeSequence == Integer.MAX_VALUE) { stampsForConceptIntStream.add(record.getStampSequence()); } else if (typeSequence == record.getTypeSequence()) { stampsForConceptIntStream.add(record.getStampSequence()); } } }); if (computer.isLatestActive(stampsForConceptIntStream.build())) { conceptSequenceIntStream.accept(possibleParentSequence); } return true; }); return conceptSequenceIntStream.build(); } /** * Gets the destination concept sequences. * * @return the destination concept sequences */ public IntStream getDestinationConceptSequences() { final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int destinationSequence, TypeStampTaxonomyRecords stampRecords) -> { conceptSequenceIntStream.accept(destinationSequence); return true; }); return conceptSequenceIntStream.build(); } /** * Gets the destination concept sequences not of type. * * @param typeSet the type set * @param tc the tc * @return the destination concept sequences not of type */ public IntStream getDestinationConceptSequencesNotOfType(ConceptSequenceSet typeSet, TaxonomyCoordinate tc) { final int flags = TaxonomyFlags.getFlagsFromTaxonomyCoordinate(tc); final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int destinationSequence, TypeStampTaxonomyRecords stampRecords) -> { final IntStream.Builder stampsForConceptIntStream = IntStream.builder(); stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { final TypeStampTaxonomyRecord record = new TypeStampTaxonomyRecord(typeStampFlag); if ((record.getTaxonomyFlags() & flags) == flags) { if (computer.onRoute(record.getStampSequence())) { if (typeSet.isEmpty()) { stampsForConceptIntStream.accept(record.getStampSequence()); } else if (!typeSet.contains(record.getTypeSequence())) { stampsForConceptIntStream.accept(record.getStampSequence()); } } } }); if (computer.isLatestActive(stampsForConceptIntStream.build())) { conceptSequenceIntStream.accept(destinationSequence); } return true; }); return conceptSequenceIntStream.build(); } /** * Gets the destination concept sequences of type. * * @param typeSet the type set * @return the destination concept sequences of type */ public IntStream getDestinationConceptSequencesOfType(ConceptSequenceSet typeSet) { final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int destinationSequence, TypeStampTaxonomyRecords stampRecords) -> { stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { if (typeSet.contains((int) typeStampFlag & TaxonomyRecordPrimitive.SEQUENCE_BIT_MASK)) { conceptSequenceIntStream.accept(destinationSequence); } }); return true; }); return conceptSequenceIntStream.build(); } /** * Gets the destination concept sequences of type. * * @param typeSet the type set * @param tc the tc * @return the destination concept sequences of type */ public IntStream getDestinationConceptSequencesOfType(ConceptSequenceSet typeSet, TaxonomyCoordinate tc) { final int flags = TaxonomyFlags.getFlagsFromTaxonomyCoordinate(tc); final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int destinationSequence, TypeStampTaxonomyRecords stampRecords) -> { final IntStream.Builder stampsForConceptIntStream = IntStream.builder(); stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { final TypeStampTaxonomyRecord record = new TypeStampTaxonomyRecord(typeStampFlag); if ((record.getTaxonomyFlags() & flags) == flags) { if (computer.onRoute(record.getStampSequence())) { if (typeSet.isEmpty()) { stampsForConceptIntStream.accept(record.getStampSequence()); } else if (typeSet.contains(record.getTypeSequence())) { stampsForConceptIntStream.accept(record.getStampSequence()); } } } }); if (computer.isLatestActive(stampsForConceptIntStream.build())) { conceptSequenceIntStream.accept(destinationSequence); } return true; }); return conceptSequenceIntStream.build(); } /** * Gets the parent concept sequences. * * @return the parent concept sequences */ public IntStream getParentConceptSequences() { final int isaSequence = TermAux.IS_A.getConceptSequence(); final IntStream.Builder conceptSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int possibleParentSequence, TypeStampTaxonomyRecords stampRecords) -> { stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { if ((typeStampFlag & TaxonomyRecordPrimitive.SEQUENCE_BIT_MASK) == isaSequence) { conceptSequenceIntStream.accept(possibleParentSequence); } }); return true; }); return conceptSequenceIntStream.build(); } /** * Gets the types for relationship. * * @param destinationId the destination id * @param tc the tc * @return the types for relationship */ IntStream getTypesForRelationship(int destinationId, TaxonomyCoordinate tc) { final int flags = TaxonomyFlags.getFlagsFromTaxonomyCoordinate(tc); final RelativePositionCalculator computer = RelativePositionCalculator.getCalculator(tc.getStampCoordinate()); final IntStream.Builder typeSequenceIntStream = IntStream.builder(); this.conceptSequenceRecordMap.forEachPair((int destinationSequence, TypeStampTaxonomyRecords stampRecords) -> { if (destinationId == destinationSequence) { final Map<Integer, IntStream.Builder> typeStampStreamMap = new HashMap<>(); stampRecords.getTypeStampFlagStream().forEach((typeStampFlag) -> { final TypeStampTaxonomyRecord record = new TypeStampTaxonomyRecord(typeStampFlag); if ((record.getTaxonomyFlags() & flags) == flags) { if (computer.onRoute(record.getStampSequence())) { if (!typeStampStreamMap.containsKey(record.typeSequence)) { typeStampStreamMap.put(record.typeSequence, IntStream.builder()); } typeStampStreamMap.get((record.typeSequence)) .accept(record.getStampSequence()); } } }); typeStampStreamMap.forEach((type, stampStreamBuilder) -> { if (computer.isLatestActive(stampStreamBuilder.build())) { typeSequenceIntStream.accept(type); } }); } return true; }); return typeSequenceIntStream.build(); } //~--- inner classes ------------------------------------------------------- /** * The Class PackConceptSequenceStampRecords. */ private class PackConceptSequenceStampRecords implements IntObjectProcedure<TypeStampTaxonomyRecords> { /** The taxonomy record array. */ int[] taxonomyRecordArray = new int[length()]; /** The destination position. */ int destinationPosition = 0; //~--- methods ---------------------------------------------------------- /** * Apply. * * @param conceptSequence the concept sequence * @param stampRecordsUnpacked the stamp records unpacked * @return true, if successful */ @Override public boolean apply(int conceptSequence, TypeStampTaxonomyRecords stampRecordsUnpacked) { stampRecordsUnpacked.addToIntArray(conceptSequence, this.taxonomyRecordArray, this.destinationPosition); this.destinationPosition += stampRecordsUnpacked.length(); return true; } } }