NIH | National Cancer Institute | NCI Wiki  

WIKI MAINTENANCE NOTICE

Please be advised that NCI Wiki will be undergoing maintenance Monday, July 22nd between 1700 ET and 1800 ET and will be unavailable during this period.
Please ensure all work is saved before said time.

If you have any questions or concerns, please contact the CBIIT Atlassian Management Team.

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3
Code Block
languageJava
titleJava Code
    /*
     * Copyright: (c) 2004-2009 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.example;
   
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
   
    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.ResolvedConceptReferenceList;
    import org.LexGrid.LexBIG.DataModel.Core.AssociatedConcept;
    import org.LexGrid.LexBIG.DataModel.Core.Association;
    import org.LexGrid.LexBIG.DataModel.Core.CodingSchemeSummary;
    import org.LexGrid.LexBIG.DataModel.Core.CodingSchemeVersionOrTag;
    import org.LexGrid.LexBIG.DataModel.Core.ConceptReference;
    import org.LexGrid.LexBIG.DataModel.Core.ResolvedConceptReference;
    import org.LexGrid.LexBIG.Exceptions.LBException;
    import org.LexGrid.LexBIG.Exceptions.LBResourceUnavailableException;
    import org.LexGrid.LexBIG.Extensions.Generic.LexBIGServiceConvenienceMethods;
    import org.LexGrid.LexBIG.Extensions.Generic.LexBIGServiceConvenienceMethods.HierarchyPathResolveOption;
    import org.LexGrid.LexBIG.Impl.LexBIGServiceImpl;
    import org.LexGrid.LexBIG.LexBIGService.CodedNodeGraph;
    import org.LexGrid.LexBIG.LexBIGService.CodedNodeSet;
    import org.LexGrid.LexBIG.LexBIGService.LexBIGService;
    import org.LexGrid.LexBIG.Utility.Constructors;
    import org.LexGrid.codingSchemes.CodingScheme;
    import org.LexGrid.commonTypes.EntityDescription;
    import org.LexGrid.naming.SupportedHierarchy;
    import org.apache.commons.lang.StringUtils;
   
    /**
     * Attempts to provide a tree, based on a focus code, that includes the
     * following information:
     *
     *
     * - All paths from the hierarchy root to one or more focus codes.
     * - Immediate children of every node in path to root
     * - Indicator to show whether any unexpanded node can be further expanded
     *
     *
     * This example accepts two parameters... The first parameter is required, and
     * must contain at least one code in a comma-delimited list. A tree is produced
     * for each code. Time to produce the tree for each code is printed in
     * milliseconds. In order to factor out costs of startup and shutdown, resolving
     * multiple codes may offer a better overall estimate performance.
     *
     * The second parameter is optional, and can indicate a hierarchy ID to navigate
     * when resolving child nodes. If not provided, "is_a" is assumed.
     */
     public class BuildTreeForCode {
        LocalNameList noopList_ = Constructors.createLocalNameList("_noop_");
   
        public BuildTreeForCode() {
            super();
        }
   
        /**
         * Entry point for processing.
         *
         * @param args
         */
        public static void main(String[] args) {
            if (args.length < 1) {
                System.out.println("Example: BuildTreeForCode \"C0000,C0001\"");
                return;
            }
   
            try {
                // Prompt for a coding scheme to run against...
                CodingSchemeSummary css = Util.promptForCodeSystem();
                if (css != null) {
   
                    // Declare common service classes for processing ...
                    LexBIGService lbsvc = LexBIGServiceImpl.defaultInstance();
                    LexBIGServiceConvenienceMethods lbscm = (LexBIGServiceConvenienceMethods) lbsvc
                            .getGenericExtension("LexBIGServiceConvenienceMethods");
   
                    // Pull the URI and version from the selected value ...
                    String scheme = css.getCodingSchemeURI();
                    CodingSchemeVersionOrTag csvt = new CodingSchemeVersionOrTag();
   
                    // For any given ontology, we know the hierarchy
                    // information. But to keep the code as generic
                    // as possible and protect against changes, we
                    // resolve the supported hierarchy info from the
                    // coding scheme...
                    String hierarchyID = args.length > 1 ? args[1] : "is_a";
                    SupportedHierarchy hierarchyDefn = getSupportedHierarchy(lbsvc, scheme, csvt, hierarchyID);
   
                    // Loop through each provided code.
                    // The time to produce the tree for each code is printed in
                    // milliseconds. In order to factor out costs of startup
                    // and shutdown, resolving multiple codes may offer a
                    // better overall estimate performance.
                    String[] codes = args[0].split(",");
                    for (String code : codes)
                        new BuildTreeForCode().run(lbsvc, lbscm, scheme, csvt, hierarchyDefn, code);
                }
            } catch (Exception e) {
                Util.displayAndLogError("REQUEST FAILED !!!", e);
            }
        }
   
        /**
         * Prints the tree for an individual code.
         */
        public void run(LexBIGService lbsvc, LexBIGServiceConvenienceMethods lbscm, String scheme,
                CodingSchemeVersionOrTag csvt, SupportedHierarchy hierarchyDefn, String focusCode) throws LBException {
   
            // Print a header and define a new tree for the code being processed.
            Util.displayMessage("============================================================");
            Util.displayMessage("Focus code: " + focusCode);
            Util.displayMessage("============================================================");
   
            TreeItem ti = new TreeItem("<Root>", "Root node");
            long ms = System.currentTimeMillis();
            int pathsResolved = 0;
            try {
   
                // Resolve 'is_a' hierarchy info. This example will
                // need to make some calls outside of what is covered
                // by existing convenience methods, but we use the
                // registered hierarchy to prevent need to hard code
                // relationship and direction info used on lookup ...
                String hierarchyID = hierarchyDefn.getLocalId();
                String[] associationsToNavigate = hierarchyDefn.getAssociationNames();
                boolean associationsNavigatedFwd = hierarchyDefn.getIsForwardNavigable();
   
                // Identify the set of all codes on path from root
                // to the focus code ...
                Map<String, EntityDescription> codesToDescriptions = new HashMap<String, EntityDescription>();
                AssociationList pathsFromRoot = getPathsFromRoot(lbsvc, lbscm, scheme, csvt, hierarchyID, focusCode,
                        codesToDescriptions);
   
                // Typically there will be one path, but handle multiple just in
                // case. Each path from root provides a 'backbone', from focus
                // code to root, for additional nodes to hang off of in our
                // printout. For every backbone node, one level of children is
                // printed, along with an indication of whether those nodes can
                // be expanded.
                for (Iterator<Association> paths = pathsFromRoot.iterateAssociation(); paths.hasNext();) {
                    addPathFromRoot(ti, lbsvc, lbscm, scheme, csvt, paths.next(), associationsToNavigate,
                            associationsNavigatedFwd, codesToDescriptions);
                    pathsResolved++;
                }
   
            } finally {
                System.out.println("Run time (milliseconds): " + (System.currentTimeMillis() - ms) + " to resolve "
                        + pathsResolved + " paths from root.");
            }
   
            // Print the result ..
            printTree(ti, focusCode, 0);
        }
   
        /**
         * The given path represents a multi-tier association with associated
         * concepts and targets. This method expands the association content and
         * adds results to the given tree item, recursing as necessary to process
         * each level in the path.
         *
         * Nodes in the association acts as the backbone for the display. For each
         * backbone node, additional children are resolved one level deep along with
         * an indication of further expandability.
         */
        protected void addPathFromRoot(TreeItem ti, LexBIGService lbsvc, LexBIGServiceConvenienceMethods lbscm,
                String scheme, CodingSchemeVersionOrTag csvt, Association path, String[] associationsToNavigate,
                boolean associationsNavigatedFwd, Map<String, EntityDescription> codesToDescriptions) throws LBException {
   
            // First, add the branch point from the path ...
            ConceptReference branchRoot = path.getAssociationReference();
            String branchCode = branchRoot.getCode();
            String branchCodeDescription = codesToDescriptions.containsKey(branchCode) ? codesToDescriptions
                    .get(branchCode).getContent() : getCodeDescription(lbsvc, scheme, csvt, branchCode);
   
            TreeItem branchPoint = new TreeItem(branchCode, branchCodeDescription);
            String branchNavText = getDirectionalLabel(lbscm, scheme, csvt, path, associationsNavigatedFwd);
   
            // Now process elements in the branch ...
            AssociatedConceptList concepts = path.getAssociatedConcepts();
            for (int i = 0; i < concepts.getAssociatedConceptCount(); i++) {
   
                // Add all immediate leafs in the branch, and indication of
                // sub-nodes. Do not process codes already in the backbone here;
                // they will be printed in the recursive call ...
                AssociatedConcept concept = concepts.getAssociatedConcept(i);
                String code = concept.getCode();
                TreeItem branchItem = new TreeItem(code, getCodeDescription(concept));
                branchPoint.addChild(branchNavText, branchItem);
   
                addChildren(branchItem, lbsvc, lbscm, scheme, csvt, code, codesToDescriptions.keySet(),
                        associationsToNavigate, associationsNavigatedFwd);
   
                // Recurse to process the remainder of the backbone ...
                AssociationList nextLevel = concept.getSourceOf();
                if (nextLevel != null) {
                    if (nextLevel.getAssociationCount() != 0) {
                        // More levels left to process ...
                        for (int j = 0; j < nextLevel.getAssociationCount(); j++)
                            addPathFromRoot(branchPoint, lbsvc, lbscm, scheme, csvt, nextLevel.getAssociation(j),
                                    associationsToNavigate, associationsNavigatedFwd, codesToDescriptions);
                    } else {
                        // End of the line ...
                        // Always add immediate children ot the focus code.
                        addChildren(branchItem, lbsvc, lbscm, scheme, csvt, concept.getCode(), Collections.EMPTY_SET,
                                associationsToNavigate, associationsNavigatedFwd);
                    }
                }
            }
   
            // Add the populated tree item to those tracked from root.
            ti.addChild(branchNavText, branchPoint);
        }
   
        /**
         * Populate child nodes for a single branch of the tree, and indicates
         * whether further expansion (to grandchildren) is possible.
         */
        protected void addChildren(TreeItem ti, LexBIGService lbsvc, LexBIGServiceConvenienceMethods lbscm, String scheme,
                CodingSchemeVersionOrTag csvt, String branchRootCode, Set<String> codesToExclude,
                String[] associationsToNavigate, boolean associationsNavigatedFwd) throws LBException {
   
            // Resolve the next branch, representing children of the given
            // code, navigated according to the provided relationship and
            // direction. Resolve the children as a code graph, looking 2
            // levels deep but leaving the final level unresolved.
            CodedNodeGraph cng = lbsvc.getNodeGraph(scheme, csvt, null);
            ConceptReference focus = Constructors.createConceptReference(branchRootCode, scheme);
            cng = cng.restrictToAssociations(Constructors.createNameAndValueList(associationsToNavigate), null);
            ResolvedConceptReferenceList branch = cng.resolveAsList(focus, associationsNavigatedFwd,
                    !associationsNavigatedFwd, -1, 2, noopList_, null, null, null, -1, true);
   
            // The resolved branch will be represented by the first node in
            // the resolved list. The node will be subdivided by source or
            // target associations (depending on direction). The associated
            // nodes define the children.
            for (Iterator<ResolvedConceptReference> nodes = branch.iterateResolvedConceptReference(); nodes.hasNext();) {
                ResolvedConceptReference node = nodes.next();
                AssociationList childAssociationList = associationsNavigatedFwd ? node.getSourceOf() : node.getTargetOf();
   
                // Process each association defining children ...
                for (Iterator<Association> pathsToChildren = childAssociationList.iterateAssociation(); pathsToChildren
                        .hasNext();) {
                    Association child = pathsToChildren.next();
                    String childNavText = getDirectionalLabel(lbscm, scheme, csvt, child, associationsNavigatedFwd);
   
                    // Each association may have multiple children ...
                    AssociatedConceptList branchItemList = child.getAssociatedConcepts();
                    for (Iterator<AssociatedConcept> branchNodes = branchItemList.iterateAssociatedConcept(); branchNodes
                            .hasNext();) {
                        AssociatedConcept branchItemNode = branchNodes.next();
                        String branchItemCode = branchItemNode.getCode();
   
                        // Add here if not in the list of excluded codes.
                        // This is also where we look to see if another level
                        // was indicated to be available. If so, mark the
                        // entry with a '+' to indicate it can be expanded.
                        if (!codesToExclude.contains(branchItemCode)) {
                            TreeItem childItem = new TreeItem(branchItemCode, getCodeDescription(branchItemNode));
                            AssociationList grandchildBranch = associationsNavigatedFwd ? branchItemNode.getSourceOf()
                                    : branchItemNode.getTargetOf();
                            if (grandchildBranch != null)
                                childItem.expandable = true;
                            ti.addChild(childNavText, childItem);
                        }
                    }
                }
            }
        }
   
        /**
         * Prints the given tree item, recursing through all branches.
         *
         * @param ti
         */
        protected void printTree(TreeItem ti, String focusCode, int depth) {
            StringBuffer indent = new StringBuffer();
            for (int i = 0; i < depth * 2; i++)
                indent.append("| ");
   
            StringBuffer codeAndText = new StringBuffer(indent).append(focusCode.equals(ti.code) ? ">>>>" : "").append(
                    ti.code).append(':').append(ti.text.length() > 64 ? ti.text.substring(0, 62) + "..." : ti.text).append(
                    ti.expandable ? " [+]" : "");
            Util.displayMessage(codeAndText.toString());
   
            indent.append("| ");
            for (String association : ti.assocToChildMap.keySet()) {
                Util.displayMessage(indent.toString() + association);
                List<TreeItem> children = ti.assocToChildMap.get(association);
                Collections.sort(children);
                for (TreeItem childItem : children)
                    printTree(childItem, focusCode, depth + 1);
            }
        }
   
        // /////////////////////////////////////////////////////
        // Helper Methods
        // /////////////////////////////////////////////////////
   
        /**
         * Returns the entity description for the given code.
         */
        protected String getCodeDescription(LexBIGService lbsvc, String scheme, CodingSchemeVersionOrTag csvt, String code)
                throws LBException {
   
            CodedNodeSet cns = lbsvc.getCodingSchemeConcepts(scheme, csvt);
            cns = cns.restrictToCodes(Constructors.createConceptReferenceList(code, scheme));
            ResolvedConceptReferenceList rcrl = cns.resolveToList(null, noopList_, null, 1);
            if (rcrl.getResolvedConceptReferenceCount() > 0) {
                EntityDescription desc = rcrl.getResolvedConceptReference(0).getEntityDescription();
                if (desc != null)
                    return desc.getContent();
            }
            return "<Not assigned>";
        }
   
        /**
         * Returns the entity description for the given resolved concept reference.
         */
        protected String getCodeDescription(ResolvedConceptReference ref) throws LBException {
            EntityDescription desc = ref.getEntityDescription();
            if (desc != null)
                return desc.getContent();
            return "<Not assigned>";
        }
   
        /**
         * Returns the label to display for the given association and directional
         * indicator.
         */
        protected String getDirectionalLabel(LexBIGServiceConvenienceMethods lbscm, String scheme,
                CodingSchemeVersionOrTag csvt, Association assoc, boolean navigatedFwd) throws LBException {
   
            String assocLabel = navigatedFwd ? lbscm.getAssociationForwardName(assoc.getAssociationName(), scheme, csvt)
                    : lbscm.getAssociationReverseName(assoc.getAssociationName(), scheme, csvt);
            if (StringUtils.isBlank(assocLabel))
                assocLabel = (navigatedFwd ? "" : "[Inverse]") + assoc.getAssociationName();
            return assocLabel;
        }
   
        /**
         * Resolves one or more paths from the hierarchy root to the given code
         * through a list of connected associations defined by the hierarchy.
         */
        protected AssociationList getPathsFromRoot(LexBIGService lbsvc, LexBIGServiceConvenienceMethods lbscm,
                String scheme, CodingSchemeVersionOrTag csvt, String hierarchyID, String focusCode,
                Map<String, EntityDescription> codesToDescriptions) throws LBException {
   
            // Get paths from the focus code to the root from the
            // convenience method. All paths are resolved. If only
            // one path is required, it would be possible to use
            // HierarchyPathResolveOption.ONE to reduce processing
            // and improve overall performance.
            AssociationList pathToRoot = lbscm.getHierarchyPathToRoot(scheme, csvt, null, focusCode, false,
                    HierarchyPathResolveOption.ALL, null);
   
            // But for purposes of this example we need to display info
            // in order coming from root direction. Process the paths to root
            // recursively to reverse the order for processing ...
            AssociationList pathFromRoot = new AssociationList();
            for (int i = pathToRoot.getAssociationCount() - 1; i >= 0; i--)
                reverseAssoc(lbsvc, lbscm, scheme, csvt, pathToRoot.getAssociation(i), pathFromRoot, codesToDescriptions);
   
            return pathFromRoot;
        }
   
        /**
         * Returns a description of the hierarchy defined by the given coding scheme
         * and matching the specified ID.
         */
        protected static SupportedHierarchy getSupportedHierarchy(LexBIGService lbsvc, String scheme,
                CodingSchemeVersionOrTag csvt, String hierarchyID) throws LBException {
   
            CodingScheme cs = lbsvc.resolveCodingScheme(scheme, csvt);
            if (cs == null) {
                throw new LBResourceUnavailableException("Coding scheme not found: " + scheme);
            }
            for (SupportedHierarchy h : cs.getMappings().getSupportedHierarchy())
                if (h.getLocalId().equals(hierarchyID))
                    return h;
            throw new LBResourceUnavailableException("Hierarchy not defined: " + hierarchyID);
        }
   
        /**
         * Recursive call to reverse order of the given association list, adding
         * results to the given list. In context of this program we use this
         * technique to determine the path from root, starting from the path to root
         * provided by the standard convenience method.
         */
        protected AssociationList reverseAssoc(LexBIGService lbsvc, LexBIGServiceConvenienceMethods lbscm, String scheme,
                CodingSchemeVersionOrTag csvt, Association assoc, AssociationList addTo,
                Map<String, EntityDescription> codeToEntityDescriptionMap) throws LBException {
   
            ConceptReference acRef = assoc.getAssociationReference();
            AssociatedConcept acFromRef = new AssociatedConcept();
            acFromRef.setCodingSchemeName(acRef.getCodingSchemeName());
            acFromRef.setConceptCode(acRef.getConceptCode());
            AssociationList acSources = new AssociationList();
            acFromRef.setIsNavigable(Boolean.TRUE);
            acFromRef.setSourceOf(acSources);
   
            // Use cached description if available (should be cached
            // for all but original root) ...
            if (codeToEntityDescriptionMap.containsKey(acRef.getConceptCode()))
                acFromRef.setEntityDescription(codeToEntityDescriptionMap.get(acRef.getConceptCode()));
            // Otherwise retrieve on demand ...
            else
                acFromRef.setEntityDescription(Constructors.createEntityDescription(getCodeDescription(lbsvc, scheme, csvt,
                        acRef.getConceptCode())));
   
            AssociatedConceptList acl = assoc.getAssociatedConcepts();
            for (AssociatedConcept ac : acl.getAssociatedConcept()) {
                // Create reverse association (same non-directional name)
                Association rAssoc = new Association();
                rAssoc.setAssociationName(assoc.getAssociationName());
   
                // On reverse, old associated concept is new reference point.
                ConceptReference ref = new ConceptReference();
                ref.setCodingSchemeName(ac.getCodingSchemeName());
                ref.setConceptCode(ac.getConceptCode());
                rAssoc.setAssociationReference(ref);
   
                // And old reference is new associated concept.
                AssociatedConceptList rAcl = new AssociatedConceptList();
                rAcl.addAssociatedConcept(acFromRef);
                rAssoc.setAssociatedConcepts(rAcl);
   
                // Set reverse directional name, if available.
                String dirName = assoc.getDirectionalName();
                if (dirName != null)
                    try {
                        rAssoc.setDirectionalName(lbscm.isForwardName(scheme, csvt, dirName) ? lbscm
                                .getAssociationReverseName(assoc.getAssociationName(), scheme, csvt) : lbscm
                                .getAssociationReverseName(assoc.getAssociationName(), scheme, csvt));
                    } catch (LBException e) {
                    }
   
                // Save code desc for future reference when setting up
                // concept references in recursive calls ...
                codeToEntityDescriptionMap.put(ac.getConceptCode(), ac.getEntityDescription());
   
                AssociationList sourceOf = ac.getSourceOf();
                if (sourceOf != null)
                    for (Association sourceAssoc : sourceOf.getAssociation()) {
                        AssociationList pos = reverseAssoc(lbsvc, lbscm, scheme, csvt, sourceAssoc, addTo,
                                codeToEntityDescriptionMap);
                        pos.addAssociation(rAssoc);
                    }
                else
                    addTo.addAssociation(rAssoc);
            }
            return acSources;
        }
   
        // /////////////////////////////////////////////////////
        // Helper classes
        // /////////////////////////////////////////////////////
   
        /**
         * Inner class to hold tree items for printout.
         */
        protected class TreeItem implements Comparable<TreeItem> {
            public String code = null;
            public String text = null;
            public boolean expandable = false;
            public Map<String, List<TreeItem>> assocToChildMap = new TreeMap<String, List<TreeItem>>();
   
            public boolean equals(Object o) {
                return o instanceof TreeItem && code.compareTo(((TreeItem) o).code) == 0;
            }
   
            public int compareTo(TreeItem ti) {
                String c1 = code;
                String c2 = ti.code;
                if (c1.startsWith("@"))
                    return 1;
                if (c2.startsWith("@"))
                    return -1;
                return c1.compareTo(c2);
            }
   
            public TreeItem(String code, String text) {
                super();
                this.code = code;
                this.text = text;
            }
   
            public void addAll(String assocText, List<TreeItem> children) {
                for (TreeItem item : children)
                    addChild(assocText, item);
            }
   
            public void addChild(String assocText, TreeItem child) {
                List<TreeItem> children = assocToChildMap.get(assocText);
                if (children == null) {
                    children = new ArrayList<TreeItem>();
                    assocToChildMap.put(assocText, children);
                }
                int i;
                if ((i = children.indexOf(child)) >= 0) {
                    TreeItem existingTreeItem = children.get(i);
                    for (String assoc : child.assocToChildMap.keySet()) {
                        List<TreeItem> toAdd = child.assocToChildMap.get(assoc);
                        if (!toAdd.isEmpty()) {
                            existingTreeItem.addAll(assoc, toAdd);
                            existingTreeItem.expandable = false;
                        }
                    }
                } else
                    children.add(child);
            }
        }
   
    }