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