JAL-4020 Added search for PyMOLWinWithConsole.bat (and PyMOLWin.exe) in likely places...
[jalview.git] / src / ext / edu / ucsf / rbvi / strucviz2 / ChimUtils.java
index aedf657..1b0285c 100644 (file)
@@ -1,9 +1,42 @@
+/* vim: set ts=2: */
+/**
+ * Copyright (c) 2006 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions, and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions, and the following
+ *      disclaimer in the documentation and/or other materials provided
+ *      with the distribution.
+ *   3. Redistributions must acknowledge that this software was
+ *      originally developed by the UCSF Computer Graphics Laboratory
+ *      under support by the NIH National Center for Research Resources,
+ *      grant P41-RR01081.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
 package ext.edu.ucsf.rbvi.strucviz2;
 
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import org.slf4j.Logger;
@@ -11,745 +44,1002 @@ import org.slf4j.LoggerFactory;
 
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 
+public abstract class ChimUtils
+{
 
-public abstract class ChimUtils {
-
-       private static Logger logger = LoggerFactory
-                       .getLogger(ChimUtils.class);
-
-       static int MAX_SUB_MODELS = 1000;
-
-       public static final HashMap<String, String> aaNames;
-
-       public static String RESIDUE_ATTR = "ChimeraResidue";
-       public static String RINALYZER_ATTR = "RINalyzerResidue";
-       public static String DEFAULT_STRUCTURE_KEY = "pdbFileName";
-
-       /**
-        * Parse the model number returned by Chimera and return the int value
-        */
-       // invoked by the ChimeraModel constructor
-       // line = model id #0 type Molecule name 1ert
-       public static int[] parseModelNumber(String inputLine) {
-               int hash = inputLine.indexOf('#');
-               int space = inputLine.indexOf(' ', hash);
-               int decimal = inputLine.substring(hash + 1, space).indexOf('.');
-               // model number is between hash+1 and space
-               int modelNumber = -1;
-               int subModelNumber = 0;
-               try {
-                       if (decimal > 0) {
-                               subModelNumber = Integer.parseInt(inputLine.substring(decimal + hash + 2, space));
-                               space = decimal + hash + 1;
-                       }
-                       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
-               } catch (Exception e) {
-                       logger.warn("Unexpected return from Chimera: " + inputLine, e);
-               }
-               return new int[] { modelNumber, subModelNumber };
-       }
-
-       /**
-        * Parse the model number returned by Chimera and return the int value
-        */
-       // invoked by openModel in ChimeraManager
-       // line: #1, chain A: hiv-1 protease
-       public static int[] parseOpenedModelNumber(String inputLine) {
-               int hash = inputLine.indexOf('#');
-               int space = inputLine.indexOf(',', hash);
-               int decimal = inputLine.substring(hash + 1, space).indexOf('.');
-               // model number is between hash+1 and space
-               int modelNumber = -1;
-               int subModelNumber = 0;
-               try {
-                       if (decimal > 0) {
-                               subModelNumber = Integer.parseInt(inputLine.substring(decimal + hash + 2, space));
-                               space = decimal + hash + 1;
-                       }
-                       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
-               } catch (Exception e) {
-                       logger.warn("Unexpected return from Chimera: " + inputLine, e);
-               }
-               return new int[] { modelNumber, subModelNumber };
-       }
-
-       /**
-        * Parse the model identifier returned by Chimera and return the String value
-        */
-       // invoked by the ChimeraModel constructor
-       // line = model id #0 type Molecule name 1ert
-       public static String parseModelName(String inputLine) {
-               int start = inputLine.indexOf("name ");
-               if (start < 0)
-                       return null;
-               // Might get a quoted string (don't understand why, but there you have it)
-               if (inputLine.startsWith("\"", start + 5)) {
-                       start += 6; // Skip over the first quote
-                       int end = inputLine.lastIndexOf('"');
-                       if (end >= 1) {
-                               return inputLine.substring(start, end);
-                       } else
-                               return inputLine.substring(start);
-               } else {
-                       return inputLine.substring(start + 5);
-               }
-       }
-
-       public static Color parseModelColor(String inputLine) {
-               try {
-                       int colorStart = inputLine.indexOf("color ");
-                       String colorString = inputLine.substring(colorStart + 6);
-                       String[] rgbStrings = colorString.split(",");
-                       float[] rgbValues = new float[4];
-                       for (int i = 0; i < rgbStrings.length; i++) {
-                               Float f = new Float(rgbStrings[i]);
-                               rgbValues[i] = f.floatValue();
-                       }
-                       if (rgbStrings.length == 4) {
-                               return new Color(rgbValues[0], rgbValues[1], rgbValues[2], rgbValues[3]);
-                       } else {
-                               return new Color(rgbValues[0], rgbValues[1], rgbValues[2]);
-                       }
-               } catch (Exception ex) {
-                       logger.warn("Unexpected return from Chimera: " + inputLine, ex);
-               }
-               return Color.white;
-       }
-
-       /**
-        * Create the key to use for forming the model/submodel key into the modelHash
-        * 
-        * @param model
-        *            the model number
-        * @param subModel
-        *            the submodel number
-        * @return the model key as an Integer
-        */
-       public static Integer makeModelKey(int model, int subModel) {
-               return new Integer(model * MAX_SUB_MODELS + subModel);
-       }
-
-       // invoked by the getResdiue (parseConnectivityReplies in CreateStructureNetworkTask)
-       // atomSpec = #0:1.A or #1:96.B@N
-       public static ChimeraModel getModel(String atomSpec, ChimeraManager chimeraManager) {
-               // System.out.println("getting model for "+atomSpec);
-               String[] split = atomSpec.split(":");
-               // No model specified....
-               if (split[0].length() == 0) {
-                       logger.info("Unexpected return from Chimera: " + atomSpec);
-                       return null;
-               }
-               // System.out.println("model = "+split[0].substring(1));
-               int model = 0;
-               int submodel = 0;
-               try {
-                       String[] subSplit = split[0].substring(1).split("\\.");
-                       if (subSplit.length > 0)
-                               model = Integer.parseInt(subSplit[0]);
-                       else
-                               model = Integer.parseInt(split[0].substring(1));
-
-                       if (subSplit.length > 1)
-                               submodel = Integer.parseInt(subSplit[1]);
-               } catch (Exception e) {
-                       // ignore
-                       logger.warn("Unexpected return from Chimera: " + atomSpec, e);
-               }
-               return chimeraManager.getChimeraModel(model, submodel);
-       }
-
-       // invoked by the parseConnectivityReplies in CreateStructureNetworkTask
-       // atomSpec = #0:1.A or #1:96.B@N
-       public static ChimeraResidue getResidue(String atomSpec, ChimeraManager chimeraManager) {
-               // System.out.println("Getting residue from: "+atomSpec);
-               ChimeraModel model = getModel(atomSpec, chimeraManager); // Get the model
-               if (model == null) {
-                       model = chimeraManager.getChimeraModel();
-               }
-               return getResidue(atomSpec, model);
-       }
-
-       // invoked by the getResdiue (parseConnectivityReplies in CreateStructureNetworkTask)
-       // atomSpec = #0:1.A or #1:96.B@N
-       public static ChimeraResidue getResidue(String atomSpec, ChimeraModel model) {
-               // System.out.println("Getting residue from: "+atomSpec);
-               String[] split = atomSpec.split(":|@");
-
-               // Split into residue and chain
-               String[] residueChain = split[1].split("\\.");
-
-               if (residueChain[0].length() == 0) {
-                       logger.info("Unexpected return from Chimera: " + atomSpec);
-                       return null;
-               }
-
-               if (residueChain.length == 2 && residueChain[1].length() > 0) {
-                       ChimeraChain chain = model.getChain(residueChain[1]);
-                       return chain.getResidue(residueChain[0]);
-               }
-               return model.getResidue("_", residueChain[0]);
-       }
-
-       public static ChimeraChain getChain(String atomSpec, ChimeraModel model) {
-               String[] split = atomSpec.split(":|@");
-
-               // Split into residue and chain
-               String[] residueChain = split[1].split("\\.");
-               if (residueChain.length == 1) {
-                       logger.info("Unexpected return from Chimera: " + atomSpec);
-                       return null;
-               }
-               return model.getChain(residueChain[1]);
-       }
-
-       public static String getAtomName(String atomSpec) {
-               String[] split = atomSpec.split("@");
-               if (split.length > 1) {
-                       return split[1];
-               }
-               return atomSpec;
-       }
-
-       public static boolean isBackbone(String atom) {
-               if (atom.equals("C") || atom.equals("CA") || atom.equals("N") || atom.equals("O")
-                               || atom.equals("H"))
-                       return true;
-               return false;
-       }
-
-       public static String getIntSubtype(String node, String atom) {
-               String[] split = node.split("#| ");
-               String resType = "";
-               if (split.length == 2) {
-                       resType = split[0].trim().toUpperCase();
-               } else if (split.length == 3) {
-                       resType = split[1].trim().toUpperCase();
-               }
-               if (resType.equalsIgnoreCase("HOH") || resType.equalsIgnoreCase("WAT")) {
-                       return "water";
-               } else if (aaNames.containsKey(resType)) {
-                       if (atom.equals("C") || atom.equals("CA") || atom.equals("N") || atom.equals("O")
-                                       || atom.equals("H")) {
-                               return "mc";
-                       } else {
-                               return "sc";
-                       }
-               } else {
-                       return "other";
-               }
-       }
-
-
-       public static String[] getResKeyParts(String resKey) {
-               // [pdbID[.modelNo]#][residueID][.chainID]
-               // pdbID := 4-character code | "URL" | "path"
-               String[] resKeyParts = new String[4];
-               String[] split = resKey.split("#");
-               String resChain = null;
-               // if no "#" then it is either only a pdb id or a residue or a chain
-               if (split.length == 1) {
-                       // pdb id without model
-                       if (resKey.length() == 4 && resKey.indexOf("\\.") < 0) {
-                               parseModelID(resKey, resKeyParts);
-                       }
-                       // pdb link or file
-                       else if (resKey.startsWith("\"")) {
-                               parseModelID(resKey, resKeyParts);
-                       }
-                       // chain and residue or model and number
-                       else {
-                               String[] splitSplit = resKey.split("\\.");
-                               if (splitSplit.length == 1) {
-                                       // only a chain or a residue
-                                       resChain = resKey;
-                               } else {
-                                       try {
-                                               // pdb with a model
-                                               Integer.parseInt(splitSplit[1]);
-                                               parseModelID(resKey, resKeyParts);
-                                       } catch (NumberFormatException ex) {
-                                               // residue and chain
-                                               resChain = resKey;
-                                       }
-                               }
-                       }
-               } else if (split.length == 2) {
-                       // model and residue+chain
-                       parseModelID(split[0], resKeyParts);
-                       resChain = split[1];
-               } else {
-                       // model string with "#"
-                       // TODO: [Optional] Are there more possibilities?
-                       parseModelID(resKey.substring(0, resKey.lastIndexOf("#")), resKeyParts);
-                       resChain = resKey.substring(resKey.lastIndexOf("#") + 1, resKey.length());
-               }
-               if (resChain != null) {
-                       //System.out.println(resChain);
-                       String[] resChainSplit = resChain.split("\\.");
-                       if (resChainSplit.length == 1) {
-                               // TODO: [Optional] Find a better way to distinguish between chain and residue
-                               // if only one character and not an int, probably a chain
-                               if (resChainSplit[0].length() == 1) {
-                                       try {
-                                               Integer.parseInt(resChainSplit[0]);
-                                               resKeyParts[3] = resChainSplit[0];
-                                       } catch (NumberFormatException ex) {
-                                               resKeyParts[2] = resChainSplit[0];
-                                       }
-                               } else {
-                                       resKeyParts[3] = resChainSplit[0];
-                               }
-                       } else if (resChainSplit.length == 2) {
-                               resKeyParts[2] = resChainSplit[0];
-                               resKeyParts[3] = resChainSplit[1];
-                       } else {
-                               // too many dots?
-                               logger.info("Could not parse residue identifier: " + resKey);
-                       }
-               }
-               // String print = "";
-               // for (int i = 0; i < resKeyParts.length; i++) {
-               // if (resKeyParts[i] == null) {
-               // print += i + ": null\t";
-               // } else {
-               // print += i + ": " + resKeyParts[i] + ";";
-               // }
-               // }
-               // System.out.println(print);
-               return resKeyParts;
-       }
-
-       public static void parseModelID(String modelID, String[] resKeyParts) {
-               if (modelID.startsWith("\"")) {
-                       if (modelID.endsWith("\"")) {
-                               resKeyParts[0] = modelID.substring(1, modelID.length() - 1);
-                               return;
-                       } else {
-                               try {
-                                       Integer.parseInt(modelID.substring(modelID.lastIndexOf("\"") + 2,
-                                                       modelID.length()));
-                                       resKeyParts[0] = modelID.substring(0, modelID.lastIndexOf("\"") - 1);
-                                       resKeyParts[1] = modelID.substring(modelID.lastIndexOf("\"") + 2,
-                                                       modelID.length());
-                               } catch (NumberFormatException ex) {
-                                       resKeyParts[0] = modelID.substring(1);
-                               }
-                       }
-               } else {
-                       String[] modelIDNo = modelID.split("\\.");
-                       if (modelIDNo.length == 1) {
-                               resKeyParts[0] = modelIDNo[0];
-                       } else if (modelIDNo.length == 2) {
-                               try {
-                                       Integer.parseInt(modelIDNo[1]);
-                                       resKeyParts[0] = modelIDNo[0];
-                                       resKeyParts[1] = modelIDNo[1];
-                               } catch (NumberFormatException ex) {
-                                       resKeyParts[0] = modelID;
-                               }
-                       } else {
-                               logger.info("Could not parse model identifier: " + modelID);
-                       }
-               }
-       }
-
-       /**
-        * This method takes a Cytoscape attribute specification ([structure#][residue][.chainID]) and
-        * returns the lowest-level object referenced by the spec. For example, if the spec is "1tkk",
-        * this method will return a ChimeraModel. If the spec is ".A", it will return a ChimeraChain,
-        * etc.
-        * 
-        * @param attrSpec
-        *            the specification string
-        * @param chimeraManager
-        *            the Chimera object we're currently using
-        * @return a ChimeraStructuralObject of the lowest type
-        */
-       public static ChimeraStructuralObject fromAttributeOld(String attrSpec,
-                       ChimeraManager chimeraManager) {
-               if (attrSpec == null || attrSpec.indexOf(',') > 0 || attrSpec.indexOf('-') > 0) {
-                       // No support for either lists or ranges
-                       logger.warn("No support for identifier: " + attrSpec);
-                       return null;
-               }
-
-               String residue = null;
-               String model = null;
-               String chain = null;
-
-               ChimeraModel chimeraModel = null;
-               ChimeraChain chimeraChain = null;
-               ChimeraResidue chimeraResidue = null;
-
-               // System.out.println("Getting object from attribute: "+attrSpec);
-               try {
-                       String[] split = attrSpec.split("#");
-                       String resChain = null;
-                       if (split.length == 1) {
-                               // no model
-                               resChain = split[0];
-                       } else if (split.length == 2) {
-                               // model and rest
-                               model = split[0];
-                               resChain = split[1];
-                       } else {
-                               // model string with "#"
-                               model = attrSpec.substring(0, attrSpec.lastIndexOf("#"));
-                               resChain = attrSpec.substring(attrSpec.lastIndexOf("#") + 1, attrSpec.length());
-                       }
-                       if (resChain != null) {
-                               String[] resChainSplit = resChain.split("\\.");
-                               if (resChainSplit.length == 1) {
-                                       residue = resChainSplit[0];
-                               } else if (resChainSplit.length == 2) {
-                                       residue = resChainSplit[0];
-                                       chain = resChainSplit[1];
-                               } else {
-                                       // too many dots?
-                                       logger.warn("No support for identifier: " + attrSpec);
-                               }
-                       }
-
-                       // if (split.length == 1) {
-                       // // No model
-                       // residue = split[0];
-                       // } else if (split.length == 3) {
-                       // // We have all three
-                       // model = split[0];
-                       // residue = split[1];
-                       // chain = split[2];
-                       // } else if (split.length == 2 && attrSpec.indexOf('#') > 0) {
-                       // // Model and Residue
-                       // model = split[0];
-                       // residue = split[1];
-                       // } else {
-                       // // Residue and Chain
-                       // residue = split[0];
-                       // chain = split[1];
-                       // }
-
-                       // System.out.println("model = " + model + " chain = " + chain + " residue = " +
-                       // residue);
-                       if (model != null) {
-                               List<ChimeraModel> models = chimeraManager.getChimeraModels(model,
-                                               ModelType.PDB_MODEL);
-                               if (models.size() == 1) {
-                                       chimeraModel = models.get(0);
-                               } else {
-                                       try {
-                                               chimeraModel = chimeraManager.getChimeraModel(Integer.valueOf(model), 0);
-                                       } catch (NumberFormatException ex) {
-                                               // ignore
-                                       }
-                               }
-                       }
-                       if (chimeraModel == null) {
-                               chimeraModel = chimeraManager.getChimeraModel();
-                       }
-                       // System.out.println("ChimeraModel = " + chimeraModel);
-
-                       if (chain != null) {
-                               chimeraChain = chimeraModel.getChain(chain);
-                               // System.out.println("ChimeraChain = " + chimeraChain);
-                       }
-                       if (residue != null) {
-                               if (chimeraChain != null) {
-                                       chimeraResidue = chimeraChain.getResidue(residue);
-                               } else {
-                                       chimeraResidue = chimeraModel.getResidue("_", residue);
-                               }
-                               // System.out.println("ChimeraResidue = " + chimeraResidue);
-                       }
-
-                       if (chimeraResidue != null)
-                               return chimeraResidue;
-
-                       if (chimeraChain != null)
-                               return chimeraChain;
-
-                       if (chimeraModel != null)
-                               return chimeraModel;
-
-               } catch (Exception ex) {
-                       logger.warn("Could not parse residue identifier: " + attrSpec, ex);
-               }
-               return null;
-       }
-
-       public static ChimeraStructuralObject fromAttribute(String attrSpec,
-                       ChimeraManager chimeraManager) {
-               // TODO: Make sure it is OK to remove this: || attrSpec.indexOf('-') > 0
-               if (attrSpec == null || attrSpec.indexOf(',') > 0) {
-                       // No support for either lists or ranges
-                       // System.out.println("No support for identifier: " + attrSpec);
-                       logger.warn("No support for identifier: " + attrSpec);
-                       return null;
-               }
-               String[] modelIDNoResChain = getResKeyParts(attrSpec);
-
-               ChimeraModel chimeraModel = null;
-               ChimeraChain chimeraChain = null;
-               ChimeraResidue chimeraResidue = null;
-
-               // System.out.println("Getting object from attribute: "+attrSpec);
-               try {
-                       if (modelIDNoResChain[0] != null) {
-                               String modelID = modelIDNoResChain[0];
-                               List<ChimeraModel> models = chimeraManager.getChimeraModels(modelID,
-                                               ModelType.PDB_MODEL);
-                               if (models.size() == 1) { // usual case with only one model
-                                       chimeraModel = models.get(0);
-                               } else if (models.size() > 1 && modelIDNoResChain[1] != null) {
-                                       // there are several submodels
-                                       try {
-                                               int modelNo = Integer.valueOf(modelIDNoResChain[1]);
-                                               for (ChimeraModel model : models) {
-                                                       if (model.getSubModelNumber() == modelNo) {
-                                                               chimeraModel = model;
-                                                               break;
-                                                       }
-                                               }
-                                       } catch (NumberFormatException ex) {
-                                               // ignore
-                                       }
-                               } else {
-                                       // TODO: [Optional] What is this doing?
-                                       try {
-                                               chimeraModel = chimeraManager.getChimeraModel(Integer.valueOf(modelID), 0);
-                                       } catch (NumberFormatException ex) {
-                                               // ignore
-                                       }
-                               }
-                       }
-                       if (chimeraModel == null) {
-                               // TODO: [Optional] Find a better way to handle this case
-                               // If no model can be matched, continue
-                               // System.out.println("No matching model could be find for " + attrSpec);
-                               return null;
-                               // chimeraModel = chimeraManager.getChimeraModel();
-                               // logger.warn("No matching model could be find for " + attrSpec + ". Trying with "
-                               // + chimeraModel.toSpec());
-                       }
-                       // System.out.println("ChimeraModel = " + chimeraModel);
-
-                       if (modelIDNoResChain[3] != null) {
-                               chimeraChain = chimeraModel.getChain(modelIDNoResChain[3]);
-                               // System.out.println("ChimeraChain = " + chimeraChain);
-                       }
-                       if (modelIDNoResChain[2] != null) {
-                               String residue = modelIDNoResChain[2];
-                               if (chimeraChain != null) {
-                                       chimeraResidue = chimeraChain.getResidue(residue);
-                               } else if (chimeraModel.getChain("_") != null) {
-                                       chimeraResidue = chimeraModel.getResidue("_", residue);
-                               } else if (chimeraModel.getChainCount() == 1) {
-                                       chimeraResidue = chimeraModel.getResidue(chimeraModel.getChainNames()
-                                                       .iterator().next(), residue);
-                               }
-                               // System.out.println("ChimeraResidue = " + chimeraResidue);
-                       }
-
-                       if (chimeraResidue != null)
-                               return chimeraResidue;
-
-                       if (chimeraChain != null)
-                               return chimeraChain;
-
-                       if (chimeraModel != null)
-                               return chimeraModel;
-
-               } catch (Exception ex) {
-                       // System.out.println("Could not parse chimera identifier: " +
-                       // attrSpec+"("+ex.getMessage()+")");
-                       logger.warn("Could not parse chimera identifier: " + attrSpec, ex);
-               }
-               return null;
-       }
-
-       /**
-        * Search for structure references in the residue list
-        * 
-        * @param residueList
-        *            the list of residues
-        * @return a concatenated list of structures encoded in the list
-        */
-       public static String findStructures(String residueList) {
-               if (residueList == null)
-                       return null;
-               String[] residues = residueList.split(",");
-               Map<String, String> structureNameMap = new HashMap<String, String>();
-               for (int i = 0; i < residues.length; i++) {
-                       String[] components = residues[i].split("#");
-                       if (components.length > 1) {
-                               structureNameMap.put(components[0], components[1]);
-                       }
-               }
-               if (structureNameMap.isEmpty())
-                       return null;
-
-               String structure = null;
-               for (String struct : structureNameMap.keySet()) {
-                       if (structure == null)
-                               structure = new String();
-                       else
-                               structure = structure.concat(",");
-                       structure = structure.concat(struct);
-               }
-               return structure;
-       }
-
-       // invoked by openStructures in StructureManager
-       public static List<String> parseFuncRes(List<String> residueNames, String modelName) {
-               List<String> resRanges = new ArrayList<String>();
-               for (int i = 0; i < residueNames.size(); i++) {
-                       String residue = residueNames.get(i);
-                       // Parse out the structure, if there is one
-                       String[] components = residue.split("#");
-                       if (components.length > 1 && !modelName.equals(components[0])) {
-                               continue;
-                       } else if (components.length > 1) {
-                               residue = components[1];
-                       } else if (components.length == 1) {
-                               residue = components[0];
-                       }
-                       // Check to see if we have a range-spec
-                       String resRange = "";
-                       if (residue == null || residue.equals("") || residue.length() == 0) {
-                               continue;
-                       }
-                       String[] range = residue.split("-", 2);
-                       String chain = null;
-                       for (int res = 0; res < range.length; res++) {
-                               if (res == 1) {
-                                       resRange = resRange.concat("-");
-                                       if (chain != null && range[res].indexOf('.') == -1)
-                                               range[res] = range[res].concat("." + chain);
-                               }
-
-                               if (res == 0 && range.length >= 2 && range[res].indexOf('.') > 0) {
-                                       // This is a range spec with the leading residue containing a chain spec
-                                       String[] resChain = range[res].split("\\.");
-                                       chain = resChain[1];
-                                       range[res] = resChain[0];
-                               }
-                               // Fix weird SFLD syntax...
-                               if (range[res].indexOf('|') > 0 && Character.isDigit(range[res].charAt(0))) {
-                                       int offset = range[res].indexOf('|');
-                                       String str = range[res].substring(offset + 1) + range[res].substring(0, offset);
-                                       range[res] = str;
-                               }
-
-                               // Convert to legal atom-spec
-                               if (Character.isDigit(range[res].charAt(0))) {
-                                       resRange = resRange.concat(range[res]);
-                               } else if (Character.isDigit(range[res].charAt(1))) {
-                                       resRange = resRange.concat(range[res].substring(1));
-                               } else if (range[res].charAt(0) == '.') {
-                                       // Do we have a chain spec?
-                                       resRange = resRange.concat(range[res]);
-                               } else {
-                                       resRange = resRange.concat(range[res].substring(3));
-                               }
-                       }
-                       if (!resRanges.contains(resRange)) {
-                               resRanges.add(resRange);
-                       }
-               }
-               return resRanges;
-       }
-
-       static {
-               aaNames = new HashMap<String, String>();
-               aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
-               aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
-               aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
-               aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
-               aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
-               aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
-               aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
-               aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
-               aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
-               aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
-               aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
-               aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
-               aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
-               aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
-               aaNames.put("PHE", "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
-               aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
-               aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
-               aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
-               aaNames.put("TRP", "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
-               aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
-               aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
-               aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
-               aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
-               aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
-               aaNames.put("HOH", "HOH HOH Water [H]O[H]");
-       }
-
-       /**
-        * Convert the amino acid type to a full name
-        * 
-        * @param aaType
-        *            the residue type to convert
-        * @return the full name of the residue
-        */
-       public static String toFullName(String aaType) {
-               if (!aaNames.containsKey(aaType))
-                       return aaType;
-               String[] ids = ((String) aaNames.get(aaType)).split(" ");
-               return ids[2].replace('_', ' ');
-       }
-
-       /**
-        * Convert the amino acid type to a single letter
-        * 
-        * @param aaType
-        *            the residue type to convert
-        * @return the single letter representation of the residue
-        */
-       public static String toSingleLetter(String aaType) {
-               if (!aaNames.containsKey(aaType))
-                       return aaType;
-               String[] ids = ((String) aaNames.get(aaType)).split(" ");
-               return ids[0];
-       }
-
-       /**
-        * Convert the amino acid type to three letters
-        * 
-        * @param aaType
-        *            the residue type to convert
-        * @return the three letter representation of the residue
-        */
-       public static String toThreeLetter(String aaType) {
-               if (!aaNames.containsKey(aaType))
-                       return aaType;
-               String[] ids = ((String) aaNames.get(aaType)).split(" ");
-               return ids[1];
-       }
-
-       /**
-        * Convert the amino acid type to its SMILES string
-        * 
-        * @param aaType
-        *            the residue type to convert
-        * @return the SMILES representation of the residue
-        */
-       public static String toSMILES(String aaType) {
-               if (!aaNames.containsKey(aaType))
-                       return null;
-               String[] ids = ((String) aaNames.get(aaType)).split(" ");
-               if (ids.length < 4)
-                       return null;
-               return ids[3];
-       }
-
-       public static String getAlignName(ChimeraStructuralObject chimObj) {
-               String name = chimObj.getChimeraModel().toString();
-               if (chimObj instanceof ChimeraChain) {
-                       name = ((ChimeraChain) chimObj).toString() + " [" + name + "]";
-               }
-               return name;
-       }
+  private static Logger logger = LoggerFactory.getLogger(ChimUtils.class);
+
+  static int MAX_SUB_MODELS = 1000;
+
+  public static final HashMap<String, String> aaNames;
+
+  public static String RESIDUE_ATTR = "ChimeraResidue";
+
+  public static String RINALYZER_ATTR = "RINalyzerResidue";
+
+  public static String DEFAULT_STRUCTURE_KEY = "pdbFileName";
+
+  /**
+   * Parse the model number returned by Chimera and return the int value
+   */
+  // invoked by the ChimeraModel constructor
+  // line = model id #0 type Molecule name 1ert
+  public static int[] parseModelNumber(String inputLine)
+  {
+    int hash = inputLine.indexOf('#');
+    int space = inputLine.indexOf(' ', hash);
+    int decimal = inputLine.substring(hash + 1, space).indexOf('.');
+    // model number is between hash+1 and space
+    int modelNumber = -1;
+    int subModelNumber = 0;
+    try
+    {
+      if (decimal > 0)
+      {
+        subModelNumber = Integer
+                .parseInt(inputLine.substring(decimal + hash + 2, space));
+        space = decimal + hash + 1;
+      }
+      modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
+    } catch (Exception e)
+    {
+      logger.warn("Unexpected return from Chimera: " + inputLine, e);
+    }
+    return new int[] { modelNumber, subModelNumber };
+  }
+
+  /**
+   * Parse the model number returned by Chimera and return the int value
+   */
+  // invoked by openModel in ChimeraManager
+  // line: #1, chain A: hiv-1 protease
+  // line: Model 0 (filename)
+  public static int[] parseOpenedModelNumber(String inputLine)
+  {
+    int hash = inputLine.indexOf('#');
+    int space = -1;
+    if (hash == (-1))
+    {
+      hash = inputLine.indexOf("Model");
+      if (hash >= 0)
+      {
+        hash = hash + 5;
+      }
+      space = inputLine.indexOf(' ', hash + 1);
+    }
+    else
+    {
+      space = inputLine.indexOf(',', hash);
+    }
+
+    int decimal = inputLine.substring(hash + 1, space).indexOf('.');
+    // model number is between hash+1 and space
+    int modelNumber = -1;
+    int subModelNumber = 0;
+    try
+    {
+      if (decimal > 0)
+      {
+        subModelNumber = Integer
+                .parseInt(inputLine.substring(decimal + hash + 2, space));
+        space = decimal + hash + 1;
+      }
+      modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
+    } catch (Exception e)
+    {
+      logger.warn("Unexpected return from Chimera: " + inputLine, e);
+    }
+    return new int[] { modelNumber, subModelNumber };
+  }
+
+  /**
+   * Parse the model identifier returned by Chimera and return the String value
+   */
+  // invoked by the ChimeraModel constructor
+  // line = model id #0 type Molecule name 1ert
+  public static String parseModelName(String inputLine)
+  {
+    int start = inputLine.indexOf("name ");
+    if (start < 0)
+    {
+      return null;
+    }
+    // Might get a quoted string (don't understand why, but there you have it)
+    if (inputLine.startsWith("\"", start + 5))
+    {
+      start += 6; // Skip over the first quote
+      int end = inputLine.lastIndexOf('"');
+      if (end >= 1)
+      {
+        return inputLine.substring(start, end);
+      }
+      else
+      {
+        return inputLine.substring(start);
+      }
+    }
+    else
+    {
+      return inputLine.substring(start + 5);
+    }
+  }
+
+  public static Color parseModelColor(String inputLine)
+  {
+    try
+    {
+      int colorStart = inputLine.indexOf("color ");
+      String colorString = inputLine.substring(colorStart + 6);
+      String[] rgbStrings = colorString.split(",");
+      float[] rgbValues = new float[4];
+      for (int i = 0; i < rgbStrings.length; i++)
+      {
+        Float f = Float.valueOf(rgbStrings[i]);
+        rgbValues[i] = f.floatValue();
+      }
+      if (rgbStrings.length == 4)
+      {
+        return new Color(rgbValues[0], rgbValues[1], rgbValues[2],
+                rgbValues[3]);
+      }
+      else
+      {
+        return new Color(rgbValues[0], rgbValues[1], rgbValues[2]);
+      }
+    } catch (Exception ex)
+    {
+      logger.warn("Unexpected return from Chimera: " + inputLine, ex);
+    }
+    return Color.white;
+  }
+
+  /**
+   * Create the key to use for forming the model/submodel key into the modelHash
+   * 
+   * @param model
+   *          the model number
+   * @param subModel
+   *          the submodel number
+   * @return the model key as an Integer
+   */
+  public static Integer makeModelKey(int model, int subModel)
+  {
+    return Integer.valueOf(model * MAX_SUB_MODELS + subModel);
+  }
+
+  // invoked by the getResdiue (parseConnectivityReplies in
+  // CreateStructureNetworkTask)
+  // atomSpec = #0:1.A or #1:96.B@N
+  public static ChimeraModel getModel(String atomSpec,
+          ChimeraManager chimeraManager)
+  {
+    // System.out.println("getting model for "+atomSpec);
+    String[] split = atomSpec.split(":");
+    // No model specified....
+    if (split[0].length() == 0)
+    {
+      logger.info("Unexpected return from Chimera: " + atomSpec);
+      return null;
+    }
+    // System.out.println("model = "+split[0].substring(1));
+    int model = 0;
+    int submodel = 0;
+    try
+    {
+      String[] subSplit = split[0].substring(1).split("\\.");
+      if (subSplit.length > 0)
+      {
+        model = Integer.parseInt(subSplit[0]);
+      }
+      else
+      {
+        model = Integer.parseInt(split[0].substring(1));
+      }
+
+      if (subSplit.length > 1)
+      {
+        submodel = Integer.parseInt(subSplit[1]);
+      }
+    } catch (Exception e)
+    {
+      // ignore
+      logger.warn("Unexpected return from Chimera: " + atomSpec, e);
+    }
+    return chimeraManager.getChimeraModel(model, submodel);
+  }
+
+  // invoked by the parseConnectivityReplies in CreateStructureNetworkTask
+  // atomSpec = #0:1.A or #1:96.B@N
+  public static ChimeraResidue getResidue(String atomSpec,
+          ChimeraManager chimeraManager)
+  {
+    // System.out.println("Getting residue from: "+atomSpec);
+    ChimeraModel model = getModel(atomSpec, chimeraManager); // Get the model
+    if (model == null)
+    {
+      model = chimeraManager.getChimeraModel();
+    }
+    return getResidue(atomSpec, model);
+  }
+
+  // invoked by the getResdiue (parseConnectivityReplies in
+  // CreateStructureNetworkTask)
+  // atomSpec = #0:1.A or #1:96.B@N
+  public static ChimeraResidue getResidue(String atomSpec,
+          ChimeraModel model)
+  {
+    // System.out.println("Getting residue from: "+atomSpec);
+    String[] split = atomSpec.split(":|@");
+
+    // Split into residue and chain
+    String[] residueChain = split[1].split("\\.");
+
+    if (residueChain[0].length() == 0)
+    {
+      logger.info("Unexpected return from Chimera: " + atomSpec);
+      return null;
+    }
+
+    if (residueChain.length == 2 && residueChain[1].length() > 0)
+    {
+      ChimeraChain chain = model.getChain(residueChain[1]);
+      return chain.getResidue(residueChain[0]);
+    }
+    return model.getResidue("_", residueChain[0]);
+  }
+
+  public static ChimeraChain getChain(String atomSpec, ChimeraModel model)
+  {
+    String[] split = atomSpec.split(":|@");
+
+    // Split into residue and chain
+    String[] residueChain = split[1].split("\\.");
+    if (residueChain.length == 1)
+    {
+      logger.info("Unexpected return from Chimera: " + atomSpec);
+      return null;
+    }
+    return model.getChain(residueChain[1]);
+  }
+
+  public static String getAtomName(String atomSpec)
+  {
+    String[] split = atomSpec.split("@");
+    if (split.length > 1)
+    {
+      return split[1];
+    }
+    return atomSpec;
+  }
+
+  public static boolean isBackbone(String atom)
+  {
+    if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
+            || atom.equals("O") || atom.equals("H"))
+    {
+      return true;
+    }
+    return false;
+  }
+
+  public static String getIntSubtype(String node, String atom)
+  {
+    String[] split = node.split("#| ");
+    String resType = "";
+    if (split.length == 2)
+    {
+      resType = split[0].trim().toUpperCase(Locale.ROOT);
+    }
+    else if (split.length == 3)
+    {
+      resType = split[1].trim().toUpperCase(Locale.ROOT);
+    }
+    if (resType.equalsIgnoreCase("HOH") || resType.equalsIgnoreCase("WAT"))
+    {
+      return "water";
+    }
+    else if (aaNames.containsKey(resType))
+    {
+      if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
+              || atom.equals("O") || atom.equals("H"))
+      {
+        return "mc";
+      }
+      else
+      {
+        return "sc";
+      }
+    }
+    else
+    {
+      return "other";
+    }
+  }
+
+  public static String[] getResKeyParts(String resKey)
+  {
+    // [pdbID[.modelNo]#][residueID][.chainID]
+    // pdbID := 4-character code | "URL" | "path"
+    String[] resKeyParts = new String[4];
+    String[] split = resKey.split("#");
+    String resChain = null;
+    // if no "#" then it is either only a pdb id or a residue or a chain
+    if (split.length == 1)
+    {
+      // pdb id without model
+      if (resKey.length() == 4 && resKey.indexOf("\\.") < 0)
+      {
+        parseModelID(resKey, resKeyParts);
+      }
+      // pdb link or file
+      else if (resKey.startsWith("\""))
+      {
+        parseModelID(resKey, resKeyParts);
+      }
+      // chain and residue or model and number
+      else
+      {
+        String[] splitSplit = resKey.split("\\.");
+        if (splitSplit.length == 1)
+        {
+          // only a chain or a residue
+          resChain = resKey;
+        }
+        else
+        {
+          try
+          {
+            // pdb with a model
+            Integer.parseInt(splitSplit[1]);
+            parseModelID(resKey, resKeyParts);
+          } catch (NumberFormatException ex)
+          {
+            // residue and chain
+            resChain = resKey;
+          }
+        }
+      }
+    }
+    else if (split.length == 2)
+    {
+      // model and residue+chain
+      parseModelID(split[0], resKeyParts);
+      resChain = split[1];
+    }
+    else
+    {
+      // model string with "#"
+      // TODO: [Optional] Are there more possibilities?
+      parseModelID(resKey.substring(0, resKey.lastIndexOf("#")),
+              resKeyParts);
+      resChain = resKey.substring(resKey.lastIndexOf("#") + 1,
+              resKey.length());
+    }
+    if (resChain != null)
+    {
+      // System.out.println(resChain);
+      String[] resChainSplit = resChain.split("\\.");
+      if (resChainSplit.length == 1)
+      {
+        // TODO: [Optional] Find a better way to distinguish between chain and
+        // residue
+        // if only one character and not an int, probably a chain
+        if (resChainSplit[0].length() == 1)
+        {
+          try
+          {
+            Integer.parseInt(resChainSplit[0]);
+            resKeyParts[3] = resChainSplit[0];
+          } catch (NumberFormatException ex)
+          {
+            resKeyParts[2] = resChainSplit[0];
+          }
+        }
+        else
+        {
+          resKeyParts[3] = resChainSplit[0];
+        }
+      }
+      else if (resChainSplit.length == 2)
+      {
+        resKeyParts[2] = resChainSplit[0];
+        resKeyParts[3] = resChainSplit[1];
+      }
+      else
+      {
+        // too many dots?
+        logger.info("Could not parse residue identifier: " + resKey);
+      }
+    }
+    // String print = "";
+    // for (int i = 0; i < resKeyParts.length; i++) {
+    // if (resKeyParts[i] == null) {
+    // print += i + ": null\t";
+    // } else {
+    // print += i + ": " + resKeyParts[i] + ";";
+    // }
+    // }
+    // System.out.println(print);
+    return resKeyParts;
+  }
+
+  public static void parseModelID(String modelID, String[] resKeyParts)
+  {
+    if (modelID.startsWith("\""))
+    {
+      if (modelID.endsWith("\""))
+      {
+        resKeyParts[0] = modelID.substring(1, modelID.length() - 1);
+        return;
+      }
+      else
+      {
+        try
+        {
+          Integer.parseInt(modelID.substring(modelID.lastIndexOf("\"") + 2,
+                  modelID.length()));
+          resKeyParts[0] = modelID.substring(0,
+                  modelID.lastIndexOf("\"") - 1);
+          resKeyParts[1] = modelID.substring(modelID.lastIndexOf("\"") + 2,
+                  modelID.length());
+        } catch (NumberFormatException ex)
+        {
+          resKeyParts[0] = modelID.substring(1);
+        }
+      }
+    }
+    else
+    {
+      String[] modelIDNo = modelID.split("\\.");
+      if (modelIDNo.length == 1)
+      {
+        resKeyParts[0] = modelIDNo[0];
+      }
+      else if (modelIDNo.length == 2)
+      {
+        try
+        {
+          Integer.parseInt(modelIDNo[1]);
+          resKeyParts[0] = modelIDNo[0];
+          resKeyParts[1] = modelIDNo[1];
+        } catch (NumberFormatException ex)
+        {
+          resKeyParts[0] = modelID;
+        }
+      }
+      else
+      {
+        // length > 1, so we probably have a file name with "." in it
+        logger.info("Could not parse model identifier: " + modelID);
+        resKeyParts[0] = modelID;
+      }
+    }
+  }
+
+  /**
+   * This method takes a Cytoscape attribute specification
+   * ([structure#][residue][.chainID]) and returns the lowest-level object
+   * referenced by the spec. For example, if the spec is "1tkk", this method
+   * will return a ChimeraModel. If the spec is ".A", it will return a
+   * ChimeraChain, etc.
+   * 
+   * @param attrSpec
+   *          the specification string
+   * @param chimeraManager
+   *          the Chimera object we're currently using
+   * @return a ChimeraStructuralObject of the lowest type
+   */
+  public static ChimeraStructuralObject fromAttributeOld(String attrSpec,
+          ChimeraManager chimeraManager)
+  {
+    if (attrSpec == null || attrSpec.indexOf(',') > 0
+            || attrSpec.indexOf('-') > 0)
+    {
+      // No support for either lists or ranges
+      logger.warn("No support for identifier: " + attrSpec);
+      return null;
+    }
+
+    String residue = null;
+    String model = null;
+    String chain = null;
+
+    ChimeraModel chimeraModel = null;
+    ChimeraChain chimeraChain = null;
+    ChimeraResidue chimeraResidue = null;
+
+    // System.out.println("Getting object from attribute: "+attrSpec);
+    try
+    {
+      String[] split = attrSpec.split("#");
+      String resChain = null;
+      if (split.length == 1)
+      {
+        // no model
+        resChain = split[0];
+      }
+      else if (split.length == 2)
+      {
+        // model and rest
+        model = split[0];
+        resChain = split[1];
+      }
+      else
+      {
+        // model string with "#"
+        model = attrSpec.substring(0, attrSpec.lastIndexOf("#"));
+        resChain = attrSpec.substring(attrSpec.lastIndexOf("#") + 1,
+                attrSpec.length());
+      }
+      if (resChain != null)
+      {
+        String[] resChainSplit = resChain.split("\\.");
+        if (resChainSplit.length == 1)
+        {
+          residue = resChainSplit[0];
+        }
+        else if (resChainSplit.length == 2)
+        {
+          residue = resChainSplit[0];
+          chain = resChainSplit[1];
+        }
+        else
+        {
+          // too many dots?
+          logger.warn("No support for identifier: " + attrSpec);
+        }
+      }
+
+      // if (split.length == 1) {
+      // // No model
+      // residue = split[0];
+      // } else if (split.length == 3) {
+      // // We have all three
+      // model = split[0];
+      // residue = split[1];
+      // chain = split[2];
+      // } else if (split.length == 2 && attrSpec.indexOf('#') > 0) {
+      // // Model and Residue
+      // model = split[0];
+      // residue = split[1];
+      // } else {
+      // // Residue and Chain
+      // residue = split[0];
+      // chain = split[1];
+      // }
+
+      // System.out.println("model = " + model + " chain = " + chain +
+      // " residue = " + residue);
+      if (model != null)
+      {
+        List<ChimeraModel> models = chimeraManager.getChimeraModels(model,
+                ModelType.PDB_MODEL);
+        if (models.size() == 1)
+        {
+          chimeraModel = models.get(0);
+        }
+        else
+        {
+          try
+          {
+            chimeraModel = chimeraManager
+                    .getChimeraModel(Integer.valueOf(model), 0);
+          } catch (NumberFormatException ex)
+          {
+            // ignore
+          }
+        }
+      }
+      if (chimeraModel == null)
+      {
+        chimeraModel = chimeraManager.getChimeraModel();
+      }
+      // System.out.println("ChimeraModel = " + chimeraModel);
+
+      if (chain != null)
+      {
+        chimeraChain = chimeraModel.getChain(chain);
+        // System.out.println("ChimeraChain = " + chimeraChain);
+      }
+      if (residue != null)
+      {
+        if (chimeraChain != null)
+        {
+          chimeraResidue = chimeraChain.getResidue(residue);
+        }
+        else
+        {
+          chimeraResidue = chimeraModel.getResidue("_", residue);
+        }
+        // System.out.println("ChimeraResidue = " + chimeraResidue);
+      }
+
+      if (chimeraResidue != null)
+      {
+        return chimeraResidue;
+      }
+
+      if (chimeraChain != null)
+      {
+        return chimeraChain;
+      }
+
+      if (chimeraModel != null)
+      {
+        return chimeraModel;
+      }
+
+    } catch (Exception ex)
+    {
+      logger.warn("Could not parse residue identifier: " + attrSpec, ex);
+    }
+    return null;
+  }
+
+  public static ChimeraStructuralObject fromAttribute(String attrSpec,
+          ChimeraManager chimeraManager)
+  {
+    // TODO: Make sure it is OK to remove this: || attrSpec.indexOf('-') > 0
+    if (attrSpec == null || attrSpec.indexOf(',') > 0)
+    {
+      // No support for either lists or ranges
+      // System.out.println("No support for identifier: " + attrSpec);
+      logger.warn("No support for identifier: " + attrSpec);
+      return null;
+    }
+    String[] modelIDNoResChain = getResKeyParts(attrSpec);
+
+    ChimeraModel chimeraModel = null;
+    ChimeraChain chimeraChain = null;
+    ChimeraResidue chimeraResidue = null;
+
+    // System.out.println("Getting object from attribute: "+attrSpec);
+    try
+    {
+      if (modelIDNoResChain[0] != null)
+      {
+        String modelID = modelIDNoResChain[0];
+        List<ChimeraModel> models = chimeraManager.getChimeraModels(modelID,
+                ModelType.PDB_MODEL);
+        if (models.size() == 1)
+        { // usual case with only one model
+          chimeraModel = models.get(0);
+        }
+        else if (models.size() > 1 && modelIDNoResChain[1] != null)
+        {
+          // there are several submodels
+          try
+          {
+            int modelNo = Integer.valueOf(modelIDNoResChain[1]);
+            for (ChimeraModel model : models)
+            {
+              if (model.getSubModelNumber() == modelNo)
+              {
+                chimeraModel = model;
+                break;
+              }
+            }
+          } catch (NumberFormatException ex)
+          {
+            // ignore
+          }
+        }
+        else
+        {
+          // TODO: [Optional] What is this doing?
+          try
+          {
+            chimeraModel = chimeraManager
+                    .getChimeraModel(Integer.valueOf(modelID), 0);
+          } catch (NumberFormatException ex)
+          {
+            // ignore
+          }
+        }
+      }
+      if (chimeraModel == null)
+      {
+        // TODO: [Optional] Find a better way to handle this case
+        // If no model can be matched, continue
+        // System.out.println("No matching model could be find for " +
+        // attrSpec);
+        return null;
+        // chimeraModel = chimeraManager.getChimeraModel();
+        // logger.warn("No matching model could be find for " + attrSpec +
+        // ". Trying with "
+        // + chimeraModel.toSpec());
+      }
+      // System.out.println("ChimeraModel = " + chimeraModel);
+
+      if (modelIDNoResChain[3] != null)
+      {
+        chimeraChain = chimeraModel.getChain(modelIDNoResChain[3]);
+        // System.out.println("ChimeraChain = " + chimeraChain);
+      }
+      if (modelIDNoResChain[2] != null)
+      {
+        String residue = modelIDNoResChain[2];
+        if (chimeraChain != null)
+        {
+          chimeraResidue = chimeraChain.getResidue(residue);
+        }
+        else if (chimeraModel.getChain("_") != null)
+        {
+          chimeraResidue = chimeraModel.getResidue("_", residue);
+        }
+        else if (chimeraModel.getChainCount() == 1)
+        {
+          chimeraResidue = chimeraModel.getResidue(
+                  chimeraModel.getChainNames().iterator().next(), residue);
+        }
+        // System.out.println("ChimeraResidue = " + chimeraResidue);
+      }
+
+      if (chimeraResidue != null)
+      {
+        return chimeraResidue;
+      }
+
+      if (chimeraChain != null)
+      {
+        return chimeraChain;
+      }
+
+      if (chimeraModel != null)
+      {
+        return chimeraModel;
+      }
+
+    } catch (Exception ex)
+    {
+      // System.out.println("Could not parse chimera identifier: " +
+      // attrSpec+"("+ex.getMessage()+")");
+      logger.warn("Could not parse chimera identifier: " + attrSpec, ex);
+    }
+    return null;
+  }
+
+  /**
+   * Search for structure references in the residue list
+   * 
+   * @param residueList
+   *          the list of residues
+   * @return a concatenated list of structures encoded in the list
+   */
+  public static String findStructures(String residueList)
+  {
+    if (residueList == null)
+    {
+      return null;
+    }
+    String[] residues = residueList.split(",");
+    Map<String, String> structureNameMap = new HashMap<String, String>();
+    for (int i = 0; i < residues.length; i++)
+    {
+      String[] components = residues[i].split("#");
+      if (components.length > 1)
+      {
+        structureNameMap.put(components[0], components[1]);
+      }
+    }
+    if (structureNameMap.isEmpty())
+    {
+      return null;
+    }
+
+    String structure = null;
+    for (String struct : structureNameMap.keySet())
+    {
+      if (structure == null)
+      {
+        structure = new String();
+      }
+      else
+      {
+        structure = structure.concat(",");
+      }
+      structure = structure.concat(struct);
+    }
+    return structure;
+  }
+
+  // invoked by openStructures in StructureManager
+  public static List<String> parseFuncRes(List<String> residueNames,
+          String modelName)
+  {
+    List<String> resRanges = new ArrayList<String>();
+    for (int i = 0; i < residueNames.size(); i++)
+    {
+      String residue = residueNames.get(i);
+      // Parse out the structure, if there is one
+      String[] components = residue.split("#");
+      if (components.length > 1 && !modelName.equals(components[0]))
+      {
+        continue;
+      }
+      else if (components.length > 1)
+      {
+        residue = components[1];
+      }
+      else if (components.length == 1)
+      {
+        residue = components[0];
+      }
+      // Check to see if we have a range-spec
+      String resRange = "";
+      if (residue == null || residue.equals("") || residue.length() == 0)
+      {
+        continue;
+      }
+      String[] range = residue.split("-", 2);
+      String chain = null;
+      for (int res = 0; res < range.length; res++)
+      {
+        if (res == 1)
+        {
+          resRange = resRange.concat("-");
+          if (chain != null && range[res].indexOf('.') == -1)
+          {
+            range[res] = range[res].concat("." + chain);
+          }
+        }
+
+        if (res == 0 && range.length >= 2 && range[res].indexOf('.') > 0)
+        {
+          // This is a range spec with the leading residue containing a chain
+          // spec
+          String[] resChain = range[res].split("\\.");
+          chain = resChain[1];
+          range[res] = resChain[0];
+        }
+        // Fix weird SFLD syntax...
+        if (range[res].indexOf('|') > 0
+                && Character.isDigit(range[res].charAt(0)))
+        {
+          int offset = range[res].indexOf('|');
+          String str = range[res].substring(offset + 1)
+                  + range[res].substring(0, offset);
+          range[res] = str;
+        }
+
+        // Convert to legal atom-spec
+        if (Character.isDigit(range[res].charAt(0)))
+        {
+          resRange = resRange.concat(range[res]);
+        }
+        else if (Character.isDigit(range[res].charAt(1)))
+        {
+          resRange = resRange.concat(range[res].substring(1));
+        }
+        else if (range[res].charAt(0) == '.')
+        {
+          // Do we have a chain spec?
+          resRange = resRange.concat(range[res]);
+        }
+        else
+        {
+          resRange = resRange.concat(range[res].substring(3));
+        }
+      }
+      if (!resRanges.contains(resRange))
+      {
+        resRanges.add(resRange);
+      }
+    }
+    return resRanges;
+  }
+
+  static
+  {
+    aaNames = new HashMap<String, String>();
+    aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
+    aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
+    aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
+    aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
+    aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
+    aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
+    aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
+    aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
+    aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
+    aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
+    aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
+    aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
+    aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
+    aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
+    aaNames.put("PHE",
+            "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
+    aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
+    aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
+    aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
+    aaNames.put("TRP",
+            "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
+    aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
+    aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
+    aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
+    aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
+    aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
+    aaNames.put("HOH", "HOH HOH Water [H]O[H]");
+  }
+
+  /**
+   * Convert the amino acid type to a full name
+   * 
+   * @param aaType
+   *          the residue type to convert
+   * @return the full name of the residue
+   */
+  public static String toFullName(String aaType)
+  {
+    if (!aaNames.containsKey(aaType))
+    {
+      return aaType;
+    }
+    String[] ids = aaNames.get(aaType).split(" ");
+    return ids[2].replace('_', ' ');
+  }
+
+  /**
+   * Convert the amino acid type to a single letter
+   * 
+   * @param aaType
+   *          the residue type to convert
+   * @return the single letter representation of the residue
+   */
+  public static String toSingleLetter(String aaType)
+  {
+    if (!aaNames.containsKey(aaType))
+    {
+      return aaType;
+    }
+    String[] ids = aaNames.get(aaType).split(" ");
+    return ids[0];
+  }
+
+  /**
+   * Convert the amino acid type to three letters
+   * 
+   * @param aaType
+   *          the residue type to convert
+   * @return the three letter representation of the residue
+   */
+  public static String toThreeLetter(String aaType)
+  {
+    if (!aaNames.containsKey(aaType))
+    {
+      return aaType;
+    }
+    String[] ids = aaNames.get(aaType).split(" ");
+    return ids[1];
+  }
+
+  /**
+   * Convert the amino acid type to its SMILES string
+   * 
+   * @param aaType
+   *          the residue type to convert
+   * @return the SMILES representation of the residue
+   */
+  public static String toSMILES(String aaType)
+  {
+    if (!aaNames.containsKey(aaType))
+    {
+      return null;
+    }
+    String[] ids = aaNames.get(aaType).split(" ");
+    if (ids.length < 4)
+    {
+      return null;
+    }
+    return ids[3];
+  }
+
+  public static String getAlignName(ChimeraStructuralObject chimObj)
+  {
+    String name = chimObj.getChimeraModel().toString();
+    if (chimObj instanceof ChimeraChain)
+    {
+      name = ((ChimeraChain) chimObj).toString() + " [" + name + "]";
+    }
+    return name;
+  }
 }