applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / Vobject.java
1 /*
2  * This file is part of the Vamsas Client version 0.1. 
3  * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, 
4  *  Andrew Waterhouse and Dominik Lindner.
5  * 
6  * Earlier versions have also been incorporated into Jalview version 2.4 
7  * since 2008, and TOPALi version 2 since 2007.
8  * 
9  * The Vamsas Client is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *  
14  * The Vamsas Client is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  * 
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 package uk.ac.vamsas.client;
23
24 import java.lang.reflect.Field;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.Iterator;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.exolab.castor.mapping.FieldDescriptor;
32 import org.exolab.castor.mapping.FieldHandler;
33 import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
34
35 import uk.ac.vamsas.test.simpleclient.VamsasArchive;
36
37 /**
38  * Base class for all Vamsas objects extracted from an IClientDocument. An
39  * Vobject maybe registered or unregistered.
40  * 
41  * @author jimp
42  * 
43  */
44 public abstract class Vobject {
45   static Log log = LogFactory.getLog(Vobject.class);
46
47   /**
48    * true if Vobject was stored in a vamsas Document or has been retrieved from
49    * it
50    */
51   protected boolean __stored_in_document = false;
52
53   /**
54    * true if Vobject was updated since the vamsas library last read a Vobj with
55    * the same VorbaId from a document.
56    */
57   protected boolean __updated_since_last_read = false;
58
59   /**
60    * true if Vobject appeared in the document after the last access by this
61    * vamsas library instance
62    */
63   protected boolean __added_since_last_read = false;
64
65   /**
66    * memory of the last doHash() value computed for the Vobject
67    * 
68    * @see doHash()
69    */
70   protected int __last_hash = 0;
71
72   /**
73    * set by testInstanceForIdField() if Vobject should have a VorbaId
74    */
75   protected boolean registerable = false;
76
77   protected boolean __visited = false;
78
79   /**
80    * reference to containing object for this Vobject.
81    */
82   protected Vobject V_parent = null;
83
84   /**
85    * unique id for all vamsas objects allows unambiguous referencing to any
86    * Vobject in the vamsas document
87    */
88   protected VorbaId vorbaId = null;
89
90   /**
91    * the source of unique VorbaIds.
92    */
93   protected IVorbaIdFactory __vorba = null;
94
95   /*
96    * (non-Javadoc)
97    * 
98    * @see java.lang.Object#finalize()
99    */
100   protected void finalize() throws Throwable {
101     V_parent = null;
102     __vorba = null;
103     vorbaId = null;
104     super.finalize();
105   }
106
107   /**
108    * 
109    */
110   protected Vobject() {
111     super();
112     testInstanceForIdField();
113   }
114
115   /**
116    * Override Object.hashCode with base value for castor generated object
117    * hashcodes.
118    */
119   public int hashCode() {
120     return 17;
121   }
122
123   java.lang.reflect.Field ___id_field = null; // set to ease pain of reflection
124
125   /**
126    * set the isRegisterable flag based on the presence of a 'private String _id'
127    * field in the reflected class instance.
128    */
129   private void testInstanceForIdField() {
130     // TODO: decide if 'id' is an appropriate reserved attribute name for the
131     // VorbaId
132     // look for the id field in all castor classes (should be an NCName string)
133
134     Class thisclass = this.getClass();
135     setRegisterable(false);
136     while (!thisclass.equals(Vobject.class)) {
137       try {
138         java.lang.reflect.Field fd = thisclass.getDeclaredField("_id");
139         if (String.class.isAssignableFrom(fd.getType())) {
140           ___id_field = fd;
141           this.setRegisterable(true);
142           break;
143         }
144       } catch (SecurityException e) {
145         log
146             .error(
147                 "Unexpected Security Exception whilst finding id fields to set!",
148                 e);
149       } catch (NoSuchFieldException e) {
150         thisclass = thisclass.getSuperclass();
151       }
152     }
153   }
154
155   // boolean __testedInstance=false;
156   /**
157    * update the Vobject instance's _id field, based on the contents of the
158    * VorbaId. Only call this if you mean to do it!
159    */
160   protected void setInstanceIdField() {
161     /*
162      * if (!registerable && !__testedInstance) { testInstanceForIdField();
163      * __testedInstance=true; }
164      */
165     if (registerable) {
166       if (__vorba != null)
167         try {
168           Method fd = this.getClass().getMethod("setId",
169               new Class[] { String.class });
170           fd.invoke((Object) this, new Object[] { new String(
171               this.getVorbaId().id) });
172           log.debug(this.getClass().getName() + " called setInstanceIdField!");
173         } catch (InvocationTargetException e) {
174           log
175               .error(
176                   "SourceGeneration of "
177                       + this.getClass().toString()
178                       + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.",
179                   e);
180         } catch (IllegalAccessException e) {
181           log
182               .error(
183                   "SourceGeneration of "
184                       + this.getClass().toString()
185                       + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.",
186                   e);
187         } catch (SecurityException e) {
188           log.error("Security access violation for "
189               + this.getClass().toString(), e);
190         } catch (NoSuchMethodException e) {
191           log
192               .warn(this.getClass().toString()
193                   + " was erroneously marked as a Vorba Vobject class (Implementation error?)");
194           this.setRegisterable(false);
195         }
196     } else {
197       System.err.println("Client error. Trying to setInstanceIdField on a "
198           + this.getClass().toString() + " (which cannot be given a vorbaId)");
199     }
200   }
201
202   protected String __getInstanceIdField() {
203     /*
204      * if (!registerable && !__testedInstance) { testInstanceForIdField();
205      * __testedInstance=true; }
206      */
207     if (registerable) {
208       if (__vorba != null)
209         try {
210           Method fd = this.getClass().getMethod("getId", (Class[]) null);
211           Object idstring = fd.invoke((Object) this, (Object[]) null);
212           log.debug(this.getClass().getName() + " called getInstanceIdField!");
213           if (idstring != null && idstring instanceof String) {
214             if (((String) idstring).length() > 0)
215               return (String) idstring;
216           }
217         } catch (InvocationTargetException e) {
218           log
219               .error(
220                   "SourceGeneration of "
221                       + this.getClass().toString()
222                       + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.",
223                   e);
224         } catch (IllegalAccessException e) {
225           log
226               .error(
227                   "SourceGeneration of "
228                       + this.getClass().toString()
229                       + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.",
230                   e);
231         } catch (SecurityException e) {
232           log.error("Security access violation for "
233               + this.getClass().toString(), e);
234         } catch (NoSuchMethodException e) {
235           log
236               .warn(this.getClass().toString()
237                   + " was erroneously marked as a Vorba Vobject class (Implementation error?)");
238           this.setRegisterable(false);
239         }
240     } else {
241       System.err.println("Client error. Trying to getInstanceIdField on a "
242           + this.getClass().toString() + " (which cannot be given a vorbaId)");
243     }
244     return null;
245   }
246
247   /**
248    * calls the castor-generated hashCode() method
249    * 
250    * @return
251    */
252   protected int __callHash() {
253     try {
254       Method fd = this.getClass().getMethod("hashCode", (Class[]) null);
255       Object hashvalue = fd.invoke((Object) this, (Object[]) null);
256       if (log.isDebugEnabled())
257         log.debug(this.getClass().getName() + " called hashCode()!");
258       if (hashvalue != null && hashvalue instanceof Integer) {
259         return ((Integer) hashvalue).intValue();
260       }
261     } catch (InvocationTargetException e) {
262       log
263           .error(
264               "SourceGeneration of "
265                   + this.getClass().toString()
266                   + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.",
267               e);
268     } catch (IllegalAccessException e) {
269       log
270           .error(
271               "SourceGeneration of "
272                   + this.getClass().toString()
273                   + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.",
274               e);
275     } catch (SecurityException e) {
276       log.error("Security access violation for " + this.getClass().toString(),
277           e);
278     } catch (NoSuchMethodException e) {
279       log
280           .warn(
281               this.getClass().toString()
282                   + " was erroneously extending from a Vorba Vobject class (Implementation error? no hashCode() method)"
283                   + "\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.",
284               e);
285     }
286     return 0;
287   }
288
289   /**
290    * calculate a hash for the Vobject with all housekeeping fields at standard
291    * values. (isRegisterable is an immutable attribute property) TODO: LATER:
292    * make this hash function compute a hash that truly reflects changes in
293    * Vobject attributes for benefit of update mechanism
294    * 
295    * @return true if new hash different to last hash (or first time its been
296    *         computed)
297    */
298   synchronized protected boolean doHash() {
299     boolean stored = __stored_in_document;
300     __stored_in_document = false;
301     boolean updated = __updated_since_last_read;
302     __updated_since_last_read = false;
303     boolean added_since = __added_since_last_read;
304     __added_since_last_read = false;
305     long __old_hash = __last_hash;
306     __last_hash = 0;
307     // leave registerable - doesn't change
308     boolean visited = __visited;
309     __visited = false;
310     Vobject _V_parent = V_parent;
311     V_parent = null;
312     VorbaId thisid = vorbaId;
313     vorbaId = null;
314     IVorbaIdFactory factory = __vorba;
315     __vorba = null;
316     java.lang.reflect.Field idfield = ___id_field;
317     ___id_field = null;
318     long l_hash = __l_hash;
319     __l_hash = 0;
320     // compute hash
321     __last_hash = __callHash();
322     // reset houseskeeping variables
323     ___id_field = idfield;
324     vorbaId = thisid;
325     __vorba = factory;
326     __stored_in_document = stored;
327     __updated_since_last_read = updated;
328     V_parent = _V_parent;
329     __visited = visited;
330     __added_since_last_read = added_since;
331     __l_hash = l_hash;
332     // return true if first time hash was computed or if hash has changed
333     return (__old_hash == 0) || (__old_hash != __last_hash);
334   }
335
336   /**
337    * TODO: combine two versions of the same collection Vobject to resolve
338    * asynchronous updates to the same vamsas Vobject Merges two vamsas objects,
339    * one of which is a later version of the earlier (ie they have the same
340    * vorbaId but one is a later version recently read from the vamsasDocument
341    * collection.
342    * 
343    * @return
344    */
345   protected boolean merge(Vobject laterCopy) {
346     log.warn(this.getClass().getName() + ".merge() not implemented.");
347     return true;
348   }
349
350   /**
351    * 
352    * @return true if Vobject is registered
353    */
354   public boolean isRegistered() {
355     return (registerable) ? (vorbaId != null) : false;
356   }
357
358   /**
359    * Method to get fixed reference for the Vobject in the vamsas document.
360    * 
361    * @returns null if Vobject is neither registered or not associated with a
362    *          properly instantiated VorbaIdFactory.
363    */
364   public VorbaId getVorbaId() {
365     if (registerable && vorbaId == null) {
366       if (this.__stored_in_document) {
367         if (__vorba != null)
368           vorbaId = uk.ac.vamsas.client.VorbaId.newId(this
369               .__getInstanceIdField());
370       }
371       // Try to use the associated factory.
372       if (__vorba != null)
373         if ((vorbaId = __vorba.makeVorbaId(this)) == null)
374           return null; // Factory not valid.
375         else {
376           this.setInstanceIdField();
377           return vorbaId;
378         }
379     }
380     return vorbaId;
381   }
382
383   /**
384    * used by the IClient implementation to generate unique Id based on client
385    * applications current namespace.
386    */
387   protected void setVorbaId(VorbaId newid) {
388     vorbaId = newid;
389   }
390
391   /**
392    * @return true if Vobject is present in Vamsas Document.
393    */
394   public boolean is__stored_in_document() {
395     return __stored_in_document;
396   }
397
398   /**
399    * @return true if this object has been updated in the currently stored
400    *         document since the last time a Vobject with the same ID was read
401    *         from a Vamsas Document
402    */
403   public boolean isUpdated() {
404     return __updated_since_last_read;
405   }
406
407   /**
408    * 
409    * @return true if this object was added to the document after the last time
410    *         the vamsas library acessed the session document
411    */
412   public boolean isNewInDocument() {
413     return __added_since_last_read;
414   }
415
416   /**
417    * Set internal flag to indicate this object was updated since the last
418    * document read
419    * 
420    * @param __updated_since_last_read
421    *          the __updated_since_last_read to set
422    */
423   protected void set__updated_since_last_read(boolean __updated_since_last_read) {
424     this.__updated_since_last_read = __updated_since_last_read;
425     if (__updated_since_last_read && log.isDebugEnabled())
426       log.debug("Registered update for " + this.getVorbaId());
427   }
428
429   /**
430    * for use by Vorba agent to reflect state of vamsas Vobject to client
431    * application. Setting stored_in_document on a registerable Vobject without a
432    * vorbaId will mean is will *never* get a vorbaId and horrible things will
433    * happen.
434    * 
435    * @param __stored_in_document
436    *          true if Vobject has been marshalled into current document.
437    */
438   protected void set__stored_in_document(boolean __stored_in_document) {
439     this.__stored_in_document = __stored_in_document;
440     if (__stored_in_document && log.isDebugEnabled())
441       log.debug("Retrieved document object: " + this.getVorbaId());
442   }
443
444   /**
445    * @param __added_since_last_read
446    *          the __added_since_last_read to set
447    */
448   protected void set__added_since_last_read(boolean __added_since_last_read) {
449     this.__added_since_last_read = __added_since_last_read;
450
451     if (__added_since_last_read && log.isDebugEnabled())
452       log.debug("New object in document: " + this.getVorbaId());
453   }
454
455   /**
456    * __last_hash is the hash value computed when the Vobject was last checked
457    * against a IClientDocument generated by the Vobject's parent IClient
458    * instance.
459    * 
460    * @return Returns the __last_hash.
461    */
462   public long get__last_hash() {
463     return __last_hash;
464   }
465
466   /**
467    * @return true if Vobject can have a vorbaId
468    */
469   public boolean isRegisterable() {
470     return registerable;
471   }
472
473   /**
474    * Called by __testInstanceForidField and the post-unmarshalling handler to
475    * indicate if Vobject will have a vorbaId.
476    * 
477    * @param registerable
478    */
479   protected void setRegisterable(boolean registerable) {
480     this.registerable = registerable;
481   }
482
483   /**
484    * ensure's internal id field corresponds to vorbaId and cascade through all
485    * fields referring to an instance of Vobject calling the same method on them.
486    * TODO: LATER: properly apply castors own field mechanisms to get at
487    * accessors TODO: FIX CYCLIC __ensure+instance_ids Implementation note for
488    * the todo: this works like a depth-first search over all vamsas objects in
489    * an vamsasDocument. __visited is the visited flag, any Vobj who's flag is of
490    * a different parity to the visited argument will be recursed on. note - the
491    * doHash() function used to be used as the 'visited' flag - this *is not* a
492    * valid heuristic, although it will work "most of the time". TODO: LATER? Add
493    * another method for setDefaultProvenanceField (in the spirit of
494    * setInstanceIdField) using the info from the __vorba.getClient/User/Session
495    * methods
496    */
497   protected void __ensure_instance_ids() {
498     __ensure_instance_ids(!__visited);
499   }
500
501   protected void __ensure_instance_ids(boolean visited) {
502     if (__vorba == null)
503       throw new Error(
504           "Improperly intialised uk.ac.vamsas.client.Vobject - no VorbaFactory given.");
505     log.debug("doing " + this.getClass() + ".__ensure_instance_ids()");
506     if (!__stored_in_document && registerable)
507       setInstanceIdField();
508     if (__visited == visited)
509       return;
510     __visited = visited;
511     // __vorba.updateHashValue(this);
512
513     Class descriptor = null;
514     XMLClassDescriptorImpl descimpl = null;
515     try {
516       // castor descriptor resolver magic
517       StringBuffer desname = new StringBuffer(this.getClass().getName());
518       desname.insert(desname.lastIndexOf("."), ".descriptors");
519       desname.append("Descriptor");
520       descriptor = this.getClass().getClassLoader().loadClass(
521           desname.toString());
522       descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor(
523           (Class[]) null).newInstance((Object[]) null);
524     } catch (Exception e) {
525       log.fatal("Source Generation Error!: Couldn't resolve descriptor for "
526           + this.getClass().getName()
527           + " was 'generate descriptors' set for castorbuilder.properties?");
528       return;
529     }
530     FieldDescriptor fields[] = descimpl.getFields();
531     for (int i = 0, j = fields.length; i < j; i++) {
532       Class type = fields[i].getFieldType();
533       if (type.isArray()) {
534         if (Vobject[].class.isAssignableFrom(type)) {
535           try {
536             Object val = fields[i].getHandler().getValue(this);
537             if (val != null) {
538               Vobject vals[] = (Vobject[]) val;
539               for (int k = 0, l = vals.length; k < l; k++) {
540                 if (vals[k].__vorba == null)
541                   vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
542                 if (vals[k].V_parent == null)
543                   vals[k].V_parent = this; // propagate parent reference to this
544                                            // element.
545                 vals[k].__ensure_instance_ids(visited);
546               }
547             }
548           } catch (Exception e) {
549             log.error("Client error - could not access array " + type.getName()
550                 + " in " + this.getClass().getName(), e);
551           }
552         }
553       } else if (Vobject.class.isAssignableFrom(type)) {
554         try {
555           FieldHandler fh = fields[i].getHandler();
556           Vobject rf = null;
557           if (fh != null) {
558             Object fval = fh.getValue(this);
559             if (fval != null) {
560               if (fval.getClass().isArray()) {
561                 // if (Vobject[].class.isAssignableFrom(type)) {
562                 try {
563                   Vobject vals[] = (Vobject[]) fval;
564                   for (int k = 0, l = vals.length; k < l; k++) {
565                     if (vals[k].__vorba == null)
566                       vals[k].__vorba = __vorba; // propagate IVorbaIdFactory
567                     if (vals[k].V_parent == null)
568                       vals[k].V_parent = this; // propagate parent reference to
569                                                // this field object
570                     vals[k].__ensure_instance_ids(visited);
571                   }
572                 } catch (Exception e) {
573                   log.error("Client error - could not access (fhval)array "
574                       + type.getName() + " in " + this.getClass().getName(), e);
575                 }
576                 // }
577               } else {
578                 rf = (Vobject) fval;
579                 log.debug("Got value for " + fields[i].getFieldName());
580               }
581             }
582           } else {
583             // castor's mechanism doesn't work for this object... so...*/
584             // fuck around, fuck around, jump up jump up and get down! */
585             Object o = fields[i].getClassDescriptor();
586             if (o != null) {
587               // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
588               String methname = "get" + fields[i].getFieldName();
589               Method fgetmeth = this.getClass().getMethod(methname,
590                   (Class[]) null);
591               if (fgetmeth != null) {
592                 Object fval = fgetmeth.invoke(this, (Object[]) null);
593                 if (fval != null)
594                   rf = (Vobject) fval;
595               } else {
596                 log.warn("Couldn't find " + this.getClass().getName() + "."
597                     + methname);
598               }
599             }
600           }
601           if (rf != null) {
602             if (rf.__vorba == null)
603               rf.__vorba = __vorba; // propagate IVorbaIdFactory
604             if (rf.V_parent == null)
605               rf.V_parent = this; // propagate parent reference
606             rf.__ensure_instance_ids(visited);
607           }
608         } catch (Exception e) {
609           log.error("Client error - could not access " + type.getName()
610               + " in " + this.getClass().getName(), e);
611         }
612       }
613     }
614
615   }
616
617   /**
618    * @return the __parent
619    */
620   public Vobject getV_parent() {
621     return V_parent;
622   }
623
624   /**
625    * @param __parent
626    *          the __parent to set
627    */
628   protected void setV_parent(Vobject V_parent) {
629     this.V_parent = V_parent;
630   }
631
632   /**
633    * LhashValue - used for change detection between document updates.
634    */
635   private long __l_hash = 0;
636
637   /**
638    * set the base LhashValue for this object
639    * 
640    * @param checksum
641    */
642   protected void __setInitHash(long checksum) {
643     __l_hash = checksum;
644   }
645
646   /**
647    * compute the final LhashValue as a difference between checksum and the
648    * current base
649    * 
650    * @param checksum
651    */
652   protected void __setFinalHash(long checksum) {
653     __l_hash = checksum - __l_hash;
654   }
655
656   /**
657    * get the LhashValue for this object
658    * 
659    * @return the difference in values passed to __setFinalHash less
660    *         __setInitHash
661    */
662   protected long __getLHash() {
663     return __l_hash;
664   }
665 }