Java tutorial
/* * Copyright: (c) 2004-2010 Mayo Foundation for Medical Education and * Research (MFMER). All rights reserved. MAYO, MAYO CLINIC, and the * triple-shield Mayo logo are trademarks and service marks of MFMER. * * Except as contained in the copyright notice above, or as used to identify * MFMER as the author of this software, the trade names, trademarks, service * marks, or product names of the copyright holder shall not be used in * advertising, promotion or otherwise in connection with this software without * prior written authorization of the copyright holder. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * */ package org.LexGrid.LexBIG.Impl.helpers.graph; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import org.LexGrid.LexBIG.DataModel.Collections.AssociatedConceptList; import org.LexGrid.LexBIG.DataModel.Collections.AssociationList; import org.LexGrid.LexBIG.DataModel.Collections.LocalNameList; import org.LexGrid.LexBIG.DataModel.Collections.NameAndValueList; import org.LexGrid.LexBIG.DataModel.Collections.ResolvedConceptReferenceList; import org.LexGrid.LexBIG.DataModel.Collections.SortOptionList; import org.LexGrid.LexBIG.DataModel.Core.AssociatedConcept; import org.LexGrid.LexBIG.DataModel.Core.Association; import org.LexGrid.LexBIG.DataModel.Core.ConceptReference; import org.LexGrid.LexBIG.DataModel.Core.NameAndValue; import org.LexGrid.LexBIG.DataModel.Core.ResolvedConceptReference; import org.LexGrid.LexBIG.Exceptions.LBInvocationException; import org.LexGrid.LexBIG.Exceptions.LBParameterException; import org.LexGrid.LexBIG.Extensions.Query.Filter; import org.LexGrid.LexBIG.Impl.CodedNodeSetImpl; import org.LexGrid.LexBIG.Impl.Extensions.ExtensionRegistryImpl; import org.LexGrid.LexBIG.Impl.dataAccess.SQLImplementedMethods; import org.LexGrid.LexBIG.Impl.helpers.AdditiveCodeHolder; import org.LexGrid.LexBIG.Impl.helpers.CodeToReturn; import org.LexGrid.LexBIG.Impl.helpers.DefaultCodeHolder; import org.LexGrid.LexBIG.Impl.helpers.KnownConceptReference; import org.LexGrid.LexBIG.Impl.helpers.comparator.ResultComparator; import org.LexGrid.LexBIG.LexBIGService.CodedNodeSet; import org.LexGrid.LexBIG.LexBIGService.CodedNodeSet.PropertyType; import org.LexGrid.annotations.LgClientSideSafe; import org.LexGrid.commonTypes.EntityDescription; import org.LexGrid.concepts.Entity; import org.apache.commons.lang.StringUtils; import org.lexevs.exceptions.MissingResourceException; import org.lexevs.exceptions.UnexpectedInternalError; import org.lexevs.system.ResourceManager; /** * Class to help me get from the SQL database to the graph object. * * @author <A HREF="mailto:armbrust.daniel@mayo.edu">Dan Armbrust</A> * @author <A HREF="mailto:johnson.thomas@mayo.edu">Thomas Johnson</A> * @author <A HREF="mailto:erdmann.jesse@mayo.edu">Jesse Erdmann</A> * @author <a href="mailto:kevin.peterson@mayo.edu">Kevin Peterson</a> * @version subversion $Revision: $ checked in on $Date: $ */ public class GHolder { // Track nodes and edges being maintained by the graph private Hashtable<String, GAssociationInfo> allAssociations_ = null; private Hashtable<String, GNode> allNodes_ = null; // Relation root private GNode focusNode_ = null; private ConceptReference focusConceptReference_ = null; // Identifies the code system for resolved info private String internalCodingSchemeName_ = null; private String internalVersionString_ = null; // Indicates direction and nature of resolution private boolean resolveForward_ = false; private boolean resolveReverse_ = false; private boolean resultsSkipped_ = false; /** * Constructs a new container to hold concept graph representations. * * @param internalCodingSchemeName * The coding scheme name for associated nodes; not null. * @param internalVersionString * The coding scheme version for associated nodes; not null. * @param focusNode * The starting point for resolved relations; null for absolute * root. * @param resolveForward * Whether relations are to be resolved in the forward direction. * @param resolveReverse * Whether relations are to be resolved in the reverse direction. */ public GHolder(String internalCodingSchemeName, String internalVersionString, ConceptReference focusNode, boolean resolveForward, boolean resolveReverse) { internalCodingSchemeName_ = internalCodingSchemeName; internalVersionString_ = internalVersionString; focusConceptReference_ = focusNode; resolveForward_ = resolveForward; resolveReverse_ = resolveReverse; allNodes_ = new Hashtable<String, GNode>(); allAssociations_ = new Hashtable<String, GAssociationInfo>(); if (focusConceptReference_ != null) focusConceptReference_.setCodingSchemeName(StringUtils .defaultIfEmpty(focusConceptReference_.getCodingSchemeName(), internalCodingSchemeName)); } /** * Add an edge to the graph. * <p> * If the association is new, return a reference to the concept defined by * the association endpoint based on specified direction (if forward, this * is the target concept; if reverse, it is the source concept). If the * association is already defined, returns null. * * @param sourceCodeSystem * @param sourceCodeNamespace * @param sourceCode * @param sourceCodeTypes * @param relationName * @param associationName * @param targetCodeSystem * @param targetCodeNamespace * @param targetCode * @param targetCodeTypes * @param qualifiers * @param forward * @param internalCodeSystemName * @param internalVersionString * @return org.LexGrid.LexBIG.DataModel.Core.ConceptReference * @throws MissingResourceException * @throws UnexpectedInternalError * @throws LBParameterException */ @LgClientSideSafe public ConceptReference addAssociation(String sourceCodeSystem, String sourceCodeNamespace, String sourceCode, String[] sourceCodeTypes, String sourceCodeEntityDescription, String relationName, String associationName, String targetCodeSystem, String targetCodeNamespace, String targetCode, String[] targetCodeTypes, String targetCodeEntityDescription, NameAndValueList qualifiers, boolean forward, String internalCodeSystemName, String internalVersionString) throws MissingResourceException, UnexpectedInternalError, LBParameterException { // Get pre-resolved node for source and target, or resolve now ... GNode source = getNode(sourceCodeSystem, sourceCodeNamespace, sourceCode, sourceCodeTypes, sourceCodeEntityDescription, forward, !forward, internalCodeSystemName, internalVersionString, associationName); GNode target = getNode(targetCodeSystem, targetCodeNamespace, targetCode, targetCodeTypes, targetCodeEntityDescription, !forward, forward, internalCodeSystemName, internalVersionString, associationName); // What I am doing here is marking the concept codes that have already // been resolved - // so that I don't end up chasing down an infinite loop on the DB side. // I only return a ConceptReference to the *new* code that was found if // it hasn't // already been queried before. if (forward) source.setNodeHasBeenPrinted(true); else target.setNodeHasBeenPrinted(true); // If focus reference is set but the associated node is not yet // resolved, check to // see if we have now found it. If so, remember it. if (focusConceptReference_ != null && focusNode_ == null) { if (focusConceptReference_.getConceptCode().equals(sourceCode) && focusConceptReference_.getCodingSchemeName().equals(sourceCodeSystem)) focusNode_ = source; else if (focusConceptReference_.getConceptCode().equals(targetCode) && focusConceptReference_.getCodingSchemeName().equals(targetCodeSystem)) focusNode_ = target; } // Get existing association info, or create now, then remember for start // and endpoint... GAssociationInfo gai = getAssociationInfo(internalCodeSystemName, internalVersionString, associationName, relationName); GAssociation targetAssociations = source.getTargetAssociation(gai); if (!targetAssociations.hasChild(target)) targetAssociations.addChild(target, qualifiers); // Associations may not be reverse-navigable; check and only remember if // appropriate... GAssociation sourceAssociations = target.getSourceAssociation(gai); Boolean navigable = sourceAssociations.getAssociationInfo().getIsNavigable(); if (navigable != null && navigable.booleanValue() && !sourceAssociations.hasChild(target)) sourceAssociations.addChild(source, qualifiers); // Return any new node serving as endpoint based on the indicated // direction if (forward && !target.isNodeHasBeenPrinted()) return new KnownConceptReference(target.getCodeSystem(), target.getCode(), target.getCodeNamespace()); if (!forward && !source.isNodeHasBeenPrinted()) return new KnownConceptReference(source.getCodeSystem(), source.getCode(), source.getCodeNamespace()); return null; } /** * Add an edge to the graph. * <p> * If the association is new, return a reference to the concept defined by * the association endpoint based on specified direction (if forward, this * is the target concept; if reverse, it is the source concept). If the * association is already defined, returns null. * * @param sourceCodeSystem * @param sourceCodeNamespace * @param sourceCode * @param sourceCodeTypes * @param relationName * @param associationName * @param targetCodeSystem * @param targetCodeNamespace * @param targetCode * @param targetCodeTypes * @param qualifiers * @param forward * @param internalCodeSystemName * @param internalVersionString * @return org.LexGrid.LexBIG.DataModel.Core.ConceptReference * @throws MissingResourceException * @throws UnexpectedInternalError * @throws LBParameterException */ @LgClientSideSafe public void addAssociationInfo(String sourceCodeSystem, String sourceCodeNamespace, String sourceCode, String[] sourceCodeTypes, String sourceCodeEntityDescription, String relationName, String associationName, String targetCodeSystem, String targetCodeNamespace, String targetCode, String[] targetCodeTypes, String targetCodeEntityDescription, NameAndValueList qualifiers, boolean forward, String internalCodeSystemName, String internalVersionString) throws MissingResourceException, UnexpectedInternalError, LBParameterException { // Get pre-resolved node for source and target, or resolve now ... GNode source = getNode(sourceCodeSystem, sourceCodeNamespace, sourceCode, sourceCodeTypes, sourceCodeEntityDescription, forward, !forward, internalCodeSystemName, internalVersionString, associationName); GNode target = getNode(targetCodeSystem, targetCodeNamespace, targetCode, targetCodeTypes, targetCodeEntityDescription, !forward, forward, internalCodeSystemName, internalVersionString, associationName); // Get existing association info, or create now, then remember for start // and endpoint... GAssociationInfo gai = getAssociationInfo(internalCodeSystemName, internalVersionString, associationName, relationName); GAssociation targetAssociations = source.getTargetAssociation(gai); targetAssociations.setUnaddedChildrenPresent(true); // Associations may not be reverse-navigable; check and only remember if // appropriate... GAssociation sourceAssociations = target.getSourceAssociation(gai); Boolean navigable = sourceAssociations.getAssociationInfo().getIsNavigable(); if (navigable != null && navigable.booleanValue() && !sourceAssociations.hasChild(target)) sourceAssociations.setUnaddedChildrenPresent(true); } /** * Copy child nodes from the old parent/association to the new. * * @param oldParent * @param oldAssoc * @param newParent * @param newAssoc */ protected void addAssociationChildren(GNode oldParent, GAssociation oldAssoc, GNode newParent, GAssociation newAssoc) { // If unadded children are indicated in the old assoc, // preserve in the new association. newAssoc.setUnaddedChildrenPresent( newAssoc.isUnaddedChildrenPresent() || oldAssoc.isUnaddedChildrenPresent()); // Copy added children from old to new ... for (Iterator<GNode> nodeIterator = oldAssoc.getChildren().iterator(); nodeIterator.hasNext();) { GNode currentNode = (GNode) nodeIterator.next(); // if not already defined as a child, copy it (but not the // sub-nodes). boolean hasChild = newAssoc.hasChild(currentNode); if (!hasChild) { // created this node yet? GNode child = allNodes_.get(currentNode.getKey()); if (child == null) { // nope, create it child = new GNode(currentNode); addNode(child); } // add it now to the association newAssoc.addChild(child, oldAssoc.getQualifier(currentNode)); } else { // child was already known for this association; make sure all // qualifiers are carried over newAssoc.addQualifiers(newAssoc.getChild(currentNode), oldAssoc.getQualifier(currentNode)); } } } /** * Basic addition of a node to those tracked by the graph. * * @param node */ protected void addNode(GNode node) { allNodes_.put(node.getKey(), node); } /** * Returns a graph node based on the given information; a new node is added * to the graph if necessary. * * @param codeSystem * @param codeNamespace * @param code * @param codeTypes * @param internalCodeSystemName * @param internalVersion * @param forward * @return A GNode based on the provided info; not null. * @throws MissingResourceException * @throws UnexpectedInternalError */ @LgClientSideSafe public ConceptReference addNode(String codeSystem, String codeNamespace, String code, String[] codeTypes, String entityDescription, String internalCodeSystemName, String internalVersion, boolean forward) throws MissingResourceException, UnexpectedInternalError { getNode(codeSystem, codeNamespace, code, codeTypes, entityDescription, forward, !forward, internalCodeSystemName, internalVersion, null); return new KnownConceptReference(codeSystem, code, codeNamespace); } /** * Create and return the list of associations for the given node, resolved * to the specified depth and modified according to parameter values. The * method will recurse as necessary to achieve the requested depth. * * @param node * Focus node. * @param forward * Indicates whether the we are resolving source or target * relations for the node. * @param currentDepth * Tracks depth for purposes of recursion. * @param resolveCodedEntryDepth * Depth in the graph to resolve coded entries. -1 means don't * resolve anything - just return the concept references, 0 means * resolve just the root nodes, 1 means resolve 1 deep, etc. * @param resolveAssociationDepth * Number of hops to resolve associations. 0 means leave all * associations unresolved, 1 means immediate neighbors, etc. -1 * means follow the entire closure of the graph. * @param propertyNames * Local names of properties to resolve. If not empty and not * null, only properties matching the given names are included * for resolved concepts. * @param propertyTypes * Indicates whether to resolve only specific property * categories, regardless of the assigned name. Any of the * enumerated PropertyType values can be specified. If not empty * and not null, only properties matching the given types are * included for resolved concepts. * @param sortOptions * List of sort options to apply during resolution. If supplied, * the sort algorithms will be applied in the order provided. Any * algorithms not valid to be applied in context of node set * iteration, as specified in the sort extension description, * will result in a parameter exception. Available algorithms can * be retrieved through the LexBIGService getSortExtensions() * method after being defined to the LexBIGServiceManager * extension registry. * @param filters * @return org.LexGrid.LexBIG.DataModel.Collections.AssociationList * @throws LBParameterException * @throws UnexpectedInternalError * @throws MissingResourceException */ protected AssociationList buildAssociationList(GNode node, boolean forward, int currentDepth, int resolveCodedEntryDepth, int resolveAssociationDepth, LocalNameList restrictToProperties, PropertyType[] restrictToPropertyTypes, SortOptionList sortBy, Filter[] filters, boolean keepLastAssociationLevelUnresolved) throws LBParameterException, UnexpectedInternalError, MissingResourceException { // Most of the time, the depth will already be properly limited here. // However, in certain cases, if // they ask for depth 0, or a depth 1 and items are linked forward and // backward, I will have // more than that. Don't add them. if (resolveAssociationDepth >= 0 && currentDepth > resolveAssociationDepth) return null; AssociationList al = new AssociationList(); ResourceManager rm = ResourceManager.instance(); // Fetch the next level of associations Enumeration<GAssociation> GAssociations; GAssociations = forward ? node.getTargetAssociations(sortBy) : node.getSourceAssociations(sortBy); // Process each ... while (GAssociations.hasMoreElements()) { GAssociation gCurrentAssociation = (GAssociation) GAssociations.nextElement(); // Assign identifying name and directional label Association assn = new Association(); assn.setAssociationName(gCurrentAssociation.getAssociationInfo().getName()); assn.setDirectionalName(forward ? gCurrentAssociation.getAssociationInfo().getForwardName() : gCurrentAssociation.getAssociationInfo().getReverseName()); // If the association is grounded in a concept, remember it assn.setAssociationReference(gCurrentAssociation.getAssociationInfo().getAssociationReference()); // Create a holder for associated concepts, then process children // in order according to sort criteria... assn.setAssociatedConcepts(new AssociatedConceptList()); for (Iterator<GNode> iter = gCurrentAssociation.getChildren(sortBy).iterator(); iter.hasNext();) { GNode gCurrentChild = (GNode) iter.next(); // If not already 'printed', mark it here to avoid recursion // later boolean nodePreviouslyPrinted = gCurrentChild.isNodeHasBeenPrinted(); gCurrentChild.setNodeHasBeenPrinted(true); // Create a concept to track to the association AssociatedConcept ac = new AssociatedConcept(); // Transfer available qualifiers to the associated concept NameAndValueList qualifiers = gCurrentAssociation.getQualifier(gCurrentChild); if (qualifiers != null && qualifiers.getNameAndValueCount() > 0) { NameAndValueList quals = new NameAndValueList(); for (int i = 0; i < qualifiers.getNameAndValueCount(); i++) { NameAndValue nv = new NameAndValue(); nv.setName(qualifiers.getNameAndValue(i).getName()); nv.setContent(qualifiers.getNameAndValue(i).getContent()); quals.addNameAndValue(nv); } ac.setAssociationQualifiers(quals); } // Assign basic identifying information to the node reference ... ac.setCodingSchemeName(rm.getExternalCodingSchemeNameForUserCodingSchemeNameOrId( gCurrentChild.getDefiningCodeSystemURN(), gCurrentChild.getDefiningCodeSystemVersion())); ac.setCodingSchemeURI(gCurrentChild.getDefiningCodeSystemURN()); ac.setCodingSchemeVersion(gCurrentChild.getDefiningCodeSystemVersion()); ac.setCode(gCurrentChild.getCode()); ac.setCodeNamespace(gCurrentChild.getCodeNamespace()); ac.setIsNavigable(gCurrentAssociation.getAssociationInfo().getIsNavigable()); String version = gCurrentChild.getDefiningCodeSystemVersion(); boolean detailsUnavailable = false; if (version == null || version.length() == 0) { // try to get a version try { version = ResourceManager.instance() .getInternalVersionStringForTag(gCurrentChild.getDefiningCodeSystemURN(), null); } catch (LBParameterException e) { // no version of this code system is available, we won't // be able to get further details. detailsUnavailable = true; } } // if there is no version, we won't be able to resolve anything. if (currentDepth <= resolveCodedEntryDepth && !detailsUnavailable) { Entity ent = SQLImplementedMethods.buildCodedEntry( rm.getInternalCodingSchemeNameForUserCodingSchemeName( gCurrentChild.getDefiningCodeSystemURN(), version), version, gCurrentChild.getCode(), gCurrentChild.getCodeNamespace(), restrictToProperties, restrictToPropertyTypes); ac.setEntityDescription(ent.getEntityDescription()); ac.setEntity(ent); } else if (!detailsUnavailable) { ac.setEntity(null); EntityDescription ed = new EntityDescription(); ed.setContent(gCurrentChild.getEntityDescription()); ac.setEntityDescription(ed); } else { ac.setEntity(null); ac.setEntityDescription(null); } // Honor any filters that may have been applied boolean add = true; if (filters != null && filters.length > 0) { for (int j = 0; j < filters.length; j++) { if (!filters[j].match(ac)) { add = false; break; } } } // If filtered or already rendered, ignore the node. if (add) { if (!nodePreviouslyPrinted) { if (forward) { ac.setSourceOf(buildAssociationList(gCurrentChild, forward, currentDepth + 1, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortBy, filters, keepLastAssociationLevelUnresolved)); } else { ac.setTargetOf(buildAssociationList(gCurrentChild, forward, currentDepth + 1, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortBy, filters, keepLastAssociationLevelUnresolved)); } } assn.getAssociatedConcepts().addAssociatedConcept(ac); } } if (assn.getAssociatedConcepts().getAssociatedConceptCount() > 0 || (keepLastAssociationLevelUnresolved && gCurrentAssociation.isUnaddedChildrenPresent())) { al.addAssociation(assn); } } if (al.getAssociationCount() == 0) { return null; } return al; } /* * returns null if a filter rejects the item */ protected ResolvedConceptReference buildResolvedConceptReference(GNode currentNode, int resolveCodedEntryDepth, int resolveAssociationDepth, LocalNameList restrictToProperties, PropertyType[] restrictToPropertyTypes, SortOptionList sortByProperty, Filter[] filters, boolean keepLastAssociationLevelUnresolved) throws UnexpectedInternalError, MissingResourceException, LBParameterException { // Assign basic identifying information to the node reference ... ResourceManager rm = ResourceManager.instance(); ResolvedConceptReference rcr = new ResolvedConceptReference(); rcr.setCodingSchemeName(rm.getExternalCodingSchemeNameForUserCodingSchemeNameOrId( currentNode.getDefiningCodeSystemURN(), currentNode.getDefiningCodeSystemVersion())); rcr.setCodingSchemeURI(currentNode.getDefiningCodeSystemURN()); rcr.setCodingSchemeVersion(currentNode.getDefiningCodeSystemVersion()); rcr.setCode(currentNode.getCode()); rcr.setCodeNamespace(currentNode.getCodeNamespace()); rcr.setEntityType(currentNode.getCodeTypes()); try { if (resolveCodedEntryDepth >= 0) { Entity ent = SQLImplementedMethods.buildCodedEntry( rm.getInternalCodingSchemeNameForUserCodingSchemeName( currentNode.getDefiningCodeSystemURN(), currentNode.getDefiningCodeSystemVersion()), currentNode.getDefiningCodeSystemVersion(), currentNode.getCode(), currentNode.getCodeNamespace(), restrictToProperties, restrictToPropertyTypes); rcr.setEntityDescription(ent.getEntityDescription()); rcr.setEntity(ent); } else { rcr.setEntity(null); EntityDescription entityDescription = new EntityDescription(); entityDescription.setContent(currentNode.getEntityDescription()); rcr.setEntityDescription(entityDescription); } } catch (Exception e) { // if there was a problem resolving the code (reference to a code // system that isn't available, for // example - don't fail, just return what is available. rcr.setEntity(null); rcr.setEntityDescription(null); } currentNode.setNodeHasBeenPrinted(true); boolean add = true; if (filters != null && filters.length > 0) { for (int j = 0; j < filters.length; j++) { if (!filters[j].match(rcr)) { add = false; break; } } } if (add) { rcr.setSourceOf(buildAssociationList(currentNode, true, 1, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortByProperty, filters, keepLastAssociationLevelUnresolved)); rcr.setTargetOf(buildAssociationList(currentNode, false, 1, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortByProperty, filters, keepLastAssociationLevelUnresolved)); return rcr; } else { // filter rejected this item return null; } } /** * Returns a shared structure containing identifying information for the * given association; a new information object is automatically created if * necessary. * * @param internalCodeSystemName * @param internalVersionString * @param associationName * @param relationName * @throws LBParameterException * @throws UnexpectedInternalError * @throws MissingResourceException */ protected GAssociationInfo getAssociationInfo(String internalCodeSystemName, String internalVersionString, String associationName, String relationName) throws LBParameterException, UnexpectedInternalError, MissingResourceException { GAssociationInfo gai = allAssociations_ .get(GAssociationInfo.getKey(internalCodeSystemName, associationName)); if (gai == null) { gai = SQLImplementedMethods.getAssociationInfo(internalCodeSystemName, internalVersionString, associationName, relationName); String assocKey = gai.getKey(); allAssociations_.put(assocKey, gai); } return gai; } /** * Returns all nodes maintained by the graph that are target but not source * of any relationship. If none exists, then return nodes that are a target * but not a source of any relationship in which it participates, sorted * according to the given options. * * @param sortBy */ protected Iterator<GNode> getChildlessNodes(SortOptionList sortBy) { Set<GNode> childless = new HashSet<GNode>(); for (Iterator<GNode> gNodes = allNodes_.values().iterator(); gNodes.hasNext();) { GNode gNode = gNodes.next(); if (gNode.isChildless()) { childless.add(gNode); } } if (childless.isEmpty()) { for (Iterator<GNode> gNodes = allNodes_.values().iterator(); gNodes.hasNext();) { GNode gNode = gNodes.next(); for (Enumeration<GAssociation> gAssociations = gNode.getSourceAssociations(); gAssociations .hasMoreElements();) { GAssociation gAssociation = gAssociations.nextElement(); if (gNode.getTargetAssociation(gAssociation.getAssociationInfo()).getChildCount() == 0) { childless.add(gNode); break; } } } } return getSortedNodeIterator(sortBy, childless); } /** * Locates and returns a graph node based on the given information; a new * node is added to the graph if necessary. * * @param codeSystem * @param codeNamespace * @param code * @param codeTypes * @param flagParentlessIfNew * @param flagChildlessIfNew * @param internalCodeSystemName * @param internalVersionString * @param associationName * @return A GNode based on the provided info; not null. * @throws MissingResourceException * @throws UnexpectedInternalError */ protected GNode getNode(String codeSystem, String codeNamespace, String code, String[] codeTypes, String entityDescription, boolean flagParentlessIfNew, boolean flagChildlessIfNew, String internalCodeSystemName, String internalVersionString, String associationName) throws MissingResourceException, UnexpectedInternalError { GNode node = allNodes_.get(GNode.getKey(codeSystem, codeNamespace, code)); if (node == null) addNode(node = new GNode(codeSystem, codeNamespace, code, codeTypes, entityDescription, internalCodeSystemName, internalVersionString)); return node; } /** * Returns the total number of nodes tracked by the graph. * * @return int */ @LgClientSideSafe public int getNodeCount() { return allNodes_.size(); } @LgClientSideSafe public CodedNodeSet getNodeList() throws LBInvocationException { AdditiveCodeHolder ch = new DefaultCodeHolder(); Enumeration<GNode> allNodes = allNodes_.elements(); while (allNodes.hasMoreElements()) { GNode current = allNodes.nextElement(); ch.add(new CodeToReturn(current.getCode(), current.getEntityDescription(), current.getDefiningCodeSystemURN(), current.getDefiningCodeSystemVersion(), 0, current.getCodeNamespace(), current.getCodeTypes())); } return new CodedNodeSetImpl(ch, internalCodingSchemeName_, internalVersionString_); } /** * Returns all nodes maintained by the graph that are source but not target * of any relationship. If none exists, return nodes that are a source not * not a target for at least one relationship in which it participates, * sorted according to the given options. * * @param sortBy */ protected Iterator<GNode> getParentlessNodes(SortOptionList sortBy) { Set<GNode> parentless = new HashSet<GNode>(); for (Iterator<GNode> gNodes = allNodes_.values().iterator(); gNodes.hasNext();) { GNode gNode = gNodes.next(); if (gNode.getIncomingLinkCount() == 0) { parentless.add(gNode); } } if (parentless.isEmpty()) { for (Iterator<GNode> gNodes = allNodes_.values().iterator(); gNodes.hasNext();) { GNode gNode = gNodes.next(); for (Enumeration<GAssociation> gAssociations = gNode.getTargetAssociations(); gAssociations .hasMoreElements();) { GAssociation gAssociation = gAssociations.nextElement(); if (gNode.getSourceAssociation(gAssociation.getAssociationInfo()).getChildCount() == 0) { parentless.add(gNode); break; } } } } return getSortedNodeIterator(sortBy, parentless); } /** * Returns the fully resolved contents of the graph. * * @param resolveCodedEntryDepth * @param resolveAssociationDepth * @param restrictToProperties * @param restrictToPropertyTypes * @param sortByProperty * @param filterOptions * @throws MissingResourceException * @throws UnexpectedInternalError * @throws LBParameterException */ @LgClientSideSafe public ResolvedConceptReferenceList getResolvedConceptReferenceList(int resolveCodedEntryDepth, int resolveAssociationDepth, LocalNameList restrictToProperties, PropertyType[] restrictToPropertyTypes, SortOptionList sortByProperty, LocalNameList filterOptions, boolean keepLastAssociationLevelUnresolved) throws MissingResourceException, UnexpectedInternalError, LBParameterException { resetNodePrintedFlags(); ResolvedConceptReferenceList list = new ResolvedConceptReferenceList(); if (isResultsSkipped()) { list.setIncomplete(new Boolean("true")); } Filter[] filters = validateFilters(filterOptions); if (focusNode_ == null) { // no focus node. If they asked for forward, our top node will be // all of the nodes that // aren't linked by anything else. If they asked for reverse, the // start nodes will be // the child nodes ( no outgoing links). Asking for forward and // reverse nodes, but having // a null focusNode_ is impossible. Iterator<GNode> nodes = (resolveForward_ ? getParentlessNodes(sortByProperty) : getChildlessNodes(sortByProperty)); while (nodes.hasNext()) { GNode currentNode = nodes.next(); listHelper(list, currentNode, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortByProperty, filters, keepLastAssociationLevelUnresolved); } } else { // have a focus, start there. GNode currentNode = focusNode_; listHelper(list, currentNode, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortByProperty, filters, keepLastAssociationLevelUnresolved); } //if a focus ConceptReference is provided, but no associations are found, that //focus ConceptReference is a graph all by itself. Resolve the focus ConceptReference and return it. if (list.getResolvedConceptReferenceCount() == 0 && focusConceptReference_ != null) { ResolvedConceptReference rcr = SQLImplementedMethods.resolveConceptReference(focusConceptReference_, internalVersionString_); if (rcr != null) { list.addResolvedConceptReference(rcr); } } return list; } /** * Returns an iterator that will sort the given nodes according to the * specified sort options. * * @param sortBy * @param nodes */ protected Iterator<GNode> getSortedNodeIterator(SortOptionList sortBy, Set<GNode> nodes) { GNode[] temp = nodes.toArray(new GNode[nodes.size()]); if (ResultComparator.isSortOptionListValid(sortBy)) { ResultComparator<GNode> compare = new ResultComparator<GNode>(sortBy, GNode.class); Arrays.sort(temp, compare); } return Arrays.asList(temp).iterator(); } /** * Performs a boolean intersection of this graph holder with another. Upon * completion, only nodes common to both are preserved. * * @param holder */ @LgClientSideSafe public void intersection(GHolder holder) { // need to remove all nodes from my graph that do not occur in the other // graph. remove associations // as necessary Enumeration<String> myKeys = allNodes_.keys(); while (myKeys.hasMoreElements()) { String myCurrentKey = (String) myKeys.nextElement(); // does the other graph have this node? GNode other = holder.allNodes_.get(myCurrentKey); // If not, remove it altogether. Otherwise, maintain // only the associations common to both graphs. if (other == null) removeNode(myCurrentKey); else { GNode myNode = allNodes_.get(myCurrentKey); myNode.intersectLinks(other); if (myNode.isChildless() && myNode.getIncomingLinkCount() == 0) allNodes_.remove(myCurrentKey); } } } /** * @return the resultsSkipped */ protected boolean isResultsSkipped() { return this.resultsSkipped_; } protected void listHelper(ResolvedConceptReferenceList list, GNode currentNode, int resolveCodedEntryDepth, int resolveAssociationDepth, LocalNameList restrictToProperties, PropertyType[] restrictToPropertyTypes, SortOptionList sortByProperty, Filter[] filters, boolean keepLastAssociationLevelUnresolved) throws UnexpectedInternalError, MissingResourceException, LBParameterException { if (currentNode != null) { ResolvedConceptReference rcr = buildResolvedConceptReference(currentNode, resolveCodedEntryDepth, resolveAssociationDepth, restrictToProperties, restrictToPropertyTypes, sortByProperty, filters, keepLastAssociationLevelUnresolved); // rcr will be null if the result didn't pass the filter. if (rcr != null) { list.addResolvedConceptReference(rcr); } } } /** * Removes the node identified by the given key from those tracked by the * graph. * * @param nodeKey */ protected void removeNode(String nodeKey) { GNode nodeToRemove = allNodes_.get(nodeKey); // remove it from the root node (if it is there) if (focusNode_ != null && focusNode_.equals(nodeToRemove)) { focusNode_ = null; } // need to find out which nodes point to this node - and remove those // links. Enumeration<GAssociation> sources = nodeToRemove.getSourceAssociations(); while (sources.hasMoreElements()) { GAssociation currentAssociation = sources.nextElement(); Collection<GNode> nodesThatPointToIt = currentAssociation.getChildren(); for (Iterator<GNode> iter = nodesThatPointToIt.iterator(); iter.hasNext();) { GNode currentNode = (GNode) iter.next(); currentNode.removeLinkTo(nodeToRemove, currentAssociation); } } // Ok, now nobody points to this node. Need to also remove any nodes // that are only reachable // from this node. Enumeration<GAssociation> targets = nodeToRemove.getTargetAssociations(); while (targets.hasMoreElements()) { GAssociation currentAssociation = targets.nextElement(); Collection<GNode> nodesItPointsTo = currentAssociation.getChildren(); for (Iterator<GNode> iter = nodesItPointsTo.iterator(); iter.hasNext();) { GNode currentNode = (GNode) iter.next(); currentNode.removeLinkFrom(nodeToRemove, currentAssociation); int incomingCount = currentNode.getIncomingLinkCount(); if (incomingCount == 0) { // If nobody points to this node any more, need to remove it // as well. // this is recursive removeNode(currentNode.getKey()); } } } // Finally, pull it from the maintained list of concepts ... allNodes_.remove(nodeKey); } /** * For each node that is tracked, this class maintains an indication of * whether or not the node has been included in the results in order to * prevent recursion. This method clears all flags. */ protected void resetNodePrintedFlags() { Enumeration<GNode> e = allNodes_.elements(); while (e.hasMoreElements()) { GNode node = (GNode) e.nextElement(); node.setNodeHasBeenPrinted(false); } } /** * @param resultsSkipped * the resultsSkipped to set */ @LgClientSideSafe public void setResultsSkipped(boolean resultsSkipped) { this.resultsSkipped_ = resultsSkipped; } /** * Performs a boolean union of this graph holder with another. Upon * completion, nodes for both are tracked. * * @param holder */ @LgClientSideSafe public void union(GHolder holder) { holder.resetNodePrintedFlags(); for (Iterator<GNode> iter = holder.allNodes_.values().iterator(); iter.hasNext();) { // check if this node has already been processed ... GNode currentOtherNode = (GNode) iter.next(); if (currentOtherNode.isNodeHasBeenPrinted()) { continue; } currentOtherNode.setNodeHasBeenPrinted(true); // find or add the basic node definition ... GNode myNode = allNodes_.get(currentOtherNode.getKey()); if (myNode == null) { // create a new GNode that is almost identical to the otherNode // doesn't carry along the relationships. myNode = new GNode(currentOtherNode); addNode(myNode); } // now, deal with the relationships for the node being processed // sources first; go over all of the associations. Enumeration<GAssociation> sourceAssociations = currentOtherNode.getSourceAssociations(); while (sourceAssociations.hasMoreElements()) { GAssociation currentOtherSourceAssociation = sourceAssociations.nextElement(); GAssociation myAssociation = myNode .getSourceAssociation(currentOtherSourceAssociation.getAssociationInfo()); addAssociationChildren(currentOtherNode, currentOtherSourceAssociation, myNode, myAssociation); } // now targets Enumeration<GAssociation> targetAssociations = currentOtherNode.getTargetAssociations(); while (targetAssociations.hasMoreElements()) { GAssociation currentOtherTargetAssociation = targetAssociations.nextElement(); GAssociation myAssociation = myNode .getTargetAssociation(currentOtherTargetAssociation.getAssociationInfo()); addAssociationChildren(currentOtherNode, currentOtherTargetAssociation, myNode, myAssociation); } // Merge focus ... if (focusNode_ == null && currentOtherNode.equals(holder.focusNode_)) focusNode_ = myNode; } // Merge directionality ... resolveForward_ = resolveForward_ || holder.resolveForward_; resolveReverse_ = resolveReverse_ || holder.resolveReverse_; resultsSkipped_ = resultsSkipped_ || holder.resultsSkipped_; } protected Filter[] validateFilters(LocalNameList filterOptions) throws LBParameterException { if (filterOptions != null && filterOptions.getEntryCount() > 0) { Filter[] temp = new Filter[filterOptions.getEntryCount()]; for (int i = 0; i < temp.length; i++) { temp[i] = ExtensionRegistryImpl.instance().getFilter(filterOptions.getEntry(i)); } return temp; } else { return null; } } public String toString() { String str = "internalCodingSchemeName_ = " + internalCodingSchemeName_ + "\n" + "internalVersionString_ = " + internalVersionString_ + "\n" + "resolveForward_ = " + resolveForward_ + "\n" + "resolveReverse_ = " + resolveReverse_ + "\n" + "resultsSkipped_ = " + resultsSkipped_ + "\n" + "focusNode_ = " + focusNode_ + "\n" + "allAssociations_ = " + allAssociations_ + "\n" + "allNodes_ = " + allNodes_ + "\n" + "focusConceptReference_ = " + focusConceptReference_ + "\n"; return str; } }