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