2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.ext.rbvi.chimera;
23 import jalview.util.IntRangeComparator;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Iterator;
28 import java.util.List;
30 import java.util.TreeMap;
33 * A class to model a Chimera atomspec pattern, for example
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
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>
49 * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
52 public class AtomSpecModel
54 private Map<Integer, Map<String, List<int[]>>> atomSpec;
59 public AtomSpecModel()
61 atomSpec = new TreeMap<>();
65 * Adds one contiguous range to this atom spec
72 public void addRange(int model, int startPos, int endPos, String chain)
75 * Get/initialize map of data for the colour and model
77 Map<String, List<int[]>> modelData = atomSpec.get(model);
78 if (modelData == null)
80 atomSpec.put(model, modelData = new TreeMap<>());
84 * Get/initialize map of data for colour, model and chain
86 List<int[]> chainData = modelData.get(chain);
87 if (chainData == null)
89 chainData = new ArrayList<>();
90 modelData.put(chain, chainData);
94 * Add the start/end positions
96 chainData.add(new int[] { startPos, endPos });
97 // TODO add intelligently, using a RangeList class
101 * Returns the range(s) formatted as a Chimera atomspec
105 public String getAtomSpec()
107 StringBuilder sb = new StringBuilder(128);
108 boolean firstModel = true;
109 for (Integer model : atomSpec.keySet())
116 sb.append("#").append(model).append(":");
118 boolean firstPositionForModel = true;
119 final Map<String, List<int[]>> modelData = atomSpec.get(model);
121 for (String chain : modelData.keySet())
123 chain = " ".equals(chain) ? chain : chain.trim();
125 List<int[]> rangeList = modelData.get(chain);
128 * sort ranges into ascending start position order
130 Collections.sort(rangeList, IntRangeComparator.ASCENDING);
132 int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
133 int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
135 Iterator<int[]> iterator = rangeList.iterator();
136 while (iterator.hasNext())
138 int[] range = iterator.next();
139 if (range[0] <= end + 1)
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)
146 end = Math.max(end, range[1]);
151 * we have a break so append the last range
153 appendRange(sb, start, end, chain, firstPositionForModel,
155 firstPositionForModel = false;
162 * and append the last range
164 if (!rangeList.isEmpty())
166 appendRange(sb, start, end, chain, firstPositionForModel, false);
167 firstPositionForModel = false;
171 return sb.toString();
179 * @param firstPositionForModel
181 protected void appendRange(StringBuilder sb, int start, int end,
182 String chain, boolean firstPositionForModel, boolean isChimeraX)
184 if (!firstPositionForModel)
194 sb.append(start).append("-").append(end);
200 if (!" ".equals(chain))
208 * Returns the range(s) formatted as a ChimeraX atomspec, for example
210 * #1/A:2-20,30-40/B:10-20|#2/A:12-30
214 public String getAtomSpecX()
216 StringBuilder sb = new StringBuilder(128);
217 boolean firstModel = true;
218 for (Integer model : atomSpec.keySet())
225 sb.append("#").append(model);
227 final Map<String, List<int[]>> modelData = atomSpec.get(model);
229 for (String chain : modelData.keySet())
231 boolean firstPositionForChain = true;
232 chain = " ".equals(chain) ? chain : chain.trim();
233 sb.append("/").append(chain).append(":");
234 List<int[]> rangeList = modelData.get(chain);
237 * sort ranges into ascending start position order
239 Collections.sort(rangeList, IntRangeComparator.ASCENDING);
241 int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
242 int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
244 Iterator<int[]> iterator = rangeList.iterator();
245 while (iterator.hasNext())
247 int[] range = iterator.next();
248 if (range[0] <= end + 1)
251 * range overlaps or is contiguous with the last one
252 * - so just extend the end position, and carry on
253 * (unless this is the last in the list)
255 end = Math.max(end, range[1]);
260 * we have a break so append the last range
262 appendRange(sb, start, end, chain, firstPositionForChain, true);
265 firstPositionForChain = false;
270 * and append the last range
272 if (!rangeList.isEmpty())
274 appendRange(sb, start, end, chain, firstPositionForChain, true);
276 firstPositionForChain = false;
279 return sb.toString();