a1d83801bb719cd8b40a92453fab37d3a93051a8
[jalview.git] / src / jalview / io / vamsas / DatastoreItem.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.vamsas;
22
23 import java.util.Enumeration;
24 import java.util.Hashtable;
25 import java.util.IdentityHashMap;
26 import java.util.Vector;
27
28 import org.apache.logging.log4j.LogManager;
29 import org.apache.logging.log4j.Logger;
30
31 import jalview.bin.Cache;
32 import jalview.io.VamsasAppDatastore;
33 import jalview.util.MessageManager;
34 import uk.ac.vamsas.client.IClientDocument;
35 import uk.ac.vamsas.client.Vobject;
36 import uk.ac.vamsas.client.VorbaId;
37 import uk.ac.vamsas.objects.core.Entry;
38 import uk.ac.vamsas.objects.core.Provenance;
39 import uk.ac.vamsas.objects.core.Seg;
40
41 /**
42  * Holds all the common machinery for binding objects to vamsas objects
43  * 
44  * @author JimP
45  * 
46  */
47 public abstract class DatastoreItem
48 {
49   /**
50    * 
51    */
52   Entry provEntry = null;
53
54   IClientDocument cdoc;
55
56   Hashtable vobj2jv;
57
58   IdentityHashMap jv2vobj;
59
60   boolean tojalview = false;
61
62   /**
63    * shared log instance
64    */
65   protected static Logger log = LogManager.getLogger(DatastoreItem.class);
66
67   /**
68    * note: this is taken verbatim from jalview.io.VamsasAppDatastore
69    * 
70    * @return the Vobject bound to Jalview datamodel object
71    */
72   protected Vobject getjv2vObj(Object jvobj)
73   {
74     if (jv2vobj.containsKey(jvobj))
75     {
76       return cdoc.getObject((VorbaId) jv2vobj.get(jvobj));
77     }
78     if (Cache.log.isDebugEnabled())
79     {
80       Cache.log.debug(
81               "Returning null VorbaID binding for jalview object " + jvobj);
82     }
83     return null;
84   }
85
86   /**
87    * 
88    * @param vobj
89    * @return Jalview datamodel object bound to the vamsas document object
90    */
91   protected Object getvObj2jv(uk.ac.vamsas.client.Vobject vobj)
92   {
93     if (vobj2jv == null)
94       return null;
95     VorbaId id = vobj.getVorbaId();
96     if (id == null)
97     {
98       id = cdoc.registerObject(vobj);
99       Cache.log.debug(
100               "Registering new object and returning null for getvObj2jv");
101       return null;
102     }
103     if (vobj2jv.containsKey(vobj.getVorbaId()))
104     {
105       return vobj2jv.get(vobj.getVorbaId());
106     }
107     return null;
108   }
109
110   /**
111    * note: this is taken verbatim from jalview.io.VamsasAppDatastore with added
112    * call to updateRegistryEntry
113    * 
114    * @param jvobj
115    * @param vobj
116    */
117   protected void bindjvvobj(Object jvobj, uk.ac.vamsas.client.Vobject vobj)
118   {
119     VorbaId id = vobj.getVorbaId();
120     if (id == null)
121     {
122       id = cdoc.registerObject(vobj);
123       if (id == null || vobj.getVorbaId() == null
124               || cdoc.getObject(id) != vobj)
125       {
126         Cache.log.error("Failed to get id for "
127                 + (vobj.isRegisterable() ? "registerable"
128                         : "unregisterable")
129                 + " object " + vobj);
130       }
131     }
132     if (vobj2jv.containsKey(vobj.getVorbaId())
133             && !(vobj2jv.get(vobj.getVorbaId())).equals(jvobj))
134     {
135       Cache.log.debug(
136               "Warning? Overwriting existing vamsas id binding for "
137                       + vobj.getVorbaId(),
138               new Exception(MessageManager.getString(
139                       "exception.overwriting_vamsas_id_binding")));
140     }
141     else if (jv2vobj.containsKey(jvobj)
142             && !((VorbaId) jv2vobj.get(jvobj)).equals(vobj.getVorbaId()))
143     {
144       Cache.log.debug(
145               "Warning? Overwriting existing jalview object binding for "
146                       + jvobj,
147               new Exception(MessageManager.getString(
148                       "exception.overwriting_jalview_id_binding")));
149     }
150     /*
151      * Cache.log.error("Attempt to make conflicting object binding! "+vobj+" id "
152      * +vobj.getVorbaId()+" already bound to "+getvObj2jv(vobj)+" and "+jvobj+"
153      * already bound to "+getjv2vObj(jvobj),new Exception("Excessive call to
154      * bindjvvobj")); }
155      */
156     // we just update the hash's regardless!
157     Cache.log.debug("Binding " + vobj.getVorbaId() + " to " + jvobj);
158     vobj2jv.put(vobj.getVorbaId(), jvobj);
159     // JBPNote - better implementing a hybrid invertible hash.
160     jv2vobj.put(jvobj, vobj.getVorbaId());
161     if (jvobj == this.jvobj || vobj == this.vobj)
162     {
163       updateRegistryEntry(jvobj, vobj);
164     }
165   }
166
167   /**
168    * update the vobj and jvobj references and the registry entry for this
169    * datastore object called by bindjvvobj and replacejvobjmapping
170    */
171   private void updateRegistryEntry(Object jvobj, Vobject vobj)
172   {
173     if (this.jvobj != null && this.vobj != null)
174     {
175       Cache.log.debug("updating dsobj registry. ("
176               + this.getClass().getName() + ")");
177     }
178     this.jvobj = jvobj;
179     this.vobj = vobj;
180     dsReg.registerDsObj(this);
181   }
182
183   /**
184    * replaces oldjvobject with newjvobject in the Jalview Object <> VorbaID
185    * binding tables note: originally taken verbatim from
186    * jalview.io.VamsasAppDatastore with added call to updateRegistryEntry
187    * 
188    * @param oldjvobject
189    * @param newjvobject
190    *          (may be null to forget the oldjvobject's document mapping)
191    * 
192    */
193   protected void replaceJvObjMapping(Object oldjvobject, Object newjvobject)
194   {
195     Object vobject = jv2vobj.remove(oldjvobject);
196     if (vobject == null)
197     {
198       throw new Error(MessageManager.formatMessage(
199               "error.implementation_error_old_jalview_object_not_bound",
200               new String[]
201               { oldjvobject.toString() }));
202     }
203     if (newjvobject != null)
204     {
205       jv2vobj.put(newjvobject, vobject);
206       vobj2jv.put(vobject, newjvobject);
207       updateRegistryEntry(newjvobject, vobj);
208     }
209   }
210
211   public DatastoreItem()
212   {
213     super();
214   }
215
216   public DatastoreItem(VamsasAppDatastore datastore)
217   {
218     this();
219     initDatastoreItem(datastore);
220     // TODO Auto-generated constructor stub
221   }
222
223   /**
224    * construct and initialise datastore object and retrieve object bound to
225    * vobj2 and validate it against boundType
226    * 
227    * @param datastore2
228    * @param vobj2
229    * @param boundType
230    */
231   public DatastoreItem(VamsasAppDatastore datastore2, Vobject vobj2,
232           Class boundType)
233   {
234     this(datastore2);
235     vobj = vobj2;
236     jvobj = getvObj2jv(vobj2);
237     tojalview = true;
238     if (jvobj != null && !(boundType.isAssignableFrom(jvobj.getClass())))
239     {
240       throw new Error(MessageManager.formatMessage(
241               "error.implementation_error_vamsas_doc_class_should_bind_to_type",
242               new String[]
243               { vobj.getClass().toString(), boundType.toString(),
244                   jvobj.getClass().toString() }));
245     }
246     dsReg.registerDsObj(this);
247   }
248
249   /**
250    * construct and initialise datastore object and retrieve document object
251    * bound to Jalview object jvobj2 and validate it against boundType
252    * 
253    * @param datastore2
254    *          the datastore
255    * @param jvobj2
256    *          the jalview object
257    * @param boundToType
258    *          - the document object class that the bound object should be
259    *          assignable from
260    */
261   public DatastoreItem(VamsasAppDatastore datastore2, Object jvobj2,
262           Class boundToType)
263   {
264     this(datastore2);
265     jvobj = jvobj2;
266     tojalview = false;
267     vobj = getjv2vObj(jvobj);
268     if (vobj != null && !(boundToType.isAssignableFrom(vobj.getClass())))
269     {
270       throw new Error(MessageManager.formatMessage(
271               "error.implementation_error_vamsas_doc_class_should_bind_to_type",
272               new String[]
273               { jvobj2.getClass().toString(), boundToType.toString(),
274                   vobj.getClass().toString() }));
275     }
276     dsReg.registerDsObj(this);
277   }
278
279   /**
280    * create a new vobj to be added to the document for the jalview object jvobj
281    * (jvobj!=null, vobj==null)
282    */
283   public abstract void addToDocument();
284
285   /**
286    * handle a conflict where both an existing vobj has been updated and a local
287    * jalview object has been updated. This method is only called from doSync,
288    * when an incoming update from the vamsas session conflicts with local
289    * modifications made by the Jalview user. (jvobj!=null, vobj!=null)
290    */
291   public abstract void conflict();
292
293   /**
294    * update an existing vobj in the document with the data and settings from
295    * jvobj (jvobj!=null, vobj!=null)
296    */
297   public abstract void updateToDoc();
298
299   /**
300    * update the local jalview object with the data from an existing vobj in the
301    * document (jvobj!=null, vobj!=null)
302    */
303   public abstract void updateFromDoc();
304
305   /**
306    * create a new local jvobj bound to the vobj in the document. (jvobj==null,
307    * vobj!=null)
308    */
309   public abstract void addFromDocument();
310
311   boolean addtodoc = false, conflicted = false, updated = false,
312           addfromdoc = false, success = false;
313
314   private boolean updatedtodoc;
315
316   private boolean updatedfromdoc;
317
318   /**
319    * Sync jalview to document. Enact addToDocument, conflict or update dependent
320    * on existence of a vobj bound to the local jvobj.
321    */
322   protected void doSync()
323   {
324     dsReg.registerDsObj(this);
325     if (vobj == null)
326     {
327       log.debug("adding new vobject to document.");
328       addtodoc = true;
329       addToDocument();
330     }
331     else
332     {
333       if (vobj.isUpdated())
334       {
335         log.debug("Handling update conflict for existing bound vobject.");
336         conflicted = true;
337         conflict();
338       }
339       else
340       {
341         log.debug("updating existing vobject in document.");
342         updatedtodoc = true;
343         updateToDoc();
344       }
345     }
346     // no exceptions were encountered...
347     success = true;
348   }
349
350   /**
351    * Update jalview from document. enact addFromDocument if no local jvobj
352    * exists, or update iff jvobj exists and the vobj.isUpdated() flag is set.
353    */
354   protected void doJvUpdate()
355   {
356     dsReg.registerDsObj(this);
357     if (jvobj == null)
358     {
359       log.debug("adding new vobject to Jalview from Document");
360       addfromdoc = true;
361       addFromDocument();
362     }
363     else
364     {
365       if (vobj.isUpdated())
366       {
367         log.debug("updating Jalview from existing bound vObject");
368         updatedfromdoc = true;
369         updateFromDoc();
370       }
371     }
372   }
373
374   VamsasAppDatastore datastore = null;
375
376   /**
377    * object in vamsas document
378    */
379   protected Vobject vobj = null;
380
381   /**
382    * local jalview object
383    */
384   protected Object jvobj = null;
385
386   protected DatastoreRegistry dsReg;
387
388   public void initDatastoreItem(VamsasAppDatastore ds)
389   {
390     datastore = ds;
391     dsReg = ds.getDatastoreRegisty();
392     initDatastoreItem(ds.getProvEntry(), ds.getClientDocument(),
393             ds.getVamsasObjectBinding(), ds.getJvObjectBinding());
394   }
395
396   private void initDatastoreItem(Entry provEntry, IClientDocument cdoc,
397           Hashtable vobj2jv, IdentityHashMap jv2vobj)
398   {
399     this.provEntry = provEntry;
400     this.cdoc = cdoc;
401     this.vobj2jv = vobj2jv;
402     this.jv2vobj = jv2vobj;
403   }
404
405   protected boolean isModifiable(String modifiable)
406   {
407     return modifiable == null; // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
408   }
409
410   protected Vector getjv2vObjs(Vector alsq)
411   {
412     Vector vObjs = new Vector();
413     Enumeration elm = alsq.elements();
414     while (elm.hasMoreElements())
415     {
416       vObjs.addElement(getjv2vObj(elm.nextElement()));
417     }
418     return vObjs;
419   }
420
421   // utility functions
422   /**
423    * get start<end range of segment, adjusting for inclusivity flag and
424    * polarity.
425    * 
426    * @param visSeg
427    * @param ensureDirection
428    *          when true - always ensure start is less than end.
429    * @return int[] { start, end, direction} where direction==1 for range running
430    *         from end to start.
431    */
432   public int[] getSegRange(Seg visSeg, boolean ensureDirection)
433   {
434     boolean incl = visSeg.getInclusive();
435     // adjust for inclusive flag.
436     int pol = (visSeg.getStart() <= visSeg.getEnd()) ? 1 : -1; // polarity of
437     // region.
438     int start = visSeg.getStart() + (incl ? 0 : pol);
439     int end = visSeg.getEnd() + (incl ? 0 : -pol);
440     if (ensureDirection && pol == -1)
441     {
442       // jalview doesn't deal with inverted ranges, yet.
443       int t = end;
444       end = start;
445       start = t;
446     }
447     return new int[] { start, end, pol < 0 ? 1 : 0 };
448   }
449
450   /**
451    * provenance bits
452    */
453   protected jalview.datamodel.Provenance getJalviewProvenance(
454           Provenance prov)
455   {
456     // TODO: fix App and Action entries and check use of provenance in jalview.
457     jalview.datamodel.Provenance jprov = new jalview.datamodel.Provenance();
458     for (int i = 0; i < prov.getEntryCount(); i++)
459     {
460       jprov.addEntry(prov.getEntry(i).getUser(),
461               prov.getEntry(i).getAction(), prov.getEntry(i).getDate(),
462               prov.getEntry(i).getId());
463     }
464
465     return jprov;
466   }
467
468   /**
469    * 
470    * @return default initial provenance list for a Jalview created vamsas
471    *         object.
472    */
473   Provenance dummyProvenance()
474   {
475     return dummyProvenance(null);
476   }
477
478   protected Entry dummyPEntry(String action)
479   {
480     Entry entry = new Entry();
481     entry.setApp(this.provEntry.getApp());
482     if (action != null)
483     {
484       entry.setAction(action);
485     }
486     else
487     {
488       entry.setAction("created.");
489     }
490     entry.setDate(new java.util.Date());
491     entry.setUser(this.provEntry.getUser());
492     return entry;
493   }
494
495   protected Provenance dummyProvenance(String action)
496   {
497     Provenance prov = new Provenance();
498     prov.addEntry(dummyPEntry(action));
499     return prov;
500   }
501
502   protected void addProvenance(Provenance p, String action)
503   {
504     p.addEntry(dummyPEntry(action));
505   }
506
507   /**
508    * @return true if jalview was being updated from the vamsas document
509    */
510   public boolean isTojalview()
511   {
512     return tojalview;
513   }
514
515   /**
516    * @return true if addToDocument() was called.
517    */
518   public boolean isAddtodoc()
519   {
520     return addtodoc;
521   }
522
523   /**
524    * @return true if conflict() was called
525    */
526   public boolean isConflicted()
527   {
528     return conflicted;
529   }
530
531   /**
532    * @return true if updateFromDoc() was called
533    */
534   public boolean isUpdatedFromDoc()
535   {
536     return updatedfromdoc;
537   }
538
539   /**
540    * @return true if updateToDoc() was called
541    */
542   public boolean isUpdatedToDoc()
543   {
544     return updatedtodoc;
545   }
546
547   /**
548    * @return true if addFromDocument() was called.
549    */
550   public boolean isAddfromdoc()
551   {
552     return addfromdoc;
553   }
554
555   /**
556    * @return true if object sync logic completed normally.
557    */
558   public boolean isSuccess()
559   {
560     return success;
561   }
562
563   /**
564    * @return the vobj
565    */
566   public Vobject getVobj()
567   {
568     return vobj;
569   }
570
571   /**
572    * @return the jvobj
573    */
574   public Object getJvobj()
575   {
576     return jvobj;
577   }
578
579   public boolean docWasUpdated()
580   {
581     return (this.addtodoc || this.updated) && this.success;
582   }
583
584   public boolean jvWasUpdated()
585   {
586     return (success); // TODO : Implement this properly!
587   }
588
589 }