/* * 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.converters.sharedUtils.umlsUtils; //~--- JDK imports ------------------------------------------------------------ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Set; //~--- non-JDK imports -------------------------------------------------------- import sh.isaac.converters.sharedUtils.ConsoleUtil; //~--- classes ---------------------------------------------------------------- /** * The Class Relationship. */ public class Relationship { /** The preferred name map. */ private static HashMap<String, String> preferredNameMap = new HashMap<>(); //~--- static initializers ------------------------------------------------- static { // I didn't like the names they provide in the UMLS - so I put those in as descriptions, and use these as the preferred terms. preferredNameMap.put("PAR", "is parent"); // This is a confusing mess in UMLS - they define it as "has parent" but they really mean "is the parent of" preferredNameMap.put("CHD", "is child"); preferredNameMap.put("SY", "synonym"); preferredNameMap.put("SIB", "sibling"); preferredNameMap.put("DEL", "deleted"); preferredNameMap.put("RB", "broader"); preferredNameMap.put("RN", "narrower"); preferredNameMap.put("AQ", "allowed qualifier"); preferredNameMap.put("RO", "other"); preferredNameMap.put("RQ", "related, possibly synonymous"); preferredNameMap.put("XR", "not related"); preferredNameMap.put("RL", "alike"); preferredNameMap.put("RU", "related"); preferredNameMap.put("QB", "qualified by"); } //~--- fields -------------------------------------------------------------- /** The name 1 snomed code. */ private final HashSet<String> name1SnomedCode = new HashSet<>(); /** The name 2 snomed code. */ private final HashSet<String> name2SnomedCode = new HashSet<>(); /** The name 1. */ private String name1; /** The description 1. */ private String description1; /** The name 2. */ private String name2; /** The description 2. */ private String description2; /** The name 1 rel type. */ private String name1RelType; /** The name 2 rel type. */ private String name2RelType; /** The is rela. */ private final boolean isRela; /** The swap. */ private Boolean swap; //~--- constructors -------------------------------------------------------- /** * Instantiates a new relationship. * * @param isRela the is rela */ public Relationship(boolean isRela) { this.isRela = isRela; } //~--- methods ------------------------------------------------------------- /** * Adds the description. * * @param name the name * @param niceName the nice name */ public void addDescription(String name, String niceName) { if (name.equals(this.name1)) { if (this.description1 != null) { throw new RuntimeException("Oops"); } this.description1 = niceName; } else if (name.equals(this.name2)) { if (this.description2 != null) { throw new RuntimeException("Oops"); } this.description2 = niceName; } else if ((this.name1 == null) && (this.name2 == null)) { if (this.description1 != null) { throw new RuntimeException("Oops"); } this.name1 = name; this.description1 = niceName; } else { throw new RuntimeException("Oops"); } } /** * Adds the rel inverse. * * @param name the name * @param inverseRelName the inverse rel name */ public void addRelInverse(String name, String inverseRelName) { if ((this.name1 == null) && (this.name2 == null)) { this.name1 = name; this.name2 = inverseRelName; } else if (name.equals(this.name1)) { if (this.name2 == null) { this.name2 = inverseRelName; } else if (!this.name2.equals(inverseRelName)) { throw new RuntimeException("oops"); } } else if (name.equals(this.name2)) { if (this.name1 == null) { this.name1 = inverseRelName; } else if (!this.name1.equals(inverseRelName)) { throw new RuntimeException("oops"); } } else { throw new RuntimeException("oops"); } } /** * Adds the rel type. * * @param name the name * @param type the type */ public void addRelType(String name, String type) { if (name.equals(this.name1)) { if (this.name1RelType == null) { this.name1RelType = type; } else if (!this.name1RelType.equals(type)) { throw new RuntimeException("oops"); } } else if (name.equals(this.name2)) { if (this.name2RelType == null) { this.name2RelType = type; } else if (!this.name2RelType.equals(type)) { throw new RuntimeException("oops"); } } else { throw new RuntimeException("oops"); } } /** * Adds the snomed code. * * @param name the name * @param code the code */ public void addSnomedCode(String name, String code) { if (name.equals(this.name1)) { this.name1SnomedCode.add(code); } else if (name.equals(this.name2)) { this.name2SnomedCode.add(code); } else { throw new RuntimeException("oops"); } } //~--- get methods --------------------------------------------------------- /** * Gets the alt name. * * @return the alt name */ public String getAltName() { return preferredNameMap.get(getFSNName()); } /** * Gets the description. * * @return the description */ public String getDescription() { return this.swap ? this.description2 : this.description1; } /** * Gets the FSN name. * * @return the FSN name */ public String getFSNName() { return this.swap ? this.name2 : this.name1; } /** * Gets the inverse alt name. * * @return the inverse alt name */ public String getInverseAltName() { return (getInverseFSNName() == null) ? null : preferredNameMap.get(getInverseFSNName()); } /** * Gets the inverse description. * * @return the inverse description */ public String getInverseDescription() { return this.swap ? this.description1 : this.description2; } /** * Gets the inverse FSN name. * * @return the inverse FSN name */ public String getInverseFSNName() { return this.swap ? this.name1 : this.name2; } /** * Gets the inverse rel snomed code. * * @return the inverse rel snomed code */ public Set<String> getInverseRelSnomedCode() { return this.swap ? this.name1SnomedCode : this.name2SnomedCode; } /** * Gets the inverse rel type. * * @return the inverse rel type */ public String getInverseRelType() { return this.swap ? this.name1RelType : this.name2RelType; } /** * Gets the checks if rela. * * @return the checks if rela */ public boolean getIsRela() { return this.isRela; } /** * Gets the rel snomed code. * * @return the rel snomed code */ public Set<String> getRelSnomedCode() { return this.swap ? this.name2SnomedCode : this.name1SnomedCode; } /** * Gets the rel type. * * @return the rel type */ public String getRelType() { return this.swap ? this.name2RelType : this.name1RelType; } //~--- set methods --------------------------------------------------------- /** * Set swap. * * @param c the c * @param tablePrefix the table prefix * @throws SQLException the SQL exception */ public void setSwap(Connection c, String tablePrefix) throws SQLException { if (this.swap != null) { throw new RuntimeException("Swap already set!"); } if ((this.name1 == null) && (this.name2 != null)) { this.swap = true; } else if ((this.name2 == null) && (this.name1 != null)) { this.swap = false; } else if (this.name1.equals(this.name2)) { this.swap = false; } else if (this.name1.equals("RN") || this.name2.equals("RN")) // narrower as primary { this.swap = this.name2.equals("RN"); } else if (this.name1.equals("AQ") || this.name2.equals("AQ")) // allowed qualifier as primary { this.swap = this.name2.equals("AQ"); } else if (this.name1.equals("CHD") || this.name2.equals("CHD")) // is child as primary { this.swap = this.name2.equals("CHD"); } else { // Use the primary assignments above, to figure out the more detailed assignments (where possible) final Statement s = c.createStatement(); final ResultSet rs = s.executeQuery("Select distinct REL from " + tablePrefix + "REL where RELA='" + this.name1 + "'"); while (rs.next()) { if (rs.getString("REL") .equals("RO")) { // ignore these - they sometimes occur in tandem with a directional one below continue; } if (this.name1.equals("mapped_from")) { // This one is all over the board in UMLS, sometimes tied to RB, sometimes RN, or a whole bunch of other types. // Just let the code below handle it. break; } final String rel = rs.getString("REL"); if (this.swap != null) { // this is a bug? in umls - has_part and inverse_isa appears with both PAR and RB rels - but we set the swap the same for each, so ignore the second one. // inverse_isa also uses RQ, but just ignore that too. if ((this.name1.equals("inverse_isa") || this.name1.equals("has_part")) && (rel.equals("PAR") || rel.equals("RB") || rel.equals("RQ"))) { continue; } else { throw new RuntimeException("too many results on rela " + this.name1); } } if (new HashSet<>(Arrays.asList(new String[] { "RB", "RN", "QB", "AQ", "PAR", "CHD" })).contains(rel)) { if (rel.equals("RN") || rel.equals("AQ") || rel.equals("CHD")) { this.swap = false; } else { this.swap = true; } } } rs.close(); s.close(); // TODO utilize MRREL DIR column - see if that helps. Also talk to Brian, see if there is better code for this. if (this.swap == null) { if (this.name1.startsWith("inverse_") || this.name2.startsWith("inverse_")) // inverse_ things as secondary { this.swap = this.name1.startsWith("inverse_"); } else if (this.name1.startsWith("has_") || this.name2.startsWith("has_")) // has_ things as primary { this.swap = this.name2.startsWith("has_"); } else if (this.name1.startsWith("may_be") || this.name2.startsWith("may_be")) // may_be X as primary { this.swap = this.name2.startsWith("may_be"); } else if (this.name1.contains("_from") || this.name2.contains("_from")) // X_from as primary { this.swap = this.name2.contains("_from"); } else if (this.name1.contains("_by") || this.name2.contains("_by")) // X_by as primary { this.swap = this.name2.contains("_by"); } else if (this.name1.contains("_in_") || this.name2.contains("_in_")) // X_in_ as primary { this.swap = this.name2.contains("_in_"); } else if (this.name1.endsWith("_in") || this.name2.endsWith("_in")) // X_in as primary { this.swap = this.name2.endsWith("_in"); } else if (this.name1.contains("_is") || this.name2.contains("_is")) // X_is as primary { this.swap = this.name2.contains("_is"); } else if (this.name1.startsWith("is_") || this.name2.startsWith("is_")) // is_ as primary { this.swap = this.name2.startsWith("is_"); } else if (this.name1.contains("_has") || this.name2.contains("_has")) // X_has as secondary { this.swap = this.name1.contains("_has"); } else if (this.name1.equals("larger_than") || this.name2.equals("larger_than")) // swap smaller_than to primary { this.swap = this.name1.equals("larger_than"); } else if (this.name1.equals("due_to") || this.name2.equals("due_to")) // due_to as primary, cause_of secondary { this.swap = this.name2.equals("due_to"); } else if (this.name1.equals("occurs_after") || this.name2.equals("occurs_after")) // occurs_after as primary, occurs_before secondary { this.swap = this.name2.equals("occurs_after"); } } } if (this.swap == null) { ConsoleUtil.println("No rel direction preference specified for " + this.name1 + "/" + this.name2 + " - using " + this.name1 + " as primary"); this.swap = false; } } }