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