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