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