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