JAL-3390 move StructureCommands to utils package
[jalview.git] / src / jalview / util / StructureCommands.java
1 package jalview.util;
2
3 import jalview.api.AlignViewportI;
4 import jalview.api.AlignmentViewPanel;
5 import jalview.api.FeatureRenderer;
6 import jalview.api.SequenceRenderer;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.HiddenColumns;
9 import jalview.datamodel.SequenceI;
10 import jalview.ext.rbvi.chimera.AtomSpecModel;
11 import jalview.renderer.seqfeatures.FeatureColourFinder;
12 import jalview.structure.StructureMapping;
13 import jalview.structures.models.AAStructureBindingModel;
14
15 import java.awt.Color;
16 import java.util.Collections;
17 import java.util.Iterator;
18 import java.util.LinkedHashMap;
19 import java.util.List;
20 import java.util.Map;
21
22 /**
23  * A class with common methods for building commands for Jmol, Chimera, or other
24  * 
25  * @author gmcarstairs
26  */
27 public abstract class StructureCommands
28 {
29
30   /**
31    * Helper method to add one contiguous range to the AtomSpec model for the given
32    * value (creating the model if necessary). As used by Jalview, {@code value} is
33    * <ul>
34    * <li>a colour, when building a 'colour structure by sequence' command</li>
35    * <li>a feature value, when building a 'set Chimera attributes from features'
36    * command</li>
37    * </ul>
38    * 
39    * @param map
40    * @param value
41    * @param model
42    * @param startPos
43    * @param endPos
44    * @param chain
45    */
46   public static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
47           Object value,
48           int model, int startPos, int endPos, String chain)
49   {
50     /*
51      * Get/initialize map of data for the colour
52      */
53     AtomSpecModel atomSpec = map.get(value);
54     if (atomSpec == null)
55     {
56       atomSpec = new AtomSpecModel();
57       map.put(value, atomSpec);
58     }
59   
60     atomSpec.addRange(model, startPos, endPos, chain);
61   }
62
63   /**
64    * Build a data structure which records contiguous subsequences by model and
65    * chain. From this we can easily generate the Chimera or Jmol specific
66    * selection expression.
67    * 
68    * <pre>
69    * Color
70    *     Model number
71    *         Chain
72    *             list of start/end ranges
73    * </pre>
74    * 
75    * Ordering is by order of addition (for colours and positions), natural
76    * ordering (for models and chains)
77    * 
78    * @param viewPanel
79    * @return
80    */
81   public static Map<Object, AtomSpecModel> buildColoursMap(
82           AAStructureBindingModel binding, AlignmentViewPanel viewPanel)
83   {
84     FeatureRenderer fr = viewPanel.getFeatureRenderer();
85     FeatureColourFinder finder = new FeatureColourFinder(fr);
86     AlignViewportI viewport = viewPanel.getAlignViewport();
87     HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
88     AlignmentI al = viewport.getAlignment();
89     SequenceRenderer sr = binding.getSequenceRenderer(viewPanel);
90     String[] files = binding.getStructureFiles();
91     SequenceI[][] sequence = binding.getSequence();
92     
93     Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
94     Color lastColour = null;
95   
96     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
97     {
98       StructureMapping[] mapping = binding.getSsm()
99               .getMapping(files[pdbfnum]);
100   
101       if (mapping == null || mapping.length < 1)
102       {
103         continue;
104       }
105   
106       int startPos = -1, lastPos = -1;
107       String lastChain = "";
108       for (int s = 0; s < sequence[pdbfnum].length; s++)
109       {
110         for (int sp, m = 0; m < mapping.length; m++)
111         {
112           final SequenceI seq = sequence[pdbfnum][s];
113           if (mapping[m].getSequence() == seq
114                   && (sp = al.findIndex(seq)) > -1)
115           {
116             SequenceI asp = al.getSequenceAt(sp);
117             for (int r = 0; r < asp.getLength(); r++)
118             {
119               // no mapping to gaps in sequence
120               if (Comparison.isGap(asp.getCharAt(r)))
121               {
122                 continue;
123               }
124               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
125   
126               if (pos < 1 || pos == lastPos)
127               {
128                 continue;
129               }
130   
131               Color colour = sr.getResidueColour(seq, r, finder);
132   
133               /*
134                * hidden regions are shown gray or, optionally, ignored
135                */
136               if (!cs.isVisible(r))
137               {
138                 if (binding.isHideHiddenRegions())
139                 {
140                   continue;
141                 }
142                 else
143                 {
144                   colour = Color.GRAY;
145                 }
146               }
147   
148               final String chain = mapping[m].getChain();
149   
150               /*
151                * Just keep incrementing the end position for this colour range
152                * _unless_ colour, PDB model or chain has changed, or there is a
153                * gap in the mapped residue sequence
154                */
155               final boolean newColour = !colour.equals(lastColour);
156               final boolean nonContig = lastPos + 1 != pos;
157               final boolean newChain = !chain.equals(lastChain);
158               if (newColour || nonContig || newChain)
159               {
160                 if (startPos != -1)
161                 {
162                   StructureCommands.addAtomSpecRange(colourMap, lastColour,
163                           pdbfnum, startPos,
164                           lastPos, lastChain);
165                 }
166                 startPos = pos;
167               }
168               lastColour = colour;
169               lastPos = pos;
170               lastChain = chain;
171             }
172             // final colour range
173             if (lastColour != null)
174             {
175               StructureCommands.addAtomSpecRange(colourMap, lastColour,
176                       pdbfnum,
177                       startPos, lastPos, lastChain);
178             }
179           }
180         }
181       }
182     }
183     return colourMap;
184   }
185
186   /**
187    * A helper method that takes a list of [start-end] ranges, format them as
188    * s1-e1,s2-e2 etc and appends to the string buffer. Ranges are sorted, and
189    * coalesced if they overlap. The chain token, if not null, is appended to each
190    * resulting subrange.
191    * 
192    * @param sb
193    * @param rangeList
194    * @param chainToken
195    * @param firstPositionForModel
196    */
197   protected static void appendResidueRange(StringBuilder sb, List<int[]> rangeList,
198           String chainToken, boolean firstPositionForModel)
199   {
200     /*
201      * sort ranges into ascending start position order
202      */
203     Collections.sort(rangeList, IntRangeComparator.ASCENDING);
204   
205     int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
206     int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
207   
208     Iterator<int[]> iterator = rangeList.iterator();
209     while (iterator.hasNext())
210     {
211       int[] range = iterator.next();
212       if (range[0] <= end + 1)
213       {
214         /*
215          * range overlaps or is contiguous with the last one
216          * - so just extend the end position, and carry on
217          * (unless this is the last in the list)
218          */
219         end = Math.max(end, range[1]);
220       }
221       else
222       {
223         /*
224          * we have a break so append the last range
225          */
226         appendRange(sb, start, end, chainToken, firstPositionForModel);
227         firstPositionForModel = false;
228         start = range[0];
229         end = range[1];
230       }
231     }
232   
233     /*
234      * and append the last range
235      */
236     if (!rangeList.isEmpty())
237     {
238       appendRange(sb, start, end, chainToken, firstPositionForModel);
239     }
240   }
241
242   /**
243    * A helper method that appends one start-end range, and an (optional) chain
244    * token to an atomspec (a null token is not appended)
245    * 
246    * @param sb
247    * @param start
248    * @param end
249    * @param chainToken
250    * @param firstPositionForModel
251    */
252   protected static void appendRange(StringBuilder sb, int start, int end,
253           String chainToken, boolean firstPositionForModel)
254   {
255     if (!firstPositionForModel)
256     {
257       sb.append(",");
258     }
259     if (end == start)
260     {
261       sb.append(start);
262     }
263     else
264     {
265       sb.append(start).append("-").append(end);
266     }
267     if (chainToken != null)
268     {
269       sb.append(chainToken);
270     }
271   }
272
273 }