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.
6 * Earlier versions have also been incorporated into Jalview version 2.4
7 * since 2008, and TOPALi version 2 since 2007.
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.
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.
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/>.
22 package uk.ac.vamsas.client;
24 import java.lang.reflect.Field;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.Iterator;
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;
35 import uk.ac.vamsas.test.simpleclient.VamsasArchive;
38 * Base class for all Vamsas objects extracted from an IClientDocument. An
39 * Vobject maybe registered or unregistered.
44 public abstract class Vobject {
45 static Log log = LogFactory.getLog(Vobject.class);
48 * true if Vobject was stored in a vamsas Document or has been retrieved from
51 protected boolean __stored_in_document = false;
54 * true if Vobject was updated since the vamsas library last read a Vobj with
55 * the same VorbaId from a document.
57 protected boolean __updated_since_last_read = false;
60 * true if Vobject appeared in the document after the last access by this
61 * vamsas library instance
63 protected boolean __added_since_last_read = false;
66 * memory of the last doHash() value computed for the Vobject
70 protected int __last_hash = 0;
73 * set by testInstanceForIdField() if Vobject should have a VorbaId
75 protected boolean registerable = false;
77 protected boolean __visited = false;
80 * reference to containing object for this Vobject.
82 protected Vobject V_parent = null;
85 * unique id for all vamsas objects allows unambiguous referencing to any
86 * Vobject in the vamsas document
88 protected VorbaId vorbaId = null;
91 * the source of unique VorbaIds.
93 protected IVorbaIdFactory __vorba = null;
98 * @see java.lang.Object#finalize()
100 protected void finalize() throws Throwable {
110 protected Vobject() {
112 testInstanceForIdField();
116 * Override Object.hashCode with base value for castor generated object
119 public int hashCode() {
123 java.lang.reflect.Field ___id_field = null; // set to ease pain of reflection
126 * set the isRegisterable flag based on the presence of a 'private String _id'
127 * field in the reflected class instance.
129 private void testInstanceForIdField() {
130 // TODO: decide if 'id' is an appropriate reserved attribute name for the
132 // look for the id field in all castor classes (should be an NCName string)
134 Class thisclass = this.getClass();
135 setRegisterable(false);
136 while (!thisclass.equals(Vobject.class)) {
138 java.lang.reflect.Field fd = thisclass.getDeclaredField("_id");
139 if (String.class.isAssignableFrom(fd.getType())) {
141 this.setRegisterable(true);
144 } catch (SecurityException e) {
147 "Unexpected Security Exception whilst finding id fields to set!",
149 } catch (NoSuchFieldException e) {
150 thisclass = thisclass.getSuperclass();
155 // boolean __testedInstance=false;
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!
160 protected void setInstanceIdField() {
162 * if (!registerable && !__testedInstance) { testInstanceForIdField();
163 * __testedInstance=true; }
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) {
176 "SourceGeneration of "
177 + this.getClass().toString()
178 + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.",
180 } catch (IllegalAccessException e) {
183 "SourceGeneration of "
184 + this.getClass().toString()
185 + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.",
187 } catch (SecurityException e) {
188 log.error("Security access violation for "
189 + this.getClass().toString(), e);
190 } catch (NoSuchMethodException e) {
192 .warn(this.getClass().toString()
193 + " was erroneously marked as a Vorba Vobject class (Implementation error?)");
194 this.setRegisterable(false);
197 System.err.println("Client error. Trying to setInstanceIdField on a "
198 + this.getClass().toString() + " (which cannot be given a vorbaId)");
202 protected String __getInstanceIdField() {
204 * if (!registerable && !__testedInstance) { testInstanceForIdField();
205 * __testedInstance=true; }
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;
217 } catch (InvocationTargetException e) {
220 "SourceGeneration of "
221 + this.getClass().toString()
222 + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.",
224 } catch (IllegalAccessException e) {
227 "SourceGeneration of "
228 + this.getClass().toString()
229 + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.",
231 } catch (SecurityException e) {
232 log.error("Security access violation for "
233 + this.getClass().toString(), e);
234 } catch (NoSuchMethodException e) {
236 .warn(this.getClass().toString()
237 + " was erroneously marked as a Vorba Vobject class (Implementation error?)");
238 this.setRegisterable(false);
241 System.err.println("Client error. Trying to getInstanceIdField on a "
242 + this.getClass().toString() + " (which cannot be given a vorbaId)");
248 * calls the castor-generated hashCode() method
252 protected int __callHash() {
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();
261 } catch (InvocationTargetException e) {
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 ?.",
268 } catch (IllegalAccessException e) {
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 ?.",
275 } catch (SecurityException e) {
276 log.error("Security access violation for " + this.getClass().toString(),
278 } catch (NoSuchMethodException e) {
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 ?.",
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
295 * @return true if new hash different to last hash (or first time its been
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;
307 // leave registerable - doesn't change
308 boolean visited = __visited;
310 Vobject _V_parent = V_parent;
312 VorbaId thisid = vorbaId;
314 IVorbaIdFactory factory = __vorba;
316 java.lang.reflect.Field idfield = ___id_field;
318 long l_hash = __l_hash;
321 __last_hash = __callHash();
322 // reset houseskeeping variables
323 ___id_field = idfield;
326 __stored_in_document = stored;
327 __updated_since_last_read = updated;
328 V_parent = _V_parent;
330 __added_since_last_read = added_since;
332 // return true if first time hash was computed or if hash has changed
333 return (__old_hash == 0) || (__old_hash != __last_hash);
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
345 protected boolean merge(Vobject laterCopy) {
346 log.warn(this.getClass().getName() + ".merge() not implemented.");
352 * @return true if Vobject is registered
354 public boolean isRegistered() {
355 return (registerable) ? (vorbaId != null) : false;
359 * Method to get fixed reference for the Vobject in the vamsas document.
361 * @returns null if Vobject is neither registered or not associated with a
362 * properly instantiated VorbaIdFactory.
364 public VorbaId getVorbaId() {
365 if (registerable && vorbaId == null) {
366 if (this.__stored_in_document) {
368 vorbaId = uk.ac.vamsas.client.VorbaId.newId(this
369 .__getInstanceIdField());
371 // Try to use the associated factory.
373 if ((vorbaId = __vorba.makeVorbaId(this)) == null)
374 return null; // Factory not valid.
376 this.setInstanceIdField();
384 * used by the IClient implementation to generate unique Id based on client
385 * applications current namespace.
387 protected void setVorbaId(VorbaId newid) {
392 * @return true if Vobject is present in Vamsas Document.
394 public boolean is__stored_in_document() {
395 return __stored_in_document;
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
403 public boolean isUpdated() {
404 return __updated_since_last_read;
409 * @return true if this object was added to the document after the last time
410 * the vamsas library acessed the session document
412 public boolean isNewInDocument() {
413 return __added_since_last_read;
417 * Set internal flag to indicate this object was updated since the last
420 * @param __updated_since_last_read
421 * the __updated_since_last_read to set
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());
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
435 * @param __stored_in_document
436 * true if Vobject has been marshalled into current document.
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());
445 * @param __added_since_last_read
446 * the __added_since_last_read to set
448 protected void set__added_since_last_read(boolean __added_since_last_read) {
449 this.__added_since_last_read = __added_since_last_read;
451 if (__added_since_last_read && log.isDebugEnabled())
452 log.debug("New object in document: " + this.getVorbaId());
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
460 * @return Returns the __last_hash.
462 public long get__last_hash() {
467 * @return true if Vobject can have a vorbaId
469 public boolean isRegisterable() {
474 * Called by __testInstanceForidField and the post-unmarshalling handler to
475 * indicate if Vobject will have a vorbaId.
477 * @param registerable
479 protected void setRegisterable(boolean registerable) {
480 this.registerable = registerable;
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
497 protected void __ensure_instance_ids() {
498 __ensure_instance_ids(!__visited);
501 protected void __ensure_instance_ids(boolean visited) {
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)
511 // __vorba.updateHashValue(this);
513 Class descriptor = null;
514 XMLClassDescriptorImpl descimpl = null;
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(
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?");
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)) {
536 Object val = fields[i].getHandler().getValue(this);
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
545 vals[k].__ensure_instance_ids(visited);
548 } catch (Exception e) {
549 log.error("Client error - could not access array " + type.getName()
550 + " in " + this.getClass().getName(), e);
553 } else if (Vobject.class.isAssignableFrom(type)) {
555 FieldHandler fh = fields[i].getHandler();
558 Object fval = fh.getValue(this);
560 if (fval.getClass().isArray()) {
561 // if (Vobject[].class.isAssignableFrom(type)) {
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
570 vals[k].__ensure_instance_ids(visited);
572 } catch (Exception e) {
573 log.error("Client error - could not access (fhval)array "
574 + type.getName() + " in " + this.getClass().getName(), e);
579 log.debug("Got value for " + fields[i].getFieldName());
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();
587 // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o;
588 String methname = "get" + fields[i].getFieldName();
589 Method fgetmeth = this.getClass().getMethod(methname,
591 if (fgetmeth != null) {
592 Object fval = fgetmeth.invoke(this, (Object[]) null);
596 log.warn("Couldn't find " + this.getClass().getName() + "."
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);
608 } catch (Exception e) {
609 log.error("Client error - could not access " + type.getName()
610 + " in " + this.getClass().getName(), e);
618 * @return the __parent
620 public Vobject getV_parent() {
626 * the __parent to set
628 protected void setV_parent(Vobject V_parent) {
629 this.V_parent = V_parent;
633 * LhashValue - used for change detection between document updates.
635 private long __l_hash = 0;
638 * set the base LhashValue for this object
642 protected void __setInitHash(long checksum) {
647 * compute the final LhashValue as a difference between checksum and the
652 protected void __setFinalHash(long checksum) {
653 __l_hash = checksum - __l_hash;
657 * get the LhashValue for this object
659 * @return the difference in values passed to __setFinalHash less
662 protected long __getLHash() {