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