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