Jalview.isJS() --> Platform.isJS(), DBRefEntry[] --> List<DBRefEntry>
[jalview.git] / src / jalview / io / ModellerDescription.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.io;
22
23 import jalview.datamodel.DBRefEntry;
24 import jalview.datamodel.SequenceI;
25
26 import java.util.List;
27
28 public class ModellerDescription
29 {
30   /**
31    * Translates between a String containing a set of colon-separated values on a
32    * single line, and sequence start/end and other properties. See PIRFile IO
33    * for its use.
34    */
35   final String[] seqTypes = { "sequence", "structure", "structureX",
36       "structureN" };
37
38   final String[] Fields = { "objectType", "objectId", "startField",
39       "startCode", "endField", "endCode", "description1", "description2",
40       "resolutionField", "tailField" };
41
42   final int TYPE = 0;
43
44   final int LOCALID = 1;
45
46   final int START = 2;
47
48   final int START_CHAIN = 3;
49
50   final int END = 4;
51
52   final int END_CHAIN = 5;
53
54   final int DESCRIPTION1 = 6;
55
56   final int DESCRIPTION2 = 7;
57
58   final int RESOLUTION = 8;
59
60   final int TAIL = 9;
61
62   /**
63    * 0 is free text or empty 1 is something that parses to an integer, or \@
64    */
65   final int Types[] = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0 };
66
67   final char Padding[] = { ' ', ' ', ' ', '.', ' ', '.', '.', '.', '.',
68       '.' };
69
70   java.util.Hashtable fields = new java.util.Hashtable();
71
72   ModellerDescription()
73   {
74     fields.put(Fields[TAIL], "");
75   }
76
77   class resCode
78   {
79     Integer val;
80
81     String field;
82
83     resCode(String f, Integer v)
84     {
85       val = v;
86       field = f;
87     }
88
89     resCode(int v)
90     {
91       val = new Integer(v);
92       field = val.toString();
93     }
94   };
95
96   private resCode validResidueCode(String field)
97   {
98     Integer val = null;
99     com.stevesoft.pat.Regex r = new com.stevesoft.pat.Regex(
100             "\\s*((([-0-9]+).?)|FIRST|LAST|@)");
101
102     if (!r.search(field))
103     {
104       return null; // invalid
105     }
106     String value = r.stringMatched(3);
107     if (value == null)
108     {
109       value = r.stringMatched(1);
110     }
111     // jalview.bin.Cache.log.debug("from '" + field + "' matched '" + value +
112     // "'");
113     try
114     {
115       val = Integer.valueOf(value);
116       return new resCode(field, val); // successful numeric extraction
117     } catch (Exception e)
118     {
119     }
120     return new resCode(field, null);
121   }
122
123   private java.util.Hashtable parseDescription(String desc)
124   {
125     java.util.Hashtable fields = new java.util.Hashtable();
126     java.util.StringTokenizer st = new java.util.StringTokenizer(desc, ":",
127             true);
128
129     String field;
130     int type = -1;
131     if (st.countTokens() > 0)
132     {
133       // parse colon-fields
134       int i = 0;
135       field = st.nextToken(":");
136       do
137       {
138         if (seqTypes[i].equalsIgnoreCase(field))
139         {
140           break;
141         }
142       } while (++i < seqTypes.length);
143
144       if (i < seqTypes.length)
145       {
146         st.nextToken(); // skip ':'
147         // valid seqType for modeller
148         type = i;
149         i = 1; // continue parsing fields
150         while (i < TAIL && st.hasMoreTokens())
151         {
152           if ((field = st.nextToken(":")) != null)
153           {
154             if (!field.equals(":"))
155             {
156               // validate residue field value
157               if (Types[i] == 1)
158               {
159                 resCode val = validResidueCode(field);
160                 if (val != null)
161                 {
162                   fields.put(new String(Fields[i] + "num"), val);
163                 }
164                 else
165                 {
166                   // jalview.bin.Cache.log.debug(
167                   // "Ignoring non-Modeller description: invalid integer-like
168                   // field '" + field + "'");
169                   type = -1; /* invalid field! - throw the FieldSet away */
170                 }
171                 ;
172               }
173               fields.put(Fields[i++], field);
174               if (st.hasMoreTokens())
175               {
176                 st.nextToken(); // skip token sep.
177               }
178             }
179             else
180             {
181               i++;
182             }
183           }
184         }
185         if (i == TAIL)
186         {
187           // slurp remaining fields
188           while (st.hasMoreTokens())
189           {
190             String tl = st.nextToken(":");
191             field += tl.equals(":") ? tl : (":" + tl);
192           }
193           fields.put(Fields[TAIL], field);
194         }
195       }
196     }
197     if (type == -1)
198     {
199       // object is not a proper ModellerPIR object
200       fields = new java.util.Hashtable();
201       fields.put(Fields[TAIL], new String(desc));
202     }
203     else
204     {
205       fields.put(Fields[TYPE], seqTypes[type]);
206     }
207     return fields;
208   }
209
210   ModellerDescription(String desc)
211   {
212     if (desc == null)
213     {
214       desc = "";
215     }
216     fields = parseDescription(desc);
217   }
218
219   void setStartCode(int v)
220   {
221     resCode r;
222     fields.put(Fields[START] + "num", r = new resCode(v));
223     fields.put(Fields[START], r.field);
224   }
225
226   void setEndCode(int v)
227   {
228     resCode r;
229     fields.put(Fields[END] + "num", r = new resCode(v));
230     fields.put(Fields[END], r.field);
231   }
232
233   /**
234    * make a possibly updated modeller field line for the sequence object
235    * 
236    * @param seq
237    *          SequenceI
238    */
239   ModellerDescription(SequenceI seq)
240   {
241
242     if (seq.getDescription() != null)
243     {
244       fields = parseDescription(seq.getDescription());
245     }
246
247     if (isModellerFieldset())
248     {
249       // Set start and end before we update the type (in the case of a
250       // synthesized field set)
251       if (getStartCode() == null || (getStartNum() != seq.getStart()
252               && getStartCode().val != null))
253       {
254         // unset or user updated sequence start position
255         setStartCode(seq.getStart());
256       }
257
258       if (getEndCode() == null || (getEndNum() != seq.getEnd()
259               && getStartCode() != null && getStartCode().val != null))
260       {
261         setEndCode(seq.getEnd());
262       }
263     }
264     else
265     {
266       // synthesize fields
267       setStartCode(seq.getStart());
268       setEndCode(seq.getEnd());
269       fields.put(Fields[LOCALID], seq.getName()); // this may be overwritten
270       // below...
271       // type - decide based on evidence of PDB database references - this also
272       // sets the local reference field
273       int t = 0; // sequence
274       if (seq.getDatasetSequence() != null
275               && seq.getDatasetSequence().getDBRefs() != null)
276       {
277         List<DBRefEntry> dbr = seq.getDatasetSequence().getDBRefs();
278         for (int i = 0, ni = dbr.size(); i < ni; i++)
279         {
280                 DBRefEntry dbri = dbr.get(i);
281           if (dbri != null)
282           {
283             // JBPNote PDB dbRefEntry needs properties to propagate onto
284             // ModellerField
285             // JBPNote Need to get info from the user about whether the sequence
286             // is the one being modelled, or if it is a template.
287             if (dbri.getSource()
288                     .equals(jalview.datamodel.DBRefSource.PDB))
289             {
290               fields.put(Fields[LOCALID], dbri.getAccessionId());
291               t = 2;
292               break;
293             }
294           }
295         }
296       }
297       fields.put(Fields[TYPE], seqTypes[t]);
298     }
299
300   }
301
302   /**
303    * Indicate if fields parsed to a modeller-like colon-separated value line
304    * 
305    * @return boolean
306    */
307   boolean isModellerFieldset()
308   {
309     return (fields.containsKey(Fields[TYPE]));
310   }
311
312   String getDescriptionLine()
313   {
314     String desc = "";
315     int lastfield = Fields.length - 1;
316
317     if (isModellerFieldset())
318     {
319       String value;
320       // try to write a minimal modeller field set, so..
321
322       // find the last valid field in the entry
323
324       for (; lastfield > 6; lastfield--)
325       {
326         if (fields.containsKey(Fields[lastfield]))
327         {
328           break;
329         }
330       }
331
332       for (int i = 0; i < lastfield; i++)
333       {
334         value = (String) fields.get(Fields[i]);
335         if (value != null && value.length() > 0)
336         {
337           desc += ((String) fields.get(Fields[i])) + ":";
338         }
339         else
340         {
341           desc += Padding[i] + ":";
342         }
343       }
344     }
345     // just return the last field if no others were defined.
346     if (fields.containsKey(Fields[lastfield]))
347     {
348       desc += (String) fields.get(Fields[lastfield]);
349     }
350     else
351     {
352       desc += ".";
353     }
354     return desc;
355   }
356
357   int getStartNum()
358   {
359     int start = 0;
360     resCode val = getStartCode();
361     if (val != null && val.val != null)
362     {
363       return val.val.intValue();
364     }
365     return start;
366   }
367
368   resCode getStartCode()
369   {
370     if (isModellerFieldset() && fields.containsKey(Fields[START] + "num"))
371     {
372       return (resCode) fields.get(Fields[START] + "num");
373     }
374     return null;
375   }
376
377   resCode getEndCode()
378   {
379     if (isModellerFieldset() && fields.containsKey(Fields[END] + "num"))
380     {
381       return (resCode) fields.get(Fields[END] + "num");
382     }
383     return null;
384   }
385
386   int getEndNum()
387   {
388     int end = 0;
389     resCode val = getEndCode();
390     if (val != null && val.val != null)
391     {
392       return val.val.intValue();
393     }
394     return end;
395   }
396
397   /**
398    * returns true if sequence object was modifed with a valid modellerField set
399    * 
400    * @param newSeq
401    *          SequenceI
402    * @return boolean
403    */
404   boolean updateSequenceI(SequenceI newSeq)
405   {
406     if (isModellerFieldset())
407     {
408       resCode rc = getStartCode();
409       if (rc != null && rc.val != null)
410       {
411         newSeq.setStart(getStartNum());
412       }
413       else
414       {
415         newSeq.setStart(1);
416       }
417       rc = getEndCode();
418       if (rc != null && rc.val != null)
419       {
420         newSeq.setEnd(getEndNum());
421       }
422       else
423       {
424         newSeq.setEnd(newSeq.getStart() + newSeq.getLength());
425       }
426       return true;
427     }
428     return false;
429   }
430 }