JAL-2446 merged to spike branch
[jalview.git] / src / jalview / ext / rbvi / chimera / AtomSpecModel.java
1 package jalview.ext.rbvi.chimera;
2
3 import jalview.util.IntRangeComparator;
4
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.TreeMap;
11
12 /**
13  * A class to model a Chimera atomspec pattern, for example
14  * 
15  * <pre>
16  * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
17  * </pre>
18  * 
19  * where
20  * <ul>
21  * <li>#0 is a model number</li>
22  * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
23  * <li>.A is a chain identifier</li>
24  * <li>residue ranges are separated by comma</li>
25  * <li>atomspecs for distinct models are separated by | (or)</li>
26  * </ul>
27  * 
28  * <pre>
29  * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
30  * </pre>
31  */
32 public class AtomSpecModel
33 {
34   private Map<Integer, Map<String, List<int[]>>> atomSpec;
35
36   /**
37    * Constructor
38    */
39   public AtomSpecModel()
40   {
41     atomSpec = new TreeMap<Integer, Map<String, List<int[]>>>();
42   }
43
44   /**
45    * Adds one contiguous range to this atom spec
46    * 
47    * @param model
48    * @param startPos
49    * @param endPos
50    * @param chain
51    */
52   public void addRange(int model, int startPos, int endPos, String chain)
53   {
54     /*
55      * Get/initialize map of data for the colour and model
56      */
57     Map<String, List<int[]>> modelData = atomSpec.get(model);
58     if (modelData == null)
59     {
60       atomSpec.put(model, modelData = new TreeMap<String, List<int[]>>());
61     }
62
63     /*
64      * Get/initialize map of data for colour, model and chain
65      */
66     List<int[]> chainData = modelData.get(chain);
67     if (chainData == null)
68     {
69       chainData = new ArrayList<int[]>();
70       modelData.put(chain, chainData);
71     }
72
73     /*
74      * Add the start/end positions
75      */
76     chainData.add(new int[] { startPos, endPos });
77     // TODO add intelligently, using a RangeList class
78   }
79
80   /**
81    * Returns the range(s) formatted as a Chimera atomspec
82    * 
83    * @return
84    */
85   public String getAtomSpec()
86   {
87     StringBuilder sb = new StringBuilder(128);
88     boolean firstModel = true;
89     for (Integer model : atomSpec.keySet())
90     {
91       if (!firstModel)
92       {
93         sb.append("|");
94       }
95       firstModel = false;
96       sb.append("#").append(model).append(":");
97
98       boolean firstPositionForModel = true;
99       final Map<String, List<int[]>> modelData = atomSpec.get(model);
100
101       for (String chain : modelData.keySet())
102       {
103         chain = chain.trim();
104
105         List<int[]> rangeList = modelData.get(chain);
106
107         /*
108          * sort ranges into ascending start position order
109          */
110         Collections.sort(rangeList, IntRangeComparator.ASCENDING);
111
112         int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
113         int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
114
115         Iterator<int[]> iterator = rangeList.iterator();
116         while (iterator.hasNext())
117         {
118           int[] range = iterator.next();
119           if (range[0] <= end + 1)
120           {
121             /*
122              * range overlaps or is contiguous with the last one
123              * - so just extend the end position, and carry on
124              * (unless this is the last in the list)
125              */
126             end = Math.max(end, range[1]);
127           }
128           else
129           {
130             /*
131              * we have a break so append the last range
132              */
133             appendRange(sb, start, end, chain, firstPositionForModel);
134             firstPositionForModel = false;
135             start = range[0];
136             end = range[1];
137           }
138         }
139
140         /*
141          * and append the last range
142          */
143         if (!rangeList.isEmpty())
144         {
145           appendRange(sb, start, end, chain, firstPositionForModel);
146           firstPositionForModel = false;
147         }
148       }
149     }
150     return sb.toString();
151   }
152
153   /**
154    * @param sb
155    * @param start
156    * @param end
157    * @param chain
158    * @param firstPositionForModel
159    */
160   protected void appendRange(StringBuilder sb, int start, int end,
161           String chain, boolean firstPositionForModel)
162   {
163     if (!firstPositionForModel)
164     {
165       sb.append(",");
166     }
167     if (end == start)
168     {
169       sb.append(start);
170     }
171     else
172     {
173       sb.append(start).append("-").append(end);
174     }
175     if (chain.length() > 0)
176     {
177       sb.append(".").append(chain);
178     }
179   }
180 }