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