JAL-2777 patch for whitespace chain
[jalview.git] / src / jalview / ext / rbvi / chimera / AtomSpecModel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ext.rbvi.chimera;
22
23 import jalview.util.IntRangeComparator;
24
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.TreeMap;
31
32 /**
33  * A class to model a Chimera atomspec pattern, for example
34  * 
35  * <pre>
36  * #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
37  * </pre>
38  * 
39  * where
40  * <ul>
41  * <li>#0 is a model number</li>
42  * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
43  * <li>.A is a chain identifier</li>
44  * <li>residue ranges are separated by comma</li>
45  * <li>atomspecs for distinct models are separated by | (or)</li>
46  * </ul>
47  * 
48  * <pre>
49  * &#64;see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
50  * </pre>
51  */
52 public class AtomSpecModel
53 {
54   private Map<Integer, Map<String, List<int[]>>> atomSpec;
55
56   /**
57    * Constructor
58    */
59   public AtomSpecModel()
60   {
61     atomSpec = new TreeMap<Integer, Map<String, List<int[]>>>();
62   }
63
64   /**
65    * Adds one contiguous range to this atom spec
66    * 
67    * @param model
68    * @param startPos
69    * @param endPos
70    * @param chain
71    */
72   public void addRange(int model, int startPos, int endPos, String chain)
73   {
74     /*
75      * Get/initialize map of data for the colour and model
76      */
77     Map<String, List<int[]>> modelData = atomSpec.get(model);
78     if (modelData == null)
79     {
80       atomSpec.put(model, modelData = new TreeMap<String, List<int[]>>());
81     }
82
83     /*
84      * Get/initialize map of data for colour, model and chain
85      */
86     List<int[]> chainData = modelData.get(chain);
87     if (chainData == null)
88     {
89       chainData = new ArrayList<int[]>();
90       modelData.put(chain, chainData);
91     }
92
93     /*
94      * Add the start/end positions
95      */
96     chainData.add(new int[] { startPos, endPos });
97     // TODO add intelligently, using a RangeList class
98   }
99
100   /**
101    * Returns the range(s) formatted as a Chimera atomspec
102    * 
103    * @return
104    */
105   public String getAtomSpec()
106   {
107     StringBuilder sb = new StringBuilder(128);
108     boolean firstModel = true;
109     for (Integer model : atomSpec.keySet())
110     {
111       if (!firstModel)
112       {
113         sb.append("|");
114       }
115       firstModel = false;
116       sb.append("#").append(model).append(":");
117
118       boolean firstPositionForModel = true;
119       final Map<String, List<int[]>> modelData = atomSpec.get(model);
120
121       for (String chain : modelData.keySet())
122       {
123         chain = " ".equals(chain) ? chain : chain.trim();
124
125         List<int[]> rangeList = modelData.get(chain);
126
127         /*
128          * sort ranges into ascending start position order
129          */
130         Collections.sort(rangeList, IntRangeComparator.ASCENDING);
131
132         int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
133         int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
134
135         Iterator<int[]> iterator = rangeList.iterator();
136         while (iterator.hasNext())
137         {
138           int[] range = iterator.next();
139           if (range[0] <= end + 1)
140           {
141             /*
142              * range overlaps or is contiguous with the last one
143              * - so just extend the end position, and carry on
144              * (unless this is the last in the list)
145              */
146             end = Math.max(end, range[1]);
147           }
148           else
149           {
150             /*
151              * we have a break so append the last range
152              */
153             appendRange(sb, start, end, chain, firstPositionForModel);
154             firstPositionForModel = false;
155             start = range[0];
156             end = range[1];
157           }
158         }
159
160         /*
161          * and append the last range
162          */
163         if (!rangeList.isEmpty())
164         {
165           appendRange(sb, start, end, chain, firstPositionForModel);
166           firstPositionForModel = false;
167         }
168       }
169     }
170     return sb.toString();
171   }
172
173   /**
174    * @param sb
175    * @param start
176    * @param end
177    * @param chain
178    * @param firstPositionForModel
179    */
180   protected void appendRange(StringBuilder sb, int start, int end,
181           String chain, boolean firstPositionForModel)
182   {
183     if (!firstPositionForModel)
184     {
185       sb.append(",");
186     }
187     if (end == start)
188     {
189       sb.append(start);
190     }
191     else
192     {
193       sb.append(start).append("-").append(end);
194     }
195
196     sb.append(".");
197     if (!" ".equals(chain)) {
198       sb.append(chain);
199     }
200   }
201 }