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