Merge branch 'alpha/origin_2022_JAL-3066_Jalview_212_slivka-integration' into spike...
[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.datamodel.DBRefEntry;
24 import jalview.datamodel.SequenceI;
25 import jalview.util.Platform;
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 static Regex VALIDATION_REGEX;
100
101   private static Regex getRegex()
102   {
103     return (VALIDATION_REGEX == null
104             ? VALIDATION_REGEX = Platform
105                     .newRegex("\\s*((([-0-9]+).?)|FIRST|LAST|@)", null)
106             : VALIDATION_REGEX);
107   }
108
109   private resCode validResidueCode(String field)
110   {
111     Integer val = null;
112     Regex r = getRegex();
113     if (!r.search(field))
114     {
115       return null; // invalid
116     }
117     String value = r.stringMatched(3);
118     if (value == null)
119     {
120       value = r.stringMatched(1);
121     }
122     // jalview.bin.Cache.log.debug("from '" + field + "' matched '" + value +
123     // "'");
124     try
125     {
126       val = Integer.valueOf(value);
127       return new resCode(field, val); // successful numeric extraction
128     } catch (Exception e)
129     {
130     }
131     return new resCode(field, null);
132   }
133
134   private java.util.Hashtable parseDescription(String desc)
135   {
136     java.util.Hashtable fields = new java.util.Hashtable();
137     java.util.StringTokenizer st = new java.util.StringTokenizer(desc, ":",
138             true);
139
140     String field;
141     int type = -1;
142     if (st.countTokens() > 0)
143     {
144       // parse colon-fields
145       int i = 0;
146       field = st.nextToken(":");
147       do
148       {
149         if (seqTypes[i].equalsIgnoreCase(field))
150         {
151           break;
152         }
153       } while (++i < seqTypes.length);
154
155       if (i < seqTypes.length)
156       {
157         st.nextToken(); // skip ':'
158         // valid seqType for modeller
159         type = i;
160         i = 1; // continue parsing fields
161         while (i < TAIL && st.hasMoreTokens())
162         {
163           if ((field = st.nextToken(":")) != null)
164           {
165             if (!field.equals(":"))
166             {
167               // validate residue field value
168               if (Types[i] == 1)
169               {
170                 resCode val = validResidueCode(field);
171                 if (val != null)
172                 {
173                   fields.put(new String(Fields[i] + "num"), val);
174                 }
175                 else
176                 {
177                   // jalview.bin.Cache.log.debug(
178                   // "Ignoring non-Modeller description: invalid integer-like
179                   // field '" + field + "'");
180                   type = -1; /* invalid field! - throw the FieldSet away */
181                 }
182                 ;
183               }
184               fields.put(Fields[i++], field);
185               if (st.hasMoreTokens())
186               {
187                 st.nextToken(); // skip token sep.
188               }
189             }
190             else
191             {
192               i++;
193             }
194           }
195         }
196         if (i == TAIL)
197         {
198           // slurp remaining fields
199           while (st.hasMoreTokens())
200           {
201             String tl = st.nextToken(":");
202             field += tl.equals(":") ? tl : (":" + tl);
203           }
204           fields.put(Fields[TAIL], field);
205         }
206       }
207     }
208     if (type == -1)
209     {
210       // object is not a proper ModellerPIR object
211       fields = new java.util.Hashtable();
212       fields.put(Fields[TAIL], new String(desc));
213     }
214     else
215     {
216       fields.put(Fields[TYPE], seqTypes[type]);
217     }
218     return fields;
219   }
220
221   ModellerDescription(String desc)
222   {
223     if (desc == null)
224     {
225       desc = "";
226     }
227     fields = parseDescription(desc);
228   }
229
230   void setStartCode(int v)
231   {
232     resCode r;
233     fields.put(Fields[START] + "num", r = new resCode(v));
234     fields.put(Fields[START], r.field);
235   }
236
237   void setEndCode(int v)
238   {
239     resCode r;
240     fields.put(Fields[END] + "num", r = new resCode(v));
241     fields.put(Fields[END], r.field);
242   }
243
244   /**
245    * make a possibly updated modeller field line for the sequence object
246    * 
247    * @param seq
248    *          SequenceI
249    */
250   ModellerDescription(SequenceI seq)
251   {
252
253     if (seq.getDescription() != null)
254     {
255       fields = parseDescription(seq.getDescription());
256     }
257
258     if (isModellerFieldset())
259     {
260       // Set start and end before we update the type (in the case of a
261       // synthesized field set)
262       if (getStartCode() == null || (getStartNum() != seq.getStart()
263               && getStartCode().val != null))
264       {
265         // unset or user updated sequence start position
266         setStartCode(seq.getStart());
267       }
268
269       if (getEndCode() == null || (getEndNum() != seq.getEnd()
270               && getStartCode() != null && getStartCode().val != null))
271       {
272         setEndCode(seq.getEnd());
273       }
274     }
275     else
276     {
277       // synthesize fields
278       setStartCode(seq.getStart());
279       setEndCode(seq.getEnd());
280       fields.put(Fields[LOCALID], seq.getName()); // this may be overwritten
281       // below...
282       // type - decide based on evidence of PDB database references - this also
283       // sets the local reference field
284       int t = 0; // sequence
285       if (seq.getDatasetSequence() != null
286               && seq.getDatasetSequence().getDBRefs() != null)
287       {
288         List<DBRefEntry> dbr = seq.getDatasetSequence().getDBRefs();
289         for (int i = 0, ni = dbr.size(); i < ni; i++)
290         {
291                 DBRefEntry dbri = dbr.get(i);
292           if (dbri != null)
293           {
294             // JBPNote PDB dbRefEntry needs properties to propagate onto
295             // ModellerField
296             // JBPNote Need to get info from the user about whether the sequence
297             // is the one being modelled, or if it is a template.
298             if (dbri.getSource()
299                     .equals(jalview.datamodel.DBRefSource.PDB))
300             {
301               fields.put(Fields[LOCALID], dbri.getAccessionId());
302               t = 2;
303               break;
304             }
305           }
306         }
307       }
308       fields.put(Fields[TYPE], seqTypes[t]);
309     }
310
311   }
312
313   /**
314    * Indicate if fields parsed to a modeller-like colon-separated value line
315    * 
316    * @return boolean
317    */
318   boolean isModellerFieldset()
319   {
320     return (fields.containsKey(Fields[TYPE]));
321   }
322
323   String getDescriptionLine()
324   {
325     String desc = "";
326     int lastfield = Fields.length - 1;
327
328     if (isModellerFieldset())
329     {
330       String value;
331       // try to write a minimal modeller field set, so..
332
333       // find the last valid field in the entry
334
335       for (; lastfield > 6; lastfield--)
336       {
337         if (fields.containsKey(Fields[lastfield]))
338         {
339           break;
340         }
341       }
342
343       for (int i = 0; i < lastfield; i++)
344       {
345         value = (String) fields.get(Fields[i]);
346         if (value != null && value.length() > 0)
347         {
348           desc += ((String) fields.get(Fields[i])) + ":";
349         }
350         else
351         {
352           desc += Padding[i] + ":";
353         }
354       }
355     }
356     // just return the last field if no others were defined.
357     if (fields.containsKey(Fields[lastfield]))
358     {
359       desc += (String) fields.get(Fields[lastfield]);
360     }
361     else
362     {
363       desc += ".";
364     }
365     return desc;
366   }
367
368   int getStartNum()
369   {
370     int start = 0;
371     resCode val = getStartCode();
372     if (val != null && val.val != null)
373     {
374       return val.val.intValue();
375     }
376     return start;
377   }
378
379   resCode getStartCode()
380   {
381     if (isModellerFieldset() && fields.containsKey(Fields[START] + "num"))
382     {
383       return (resCode) fields.get(Fields[START] + "num");
384     }
385     return null;
386   }
387
388   resCode getEndCode()
389   {
390     if (isModellerFieldset() && fields.containsKey(Fields[END] + "num"))
391     {
392       return (resCode) fields.get(Fields[END] + "num");
393     }
394     return null;
395   }
396
397   int getEndNum()
398   {
399     int end = 0;
400     resCode val = getEndCode();
401     if (val != null && val.val != null)
402     {
403       return val.val.intValue();
404     }
405     return end;
406   }
407
408   /**
409    * returns true if sequence object was modifed with a valid modellerField set
410    * 
411    * @param newSeq
412    *          SequenceI
413    * @return boolean
414    */
415   boolean updateSequenceI(SequenceI newSeq)
416   {
417     if (isModellerFieldset())
418     {
419       resCode rc = getStartCode();
420       if (rc != null && rc.val != null)
421       {
422         newSeq.setStart(getStartNum());
423       }
424       else
425       {
426         newSeq.setStart(1);
427       }
428       rc = getEndCode();
429       if (rc != null && rc.val != null)
430       {
431         newSeq.setEnd(getEndNum());
432       }
433       else
434       {
435         newSeq.setEnd(newSeq.getStart() + newSeq.getLength());
436       }
437       return true;
438     }
439     return false;
440   }
441 }