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