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