JAL-2325 applied BSD license for chimera/StrucViz2 code
[jalview.git] / src / ext / edu / ucsf / rbvi / strucviz2 / ChimUtils.java
1 /* vim: set ts=2: */
2 /**
3  * Copyright (c) 2006 The Regents of the University of California.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *   1. Redistributions of source code must retain the above copyright
10  *      notice, this list of conditions, and the following disclaimer.
11  *   2. Redistributions in binary form must reproduce the above
12  *      copyright notice, this list of conditions, and the following
13  *      disclaimer in the documentation and/or other materials provided
14  *      with the distribution.
15  *   3. Redistributions must acknowledge that this software was
16  *      originally developed by the UCSF Computer Graphics Laboratory
17  *      under support by the NIH National Center for Research Resources,
18  *      grant P41-RR01081.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  */
33 package ext.edu.ucsf.rbvi.strucviz2;
34
35 import java.awt.Color;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
45
46 public abstract class ChimUtils
47 {
48
49   private static Logger logger = LoggerFactory.getLogger(ChimUtils.class);
50
51   static int MAX_SUB_MODELS = 1000;
52
53   public static final HashMap<String, String> aaNames;
54
55   public static String RESIDUE_ATTR = "ChimeraResidue";
56
57   public static String RINALYZER_ATTR = "RINalyzerResidue";
58
59   public static String DEFAULT_STRUCTURE_KEY = "pdbFileName";
60
61   /**
62    * Parse the model number returned by Chimera and return the int value
63    */
64   // invoked by the ChimeraModel constructor
65   // line = model id #0 type Molecule name 1ert
66   public static int[] parseModelNumber(String inputLine)
67   {
68     int hash = inputLine.indexOf('#');
69     int space = inputLine.indexOf(' ', hash);
70     int decimal = inputLine.substring(hash + 1, space).indexOf('.');
71     // model number is between hash+1 and space
72     int modelNumber = -1;
73     int subModelNumber = 0;
74     try
75     {
76       if (decimal > 0)
77       {
78         subModelNumber = Integer.parseInt(inputLine.substring(decimal
79                 + hash + 2, space));
80         space = decimal + hash + 1;
81       }
82       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
83     } catch (Exception e)
84     {
85       logger.warn("Unexpected return from Chimera: " + inputLine, e);
86     }
87     return new int[] { modelNumber, subModelNumber };
88   }
89
90   /**
91    * Parse the model number returned by Chimera and return the int value
92    */
93   // invoked by openModel in ChimeraManager
94   // line: #1, chain A: hiv-1 protease
95   // line: Model 0 (filename)
96   public static int[] parseOpenedModelNumber(String inputLine)
97   {
98     int hash = inputLine.indexOf('#');
99     int space = -1;
100     if (hash == (-1))
101     {
102       hash = inputLine.indexOf("Model");
103       if (hash >= 0)
104       {
105         hash = hash + 5;
106       }
107       space = inputLine.indexOf(' ', hash + 1);
108     }
109     else
110     {
111       space = inputLine.indexOf(',', hash);
112     }
113
114     int decimal = inputLine.substring(hash + 1, space).indexOf('.');
115     // model number is between hash+1 and space
116     int modelNumber = -1;
117     int subModelNumber = 0;
118     try
119     {
120       if (decimal > 0)
121       {
122         subModelNumber = Integer.parseInt(inputLine.substring(decimal
123                 + hash + 2, space));
124         space = decimal + hash + 1;
125       }
126       modelNumber = Integer.parseInt(inputLine.substring(hash + 1, space));
127     } catch (Exception e)
128     {
129       logger.warn("Unexpected return from Chimera: " + inputLine, e);
130     }
131     return new int[] { modelNumber, subModelNumber };
132   }
133
134   /**
135    * Parse the model identifier returned by Chimera and return the String value
136    */
137   // invoked by the ChimeraModel constructor
138   // line = model id #0 type Molecule name 1ert
139   public static String parseModelName(String inputLine)
140   {
141     int start = inputLine.indexOf("name ");
142     if (start < 0)
143     {
144       return null;
145     }
146     // Might get a quoted string (don't understand why, but there you have it)
147     if (inputLine.startsWith("\"", start + 5))
148     {
149       start += 6; // Skip over the first quote
150       int end = inputLine.lastIndexOf('"');
151       if (end >= 1)
152       {
153         return inputLine.substring(start, end);
154       }
155       else
156       {
157         return inputLine.substring(start);
158       }
159     }
160     else
161     {
162       return inputLine.substring(start + 5);
163     }
164   }
165
166   public static Color parseModelColor(String inputLine)
167   {
168     try
169     {
170       int colorStart = inputLine.indexOf("color ");
171       String colorString = inputLine.substring(colorStart + 6);
172       String[] rgbStrings = colorString.split(",");
173       float[] rgbValues = new float[4];
174       for (int i = 0; i < rgbStrings.length; i++)
175       {
176         Float f = new Float(rgbStrings[i]);
177         rgbValues[i] = f.floatValue();
178       }
179       if (rgbStrings.length == 4)
180       {
181         return new Color(rgbValues[0], rgbValues[1], rgbValues[2],
182                 rgbValues[3]);
183       }
184       else
185       {
186         return new Color(rgbValues[0], rgbValues[1], rgbValues[2]);
187       }
188     } catch (Exception ex)
189     {
190       logger.warn("Unexpected return from Chimera: " + inputLine, ex);
191     }
192     return Color.white;
193   }
194
195   /**
196    * Create the key to use for forming the model/submodel key into the modelHash
197    * 
198    * @param model
199    *          the model number
200    * @param subModel
201    *          the submodel number
202    * @return the model key as an Integer
203    */
204   public static Integer makeModelKey(int model, int subModel)
205   {
206     return new Integer(model * MAX_SUB_MODELS + subModel);
207   }
208
209   // invoked by the getResdiue (parseConnectivityReplies in
210   // CreateStructureNetworkTask)
211   // atomSpec = #0:1.A or #1:96.B@N
212   public static ChimeraModel getModel(String atomSpec,
213           ChimeraManager chimeraManager)
214   {
215     // System.out.println("getting model for "+atomSpec);
216     String[] split = atomSpec.split(":");
217     // No model specified....
218     if (split[0].length() == 0)
219     {
220       logger.info("Unexpected return from Chimera: " + atomSpec);
221       return null;
222     }
223     // System.out.println("model = "+split[0].substring(1));
224     int model = 0;
225     int submodel = 0;
226     try
227     {
228       String[] subSplit = split[0].substring(1).split("\\.");
229       if (subSplit.length > 0)
230       {
231         model = Integer.parseInt(subSplit[0]);
232       }
233       else
234       {
235         model = Integer.parseInt(split[0].substring(1));
236       }
237
238       if (subSplit.length > 1)
239       {
240         submodel = Integer.parseInt(subSplit[1]);
241       }
242     } catch (Exception e)
243     {
244       // ignore
245       logger.warn("Unexpected return from Chimera: " + atomSpec, e);
246     }
247     return chimeraManager.getChimeraModel(model, submodel);
248   }
249
250   // invoked by the parseConnectivityReplies in CreateStructureNetworkTask
251   // atomSpec = #0:1.A or #1:96.B@N
252   public static ChimeraResidue getResidue(String atomSpec,
253           ChimeraManager chimeraManager)
254   {
255     // System.out.println("Getting residue from: "+atomSpec);
256     ChimeraModel model = getModel(atomSpec, chimeraManager); // Get the model
257     if (model == null)
258     {
259       model = chimeraManager.getChimeraModel();
260     }
261     return getResidue(atomSpec, model);
262   }
263
264   // invoked by the getResdiue (parseConnectivityReplies in
265   // CreateStructureNetworkTask)
266   // atomSpec = #0:1.A or #1:96.B@N
267   public static ChimeraResidue getResidue(String atomSpec,
268           ChimeraModel model)
269   {
270     // System.out.println("Getting residue from: "+atomSpec);
271     String[] split = atomSpec.split(":|@");
272
273     // Split into residue and chain
274     String[] residueChain = split[1].split("\\.");
275
276     if (residueChain[0].length() == 0)
277     {
278       logger.info("Unexpected return from Chimera: " + atomSpec);
279       return null;
280     }
281
282     if (residueChain.length == 2 && residueChain[1].length() > 0)
283     {
284       ChimeraChain chain = model.getChain(residueChain[1]);
285       return chain.getResidue(residueChain[0]);
286     }
287     return model.getResidue("_", residueChain[0]);
288   }
289
290   public static ChimeraChain getChain(String atomSpec, ChimeraModel model)
291   {
292     String[] split = atomSpec.split(":|@");
293
294     // Split into residue and chain
295     String[] residueChain = split[1].split("\\.");
296     if (residueChain.length == 1)
297     {
298       logger.info("Unexpected return from Chimera: " + atomSpec);
299       return null;
300     }
301     return model.getChain(residueChain[1]);
302   }
303
304   public static String getAtomName(String atomSpec)
305   {
306     String[] split = atomSpec.split("@");
307     if (split.length > 1)
308     {
309       return split[1];
310     }
311     return atomSpec;
312   }
313
314   public static boolean isBackbone(String atom)
315   {
316     if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
317             || atom.equals("O") || atom.equals("H"))
318     {
319       return true;
320     }
321     return false;
322   }
323
324   public static String getIntSubtype(String node, String atom)
325   {
326     String[] split = node.split("#| ");
327     String resType = "";
328     if (split.length == 2)
329     {
330       resType = split[0].trim().toUpperCase();
331     }
332     else if (split.length == 3)
333     {
334       resType = split[1].trim().toUpperCase();
335     }
336     if (resType.equalsIgnoreCase("HOH") || resType.equalsIgnoreCase("WAT"))
337     {
338       return "water";
339     }
340     else if (aaNames.containsKey(resType))
341     {
342       if (atom.equals("C") || atom.equals("CA") || atom.equals("N")
343               || atom.equals("O") || atom.equals("H"))
344       {
345         return "mc";
346       }
347       else
348       {
349         return "sc";
350       }
351     }
352     else
353     {
354       return "other";
355     }
356   }
357
358   public static String[] getResKeyParts(String resKey)
359   {
360     // [pdbID[.modelNo]#][residueID][.chainID]
361     // pdbID := 4-character code | "URL" | "path"
362     String[] resKeyParts = new String[4];
363     String[] split = resKey.split("#");
364     String resChain = null;
365     // if no "#" then it is either only a pdb id or a residue or a chain
366     if (split.length == 1)
367     {
368       // pdb id without model
369       if (resKey.length() == 4 && resKey.indexOf("\\.") < 0)
370       {
371         parseModelID(resKey, resKeyParts);
372       }
373       // pdb link or file
374       else if (resKey.startsWith("\""))
375       {
376         parseModelID(resKey, resKeyParts);
377       }
378       // chain and residue or model and number
379       else
380       {
381         String[] splitSplit = resKey.split("\\.");
382         if (splitSplit.length == 1)
383         {
384           // only a chain or a residue
385           resChain = resKey;
386         }
387         else
388         {
389           try
390           {
391             // pdb with a model
392             Integer.parseInt(splitSplit[1]);
393             parseModelID(resKey, resKeyParts);
394           } catch (NumberFormatException ex)
395           {
396             // residue and chain
397             resChain = resKey;
398           }
399         }
400       }
401     }
402     else if (split.length == 2)
403     {
404       // model and residue+chain
405       parseModelID(split[0], resKeyParts);
406       resChain = split[1];
407     }
408     else
409     {
410       // model string with "#"
411       // TODO: [Optional] Are there more possibilities?
412       parseModelID(resKey.substring(0, resKey.lastIndexOf("#")),
413               resKeyParts);
414       resChain = resKey.substring(resKey.lastIndexOf("#") + 1,
415               resKey.length());
416     }
417     if (resChain != null)
418     {
419       // System.out.println(resChain);
420       String[] resChainSplit = resChain.split("\\.");
421       if (resChainSplit.length == 1)
422       {
423         // TODO: [Optional] Find a better way to distinguish between chain and
424         // residue
425         // if only one character and not an int, probably a chain
426         if (resChainSplit[0].length() == 1)
427         {
428           try
429           {
430             Integer.parseInt(resChainSplit[0]);
431             resKeyParts[3] = resChainSplit[0];
432           } catch (NumberFormatException ex)
433           {
434             resKeyParts[2] = resChainSplit[0];
435           }
436         }
437         else
438         {
439           resKeyParts[3] = resChainSplit[0];
440         }
441       }
442       else if (resChainSplit.length == 2)
443       {
444         resKeyParts[2] = resChainSplit[0];
445         resKeyParts[3] = resChainSplit[1];
446       }
447       else
448       {
449         // too many dots?
450         logger.info("Could not parse residue identifier: " + resKey);
451       }
452     }
453     // String print = "";
454     // for (int i = 0; i < resKeyParts.length; i++) {
455     // if (resKeyParts[i] == null) {
456     // print += i + ": null\t";
457     // } else {
458     // print += i + ": " + resKeyParts[i] + ";";
459     // }
460     // }
461     // System.out.println(print);
462     return resKeyParts;
463   }
464
465   public static void parseModelID(String modelID, String[] resKeyParts)
466   {
467     if (modelID.startsWith("\""))
468     {
469       if (modelID.endsWith("\""))
470       {
471         resKeyParts[0] = modelID.substring(1, modelID.length() - 1);
472         return;
473       }
474       else
475       {
476         try
477         {
478           Integer.parseInt(modelID.substring(modelID.lastIndexOf("\"") + 2,
479                   modelID.length()));
480           resKeyParts[0] = modelID.substring(0,
481                   modelID.lastIndexOf("\"") - 1);
482           resKeyParts[1] = modelID.substring(modelID.lastIndexOf("\"") + 2,
483                   modelID.length());
484         } catch (NumberFormatException ex)
485         {
486           resKeyParts[0] = modelID.substring(1);
487         }
488       }
489     }
490     else
491     {
492       String[] modelIDNo = modelID.split("\\.");
493       if (modelIDNo.length == 1)
494       {
495         resKeyParts[0] = modelIDNo[0];
496       }
497       else if (modelIDNo.length == 2)
498       {
499         try
500         {
501           Integer.parseInt(modelIDNo[1]);
502           resKeyParts[0] = modelIDNo[0];
503           resKeyParts[1] = modelIDNo[1];
504         } catch (NumberFormatException ex)
505         {
506           resKeyParts[0] = modelID;
507         }
508       }
509       else
510       {
511         // length > 1, so we probably have a file name with "." in it
512         logger.info("Could not parse model identifier: " + modelID);
513         resKeyParts[0] = modelID;
514       }
515     }
516   }
517
518   /**
519    * This method takes a Cytoscape attribute specification
520    * ([structure#][residue][.chainID]) and returns the lowest-level object
521    * referenced by the spec. For example, if the spec is "1tkk", this method
522    * will return a ChimeraModel. If the spec is ".A", it will return a
523    * ChimeraChain, etc.
524    * 
525    * @param attrSpec
526    *          the specification string
527    * @param chimeraManager
528    *          the Chimera object we're currently using
529    * @return a ChimeraStructuralObject of the lowest type
530    */
531   public static ChimeraStructuralObject fromAttributeOld(String attrSpec,
532           ChimeraManager chimeraManager)
533   {
534     if (attrSpec == null || attrSpec.indexOf(',') > 0
535             || attrSpec.indexOf('-') > 0)
536     {
537       // No support for either lists or ranges
538       logger.warn("No support for identifier: " + attrSpec);
539       return null;
540     }
541
542     String residue = null;
543     String model = null;
544     String chain = null;
545
546     ChimeraModel chimeraModel = null;
547     ChimeraChain chimeraChain = null;
548     ChimeraResidue chimeraResidue = null;
549
550     // System.out.println("Getting object from attribute: "+attrSpec);
551     try
552     {
553       String[] split = attrSpec.split("#");
554       String resChain = null;
555       if (split.length == 1)
556       {
557         // no model
558         resChain = split[0];
559       }
560       else if (split.length == 2)
561       {
562         // model and rest
563         model = split[0];
564         resChain = split[1];
565       }
566       else
567       {
568         // model string with "#"
569         model = attrSpec.substring(0, attrSpec.lastIndexOf("#"));
570         resChain = attrSpec.substring(attrSpec.lastIndexOf("#") + 1,
571                 attrSpec.length());
572       }
573       if (resChain != null)
574       {
575         String[] resChainSplit = resChain.split("\\.");
576         if (resChainSplit.length == 1)
577         {
578           residue = resChainSplit[0];
579         }
580         else if (resChainSplit.length == 2)
581         {
582           residue = resChainSplit[0];
583           chain = resChainSplit[1];
584         }
585         else
586         {
587           // too many dots?
588           logger.warn("No support for identifier: " + attrSpec);
589         }
590       }
591
592       // if (split.length == 1) {
593       // // No model
594       // residue = split[0];
595       // } else if (split.length == 3) {
596       // // We have all three
597       // model = split[0];
598       // residue = split[1];
599       // chain = split[2];
600       // } else if (split.length == 2 && attrSpec.indexOf('#') > 0) {
601       // // Model and Residue
602       // model = split[0];
603       // residue = split[1];
604       // } else {
605       // // Residue and Chain
606       // residue = split[0];
607       // chain = split[1];
608       // }
609
610       // System.out.println("model = " + model + " chain = " + chain +
611       // " residue = " + residue);
612       if (model != null)
613       {
614         List<ChimeraModel> models = chimeraManager.getChimeraModels(model,
615                 ModelType.PDB_MODEL);
616         if (models.size() == 1)
617         {
618           chimeraModel = models.get(0);
619         }
620         else
621         {
622           try
623           {
624             chimeraModel = chimeraManager.getChimeraModel(
625                     Integer.valueOf(model), 0);
626           } catch (NumberFormatException ex)
627           {
628             // ignore
629           }
630         }
631       }
632       if (chimeraModel == null)
633       {
634         chimeraModel = chimeraManager.getChimeraModel();
635       }
636       // System.out.println("ChimeraModel = " + chimeraModel);
637
638       if (chain != null)
639       {
640         chimeraChain = chimeraModel.getChain(chain);
641         // System.out.println("ChimeraChain = " + chimeraChain);
642       }
643       if (residue != null)
644       {
645         if (chimeraChain != null)
646         {
647           chimeraResidue = chimeraChain.getResidue(residue);
648         }
649         else
650         {
651           chimeraResidue = chimeraModel.getResidue("_", residue);
652         }
653         // System.out.println("ChimeraResidue = " + chimeraResidue);
654       }
655
656       if (chimeraResidue != null)
657       {
658         return chimeraResidue;
659       }
660
661       if (chimeraChain != null)
662       {
663         return chimeraChain;
664       }
665
666       if (chimeraModel != null)
667       {
668         return chimeraModel;
669       }
670
671     } catch (Exception ex)
672     {
673       logger.warn("Could not parse residue identifier: " + attrSpec, ex);
674     }
675     return null;
676   }
677
678   public static ChimeraStructuralObject fromAttribute(String attrSpec,
679           ChimeraManager chimeraManager)
680   {
681     // TODO: Make sure it is OK to remove this: || attrSpec.indexOf('-') > 0
682     if (attrSpec == null || attrSpec.indexOf(',') > 0)
683     {
684       // No support for either lists or ranges
685       // System.out.println("No support for identifier: " + attrSpec);
686       logger.warn("No support for identifier: " + attrSpec);
687       return null;
688     }
689     String[] modelIDNoResChain = getResKeyParts(attrSpec);
690
691     ChimeraModel chimeraModel = null;
692     ChimeraChain chimeraChain = null;
693     ChimeraResidue chimeraResidue = null;
694
695     // System.out.println("Getting object from attribute: "+attrSpec);
696     try
697     {
698       if (modelIDNoResChain[0] != null)
699       {
700         String modelID = modelIDNoResChain[0];
701         List<ChimeraModel> models = chimeraManager.getChimeraModels(
702                 modelID, ModelType.PDB_MODEL);
703         if (models.size() == 1)
704         { // usual case with only one model
705           chimeraModel = models.get(0);
706         }
707         else if (models.size() > 1 && modelIDNoResChain[1] != null)
708         {
709           // there are several submodels
710           try
711           {
712             int modelNo = Integer.valueOf(modelIDNoResChain[1]);
713             for (ChimeraModel model : models)
714             {
715               if (model.getSubModelNumber() == modelNo)
716               {
717                 chimeraModel = model;
718                 break;
719               }
720             }
721           } catch (NumberFormatException ex)
722           {
723             // ignore
724           }
725         }
726         else
727         {
728           // TODO: [Optional] What is this doing?
729           try
730           {
731             chimeraModel = chimeraManager.getChimeraModel(
732                     Integer.valueOf(modelID), 0);
733           } catch (NumberFormatException ex)
734           {
735             // ignore
736           }
737         }
738       }
739       if (chimeraModel == null)
740       {
741         // TODO: [Optional] Find a better way to handle this case
742         // If no model can be matched, continue
743         // System.out.println("No matching model could be find for " +
744         // attrSpec);
745         return null;
746         // chimeraModel = chimeraManager.getChimeraModel();
747         // logger.warn("No matching model could be find for " + attrSpec +
748         // ". Trying with "
749         // + chimeraModel.toSpec());
750       }
751       // System.out.println("ChimeraModel = " + chimeraModel);
752
753       if (modelIDNoResChain[3] != null)
754       {
755         chimeraChain = chimeraModel.getChain(modelIDNoResChain[3]);
756         // System.out.println("ChimeraChain = " + chimeraChain);
757       }
758       if (modelIDNoResChain[2] != null)
759       {
760         String residue = modelIDNoResChain[2];
761         if (chimeraChain != null)
762         {
763           chimeraResidue = chimeraChain.getResidue(residue);
764         }
765         else if (chimeraModel.getChain("_") != null)
766         {
767           chimeraResidue = chimeraModel.getResidue("_", residue);
768         }
769         else if (chimeraModel.getChainCount() == 1)
770         {
771           chimeraResidue = chimeraModel.getResidue(chimeraModel
772                   .getChainNames().iterator().next(), residue);
773         }
774         // System.out.println("ChimeraResidue = " + chimeraResidue);
775       }
776
777       if (chimeraResidue != null)
778       {
779         return chimeraResidue;
780       }
781
782       if (chimeraChain != null)
783       {
784         return chimeraChain;
785       }
786
787       if (chimeraModel != null)
788       {
789         return chimeraModel;
790       }
791
792     } catch (Exception ex)
793     {
794       // System.out.println("Could not parse chimera identifier: " +
795       // attrSpec+"("+ex.getMessage()+")");
796       logger.warn("Could not parse chimera identifier: " + attrSpec, ex);
797     }
798     return null;
799   }
800
801   /**
802    * Search for structure references in the residue list
803    * 
804    * @param residueList
805    *          the list of residues
806    * @return a concatenated list of structures encoded in the list
807    */
808   public static String findStructures(String residueList)
809   {
810     if (residueList == null)
811     {
812       return null;
813     }
814     String[] residues = residueList.split(",");
815     Map<String, String> structureNameMap = new HashMap<String, String>();
816     for (int i = 0; i < residues.length; i++)
817     {
818       String[] components = residues[i].split("#");
819       if (components.length > 1)
820       {
821         structureNameMap.put(components[0], components[1]);
822       }
823     }
824     if (structureNameMap.isEmpty())
825     {
826       return null;
827     }
828
829     String structure = null;
830     for (String struct : structureNameMap.keySet())
831     {
832       if (structure == null)
833       {
834         structure = new String();
835       }
836       else
837       {
838         structure = structure.concat(",");
839       }
840       structure = structure.concat(struct);
841     }
842     return structure;
843   }
844
845   // invoked by openStructures in StructureManager
846   public static List<String> parseFuncRes(List<String> residueNames,
847           String modelName)
848   {
849     List<String> resRanges = new ArrayList<String>();
850     for (int i = 0; i < residueNames.size(); i++)
851     {
852       String residue = residueNames.get(i);
853       // Parse out the structure, if there is one
854       String[] components = residue.split("#");
855       if (components.length > 1 && !modelName.equals(components[0]))
856       {
857         continue;
858       }
859       else if (components.length > 1)
860       {
861         residue = components[1];
862       }
863       else if (components.length == 1)
864       {
865         residue = components[0];
866       }
867       // Check to see if we have a range-spec
868       String resRange = "";
869       if (residue == null || residue.equals("") || residue.length() == 0)
870       {
871         continue;
872       }
873       String[] range = residue.split("-", 2);
874       String chain = null;
875       for (int res = 0; res < range.length; res++)
876       {
877         if (res == 1)
878         {
879           resRange = resRange.concat("-");
880           if (chain != null && range[res].indexOf('.') == -1)
881           {
882             range[res] = range[res].concat("." + chain);
883           }
884         }
885
886         if (res == 0 && range.length >= 2 && range[res].indexOf('.') > 0)
887         {
888           // This is a range spec with the leading residue containing a chain
889           // spec
890           String[] resChain = range[res].split("\\.");
891           chain = resChain[1];
892           range[res] = resChain[0];
893         }
894         // Fix weird SFLD syntax...
895         if (range[res].indexOf('|') > 0
896                 && Character.isDigit(range[res].charAt(0)))
897         {
898           int offset = range[res].indexOf('|');
899           String str = range[res].substring(offset + 1)
900                   + range[res].substring(0, offset);
901           range[res] = str;
902         }
903
904         // Convert to legal atom-spec
905         if (Character.isDigit(range[res].charAt(0)))
906         {
907           resRange = resRange.concat(range[res]);
908         }
909         else if (Character.isDigit(range[res].charAt(1)))
910         {
911           resRange = resRange.concat(range[res].substring(1));
912         }
913         else if (range[res].charAt(0) == '.')
914         {
915           // Do we have a chain spec?
916           resRange = resRange.concat(range[res]);
917         }
918         else
919         {
920           resRange = resRange.concat(range[res].substring(3));
921         }
922       }
923       if (!resRanges.contains(resRange))
924       {
925         resRanges.add(resRange);
926       }
927     }
928     return resRanges;
929   }
930
931   static
932   {
933     aaNames = new HashMap<String, String>();
934     aaNames.put("ALA", "A Ala Alanine N[C@@H](C)C(O)=O");
935     aaNames.put("ARG", "R Arg Arginine N[C@@H](CCCNC(N)=N)C(O)=O");
936     aaNames.put("ASN", "N Asn Asparagine N[C@@H](CC(N)=O)C(O)=O");
937     aaNames.put("ASP", "D Asp Aspartic_acid N[C@@H](CC(O)=O)C(O)=O");
938     aaNames.put("CYS", "C Cys Cysteine N[C@@H](CS)C(O)=O");
939     aaNames.put("GLN", "Q Gln Glutamine N[C@H](C(O)=O)CCC(N)=O");
940     aaNames.put("GLU", "E Glu Glumatic_acid N[C@H](C(O)=O)CCC(O)=O");
941     aaNames.put("GLY", "G Gly Glycine NCC(O)=O");
942     aaNames.put("HIS", "H His Histidine N[C@@H](CC1=CN=CN1)C(O)=O");
943     aaNames.put("ILE", "I Ile Isoleucine N[C@]([C@H](C)CC)([H])C(O)=O");
944     aaNames.put("LEU", "L Leu Leucine N[C@](CC(C)C)([H])C(O)=O");
945     aaNames.put("LYS", "K Lys Lysine N[C@](CCCCN)([H])C(O)=O");
946     aaNames.put("DLY", "K Dly D-Lysine NCCCC[C@@H](N)C(O)=O");
947     aaNames.put("MET", "M Met Methionine N[C@](CCSC)([H])C(O)=O");
948     aaNames.put("PHE", "F Phe Phenylalanine N[C@](CC1=CC=CC=C1)([H])C(O)=O");
949     aaNames.put("PRO", "P Pro Proline OC([C@@]1([H])NCCC1)=O");
950     aaNames.put("SER", "S Ser Serine OC[C@](C(O)=O)([H])N");
951     aaNames.put("THR", "T Thr Threonine O[C@H](C)[C@](C(O)=O)([H])N");
952     aaNames.put("TRP",
953             "W Trp Tryptophan N[C@@]([H])(CC1=CN([H])C2=C1C=CC=C2)C(O)=O");
954     aaNames.put("TYR", "Y Tyr Tyrosine N[C@@](C(O)=O)([H])CC1=CC=C(O)C=C1");
955     aaNames.put("VAL", "V Val Valine N[C@@](C(O)=O)([H])C(C)C");
956     aaNames.put("ASX", "B Asx Aspartic_acid_or_Asparagine");
957     aaNames.put("GLX", "Z Glx Glutamine_or_Glutamic_acid");
958     aaNames.put("XAA", "X Xaa Any_or_unknown_amino_acid");
959     aaNames.put("HOH", "HOH HOH Water [H]O[H]");
960   }
961
962   /**
963    * Convert the amino acid type to a full name
964    * 
965    * @param aaType
966    *          the residue type to convert
967    * @return the full name of the residue
968    */
969   public static String toFullName(String aaType)
970   {
971     if (!aaNames.containsKey(aaType))
972     {
973       return aaType;
974     }
975     String[] ids = aaNames.get(aaType).split(" ");
976     return ids[2].replace('_', ' ');
977   }
978
979   /**
980    * Convert the amino acid type to a single letter
981    * 
982    * @param aaType
983    *          the residue type to convert
984    * @return the single letter representation of the residue
985    */
986   public static String toSingleLetter(String aaType)
987   {
988     if (!aaNames.containsKey(aaType))
989     {
990       return aaType;
991     }
992     String[] ids = aaNames.get(aaType).split(" ");
993     return ids[0];
994   }
995
996   /**
997    * Convert the amino acid type to three letters
998    * 
999    * @param aaType
1000    *          the residue type to convert
1001    * @return the three letter representation of the residue
1002    */
1003   public static String toThreeLetter(String aaType)
1004   {
1005     if (!aaNames.containsKey(aaType))
1006     {
1007       return aaType;
1008     }
1009     String[] ids = aaNames.get(aaType).split(" ");
1010     return ids[1];
1011   }
1012
1013   /**
1014    * Convert the amino acid type to its SMILES string
1015    * 
1016    * @param aaType
1017    *          the residue type to convert
1018    * @return the SMILES representation of the residue
1019    */
1020   public static String toSMILES(String aaType)
1021   {
1022     if (!aaNames.containsKey(aaType))
1023     {
1024       return null;
1025     }
1026     String[] ids = aaNames.get(aaType).split(" ");
1027     if (ids.length < 4)
1028     {
1029       return null;
1030     }
1031     return ids[3];
1032   }
1033
1034   public static String getAlignName(ChimeraStructuralObject chimObj)
1035   {
1036     String name = chimObj.getChimeraModel().toString();
1037     if (chimObj instanceof ChimeraChain)
1038     {
1039       name = ((ChimeraChain) chimObj).toString() + " [" + name + "]";
1040     }
1041     return name;
1042   }
1043 }