update author list in license for (JAL-826)
[jalview.git] / src / jalview / io / ModellerDescription.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, 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, ":",true);
123     
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         st.nextToken(); // skip ':'
142         // valid seqType for modeller
143         type = i;
144         i = 1; // continue parsing fields
145         while (i < TAIL && st.hasMoreTokens())
146         {
147           if ((field = st.nextToken(":")) != null)
148           {
149             if (!field.equals(":"))
150             {
151             // validate residue field value
152             if (Types[i] == 1)
153             {
154               resCode val = validResidueCode(field);
155               if (val != null)
156               {
157                 fields.put(new String(Fields[i] + "num"), val);
158               }
159               else
160               {
161                 // jalview.bin.Cache.log.debug(
162                 // "Ignoring non-Modeller description: invalid integer-like
163                 // field '" + field + "'");
164                 type = -1; /* invalid field! - throw the FieldSet away */
165               }
166               ;
167             }
168             fields.put(Fields[i++], field);
169             if (st.hasMoreTokens()) {
170               st.nextToken(); // skip token sep.
171             }
172             } else {
173               i++;
174             }
175           }
176         }
177         if (i == TAIL)
178         {
179           // slurp remaining fields
180           while (st.hasMoreTokens())
181           {
182             String tl = st.nextToken(":");
183             field += tl.equals(":") ? tl : (":" + tl); 
184           }
185           fields.put(Fields[TAIL], field);
186         }
187       }
188     }
189     if (type == -1)
190     {
191       // object is not a proper ModellerPIR object
192       fields = new java.util.Hashtable();
193       fields.put(Fields[TAIL], new String(desc));
194     }
195     else
196     {
197       fields.put(Fields[TYPE], seqTypes[type]);
198     }
199     return fields;
200   }
201
202   ModellerDescription(String desc)
203   {
204     if (desc == null)
205     {
206       desc = "";
207     }
208     fields = parseDescription(desc);
209   }
210
211   void setStartCode(int v)
212   {
213     resCode r;
214     fields.put(Fields[START] + "num", r = new resCode(v));
215     fields.put(Fields[START], r.field);
216   }
217
218   void setEndCode(int v)
219   {
220     resCode r;
221     fields.put(Fields[END] + "num", r = new resCode(v));
222     fields.put(Fields[END], r.field);
223   }
224
225   /**
226    * make a possibly updated modeller field line for the sequence object
227    * 
228    * @param seq
229    *          SequenceI
230    */
231   ModellerDescription(SequenceI seq)
232   {
233
234     if (seq.getDescription() != null)
235     {
236       fields = parseDescription(seq.getDescription());
237     }
238
239     if (isModellerFieldset())
240     {
241       // Set start and end before we update the type (in the case of a
242       // synthesized field set)
243       if (getStartCode()==null || (getStartNum() != seq.getStart() && getStartCode().val != null))
244       {
245         // unset or user updated sequence start position
246         setStartCode(seq.getStart());
247       }
248
249       if (getEndCode()==null || (getEndNum() != seq.getEnd() && getStartCode()!=null && getStartCode().val != null))
250       {
251         setEndCode(seq.getEnd());
252       }
253     }
254     else
255     {
256       // synthesize fields
257       setStartCode(seq.getStart());
258       setEndCode(seq.getEnd());
259       fields.put(Fields[LOCALID], seq.getName()); // this may be overwritten
260       // below...
261       // type - decide based on evidence of PDB database references - this also
262       // sets the local reference field
263       int t = 0; // sequence
264       if (seq.getDatasetSequence() != null
265               && seq.getDatasetSequence().getDBRef() != null)
266       {
267         jalview.datamodel.DBRefEntry[] dbr = seq.getDatasetSequence()
268                 .getDBRef();
269         int i, j;
270         for (i = 0, j = dbr.length; i < j; i++)
271         {
272           if (dbr[i] != null)
273           {
274             // JBPNote PDB dbRefEntry needs properties to propagate onto
275             // ModellerField
276             // JBPNote Need to get info from the user about whether the sequence
277             // is the one being modelled, or if it is a template.
278             if (dbr[i].getSource()
279                     .equals(jalview.datamodel.DBRefSource.PDB))
280             {
281               fields.put(Fields[LOCALID], dbr[i].getAccessionId());
282               t = 2;
283               break;
284             }
285           }
286         }
287       }
288       fields.put(Fields[TYPE], seqTypes[t]);
289     }
290
291   }
292
293   /**
294    * Indicate if fields parsed to a modeller-like colon-separated value line
295    * 
296    * @return boolean
297    */
298   boolean isModellerFieldset()
299   {
300     return (fields.containsKey(Fields[TYPE]));
301   }
302
303   String getDescriptionLine()
304   {
305     String desc = "";
306     int lastfield = Fields.length - 1;
307
308     if (isModellerFieldset())
309     {
310       String value;
311       // try to write a minimal modeller field set, so..
312
313       // find the last valid field in the entry
314
315       for (; lastfield > 6; lastfield--)
316       {
317         if (fields.containsKey(Fields[lastfield]))
318         {
319           break;
320         }
321       }
322
323       for (int i = 0; i < lastfield; i++)
324       {
325         value = (String) fields.get(Fields[i]);
326         if (value != null && value.length() > 0)
327         {
328           desc += ((String) fields.get(Fields[i])) + ":";
329         }
330         else
331         {
332           desc += Padding[i] + ":";
333         }
334       }
335     }
336     // just return the last field if no others were defined.
337     if (fields.containsKey(Fields[lastfield]))
338     {
339       desc += (String) fields.get(Fields[lastfield]);
340     }
341     else
342     {
343       desc += ".";
344     }
345     return desc;
346   }
347
348   int getStartNum()
349   {
350     int start = 0;
351     resCode val = getStartCode();
352     if (val!=null && val.val != null)
353     {
354       return val.val.intValue();
355     }
356     return start;
357   }
358
359   resCode getStartCode()
360   {
361     if (isModellerFieldset() && fields.containsKey(Fields[START] + "num"))
362     {
363       return (resCode) fields.get(Fields[START] + "num");
364     }
365     return null;
366   }
367
368   resCode getEndCode()
369   {
370     if (isModellerFieldset() && fields.containsKey(Fields[END] + "num"))
371     {
372       return (resCode) fields.get(Fields[END] + "num");
373     }
374     return null;
375   }
376
377   int getEndNum()
378   {
379     int end = 0;
380     resCode val = getEndCode();
381     if (val!=null && val.val != null)
382     {
383       return val.val.intValue();
384     }
385     return end;
386   }
387
388   /**
389    * returns true if sequence object was modifed with a valid modellerField set
390    * 
391    * @param newSeq
392    *          SequenceI
393    * @return boolean
394    */
395   boolean updateSequenceI(SequenceI newSeq)
396   {
397     if (isModellerFieldset())
398     {
399       resCode rc=getStartCode();
400       if (rc!=null && rc.val != null)
401       {
402         newSeq.setStart(getStartNum());
403       }
404       else
405       {
406         newSeq.setStart(1);
407       }
408       rc=getEndCode();
409       if (rc!=null && rc.val != null)
410       {
411         newSeq.setEnd(getEndNum());
412       }
413       else
414       {
415         newSeq.setEnd(newSeq.getStart() + newSeq.getLength());
416       }
417       return true;
418     }
419     return false;
420   }
421 }