/* * This file is part of the Vamsas Client version 0.2. * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, * Andrew Waterhouse and Dominik Lindner. * * Earlier versions have also been incorporated into Jalview version 2.4 * since 2008, and TOPALi version 2 since 2007. * * The Vamsas Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Vamsas Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Vamsas Client. If not, see . */ package uk.ac.vamsas.client; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.xml.util.XMLClassDescriptorImpl; import uk.ac.vamsas.test.simpleclient.VamsasArchive; /** * Base class for all Vamsas objects extracted from an IClientDocument. An * Vobject maybe registered or unregistered. * * @author jimp * */ public abstract class Vobject { static Log log = LogFactory.getLog(Vobject.class); /** * true if Vobject was stored in a vamsas Document or has been retrieved from * it */ protected boolean __stored_in_document = false; /** * true if Vobject was updated since the vamsas library last read a Vobj with * the same VorbaId from a document. */ protected boolean __updated_since_last_read = false; /** * true if Vobject appeared in the document after the last access by this * vamsas library instance */ protected boolean __added_since_last_read = false; /** * memory of the last doHash() value computed for the Vobject * * @see doHash() */ protected int __last_hash = 0; /** * set by testInstanceForIdField() if Vobject should have a VorbaId */ protected boolean registerable = false; protected boolean __visited = false; /** * reference to containing object for this Vobject. */ protected Vobject V_parent = null; /** * unique id for all vamsas objects allows unambiguous referencing to any * Vobject in the vamsas document */ protected VorbaId vorbaId = null; /** * the source of unique VorbaIds. */ protected IVorbaIdFactory __vorba = null; /* * (non-Javadoc) * * @see java.lang.Object#finalize() */ protected void finalize() throws Throwable { V_parent = null; __vorba = null; vorbaId = null; super.finalize(); } /** * */ protected Vobject() { super(); testInstanceForIdField(); } /** * Override Object.hashCode with base value for castor generated object * hashcodes. */ public int hashCode() { return 17; } java.lang.reflect.Field ___id_field = null; // set to ease pain of reflection /** * set the isRegisterable flag based on the presence of a 'private String _id' * field in the reflected class instance. */ private void testInstanceForIdField() { // TODO: decide if 'id' is an appropriate reserved attribute name for the // VorbaId // look for the id field in all castor classes (should be an NCName string) Class thisclass = this.getClass(); setRegisterable(false); while (!thisclass.equals(Vobject.class)) { try { java.lang.reflect.Field fd = thisclass.getDeclaredField("_id"); if (String.class.isAssignableFrom(fd.getType())) { ___id_field = fd; this.setRegisterable(true); break; } } catch (SecurityException e) { log .error( "Unexpected Security Exception whilst finding id fields to set!", e); } catch (NoSuchFieldException e) { thisclass = thisclass.getSuperclass(); } } } // boolean __testedInstance=false; /** * update the Vobject instance's _id field, based on the contents of the * VorbaId. Only call this if you mean to do it! */ protected void setInstanceIdField() { /* * if (!registerable && !__testedInstance) { testInstanceForIdField(); * __testedInstance=true; } */ if (registerable) { if (__vorba != null) try { Method fd = this.getClass().getMethod("setId", new Class[] { String.class }); fd.invoke((Object) this, new Object[] { new String( this.getVorbaId().id) }); log.debug(this.getClass().getName() + " called setInstanceIdField!"); } catch (InvocationTargetException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e); } catch (IllegalAccessException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'setId' method!\nCannot set ID from the vorbaId Vobject.", e); } catch (SecurityException e) { log.error("Security access violation for " + this.getClass().toString(), e); } catch (NoSuchMethodException e) { log .warn(this.getClass().toString() + " was erroneously marked as a Vorba Vobject class (Implementation error?)"); this.setRegisterable(false); } } else { System.err.println("Client error. Trying to setInstanceIdField on a " + this.getClass().toString() + " (which cannot be given a vorbaId)"); } } protected String __getInstanceIdField() { /* * if (!registerable && !__testedInstance) { testInstanceForIdField(); * __testedInstance=true; } */ if (registerable) { if (__vorba != null) try { Method fd = this.getClass().getMethod("getId", (Class[]) null); Object idstring = fd.invoke((Object) this, (Object[]) null); log.debug(this.getClass().getName() + " called getInstanceIdField!"); if (idstring != null && idstring instanceof String) { if (((String) idstring).length() > 0) return (String) idstring; } } catch (InvocationTargetException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e); } catch (IllegalAccessException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'getId' method!\nCannot set ID from the vorbaId Vobject.", e); } catch (SecurityException e) { log.error("Security access violation for " + this.getClass().toString(), e); } catch (NoSuchMethodException e) { log .warn(this.getClass().toString() + " was erroneously marked as a Vorba Vobject class (Implementation error?)"); this.setRegisterable(false); } } else { System.err.println("Client error. Trying to getInstanceIdField on a " + this.getClass().toString() + " (which cannot be given a vorbaId)"); } return null; } /** * calls the castor-generated hashCode() method * * @return */ protected int __callHash() { try { Method fd = this.getClass().getMethod("hashCode", (Class[]) null); Object hashvalue = fd.invoke((Object) this, (Object[]) null); if (log.isDebugEnabled()) log.debug(this.getClass().getName() + " called hashCode()!"); if (hashvalue != null && hashvalue instanceof Integer) { return ((Integer) hashvalue).intValue(); } } catch (InvocationTargetException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e); } catch (IllegalAccessException e) { log .error( "SourceGeneration of " + this.getClass().toString() + "\n has resulted in an inaccessible 'hashCode' method!\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e); } catch (SecurityException e) { log.error("Security access violation for " + this.getClass().toString(), e); } catch (NoSuchMethodException e) { log .warn( this.getClass().toString() + " was erroneously extending from a Vorba Vobject class (Implementation error? no hashCode() method)" + "\nHave you set org.exolab.castor.builder.equalsmethod=true in castorbuilder.properties ?.", e); } return 0; } /** * calculate a hash for the Vobject with all housekeeping fields at standard * values. (isRegisterable is an immutable attribute property) TODO: LATER: * make this hash function compute a hash that truly reflects changes in * Vobject attributes for benefit of update mechanism * * @return true if new hash different to last hash (or first time its been * computed) */ synchronized protected boolean doHash() { boolean stored = __stored_in_document; __stored_in_document = false; boolean updated = __updated_since_last_read; __updated_since_last_read = false; boolean added_since = __added_since_last_read; __added_since_last_read = false; long __old_hash = __last_hash; __last_hash = 0; // leave registerable - doesn't change boolean visited = __visited; __visited = false; Vobject _V_parent = V_parent; V_parent = null; VorbaId thisid = vorbaId; vorbaId = null; IVorbaIdFactory factory = __vorba; __vorba = null; java.lang.reflect.Field idfield = ___id_field; ___id_field = null; long l_hash = __l_hash; __l_hash = 0; // compute hash __last_hash = __callHash(); // reset houseskeeping variables ___id_field = idfield; vorbaId = thisid; __vorba = factory; __stored_in_document = stored; __updated_since_last_read = updated; V_parent = _V_parent; __visited = visited; __added_since_last_read = added_since; __l_hash = l_hash; // return true if first time hash was computed or if hash has changed return (__old_hash == 0) || (__old_hash != __last_hash); } /** * TODO: combine two versions of the same collection Vobject to resolve * asynchronous updates to the same vamsas Vobject Merges two vamsas objects, * one of which is a later version of the earlier (ie they have the same * vorbaId but one is a later version recently read from the vamsasDocument * collection. * * @return */ protected boolean merge(Vobject laterCopy) { log.warn(this.getClass().getName() + ".merge() not implemented."); return true; } /** * * @return true if Vobject is registered */ public boolean isRegistered() { return (registerable) ? (vorbaId != null) : false; } /** * Method to get fixed reference for the Vobject in the vamsas document. * * @returns null if Vobject is neither registered or not associated with a * properly instantiated VorbaIdFactory. */ public VorbaId getVorbaId() { if (registerable && vorbaId == null) { if (this.__stored_in_document) { if (__vorba != null) vorbaId = uk.ac.vamsas.client.VorbaId.newId(this .__getInstanceIdField()); } // Try to use the associated factory. if (__vorba != null) if ((vorbaId = __vorba.makeVorbaId(this)) == null) return null; // Factory not valid. else { this.setInstanceIdField(); return vorbaId; } } return vorbaId; } /** * used by the IClient implementation to generate unique Id based on client * applications current namespace. */ protected void setVorbaId(VorbaId newid) { vorbaId = newid; } /** * @return true if Vobject is present in Vamsas Document. */ public boolean is__stored_in_document() { return __stored_in_document; } /** * @return true if this object has been updated in the currently stored * document since the last time a Vobject with the same ID was read * from a Vamsas Document */ public boolean isUpdated() { return __updated_since_last_read; } /** * * @return true if this object was added to the document after the last time * the vamsas library acessed the session document */ public boolean isNewInDocument() { return __added_since_last_read; } /** * Set internal flag to indicate this object was updated since the last * document read * * @param __updated_since_last_read * the __updated_since_last_read to set */ protected void set__updated_since_last_read(boolean __updated_since_last_read) { this.__updated_since_last_read = __updated_since_last_read; if (__updated_since_last_read && log.isDebugEnabled()) log.debug("Registered update for " + this.getVorbaId()); } /** * for use by Vorba agent to reflect state of vamsas Vobject to client * application. Setting stored_in_document on a registerable Vobject without a * vorbaId will mean is will *never* get a vorbaId and horrible things will * happen. * * @param __stored_in_document * true if Vobject has been marshalled into current document. */ protected void set__stored_in_document(boolean __stored_in_document) { this.__stored_in_document = __stored_in_document; if (__stored_in_document && log.isDebugEnabled()) log.debug("Retrieved document object: " + this.getVorbaId()); } /** * @param __added_since_last_read * the __added_since_last_read to set */ protected void set__added_since_last_read(boolean __added_since_last_read) { this.__added_since_last_read = __added_since_last_read; if (__added_since_last_read && log.isDebugEnabled()) log.debug("New object in document: " + this.getVorbaId()); } /** * __last_hash is the hash value computed when the Vobject was last checked * against a IClientDocument generated by the Vobject's parent IClient * instance. * * @return Returns the __last_hash. */ public long get__last_hash() { return __last_hash; } /** * @return true if Vobject can have a vorbaId */ public boolean isRegisterable() { return registerable; } /** * Called by __testInstanceForidField and the post-unmarshalling handler to * indicate if Vobject will have a vorbaId. * * @param registerable */ protected void setRegisterable(boolean registerable) { this.registerable = registerable; } /** * ensure's internal id field corresponds to vorbaId and cascade through all * fields referring to an instance of Vobject calling the same method on them. * TODO: LATER: properly apply castors own field mechanisms to get at * accessors TODO: FIX CYCLIC __ensure+instance_ids Implementation note for * the todo: this works like a depth-first search over all vamsas objects in * an vamsasDocument. __visited is the visited flag, any Vobj who's flag is of * a different parity to the visited argument will be recursed on. note - the * doHash() function used to be used as the 'visited' flag - this *is not* a * valid heuristic, although it will work "most of the time". TODO: LATER? Add * another method for setDefaultProvenanceField (in the spirit of * setInstanceIdField) using the info from the __vorba.getClient/User/Session * methods */ protected void __ensure_instance_ids() { __ensure_instance_ids(!__visited); } protected void __ensure_instance_ids(boolean visited) { if (__vorba == null) throw new Error( "Improperly intialised uk.ac.vamsas.client.Vobject - no VorbaFactory given."); log.debug("doing " + this.getClass() + ".__ensure_instance_ids()"); if (!__stored_in_document && registerable) setInstanceIdField(); if (__visited == visited) return; __visited = visited; // __vorba.updateHashValue(this); Class descriptor = null; XMLClassDescriptorImpl descimpl = null; try { // castor descriptor resolver magic StringBuffer desname = new StringBuffer(this.getClass().getName()); desname.insert(desname.lastIndexOf("."), ".descriptors"); desname.append("Descriptor"); descriptor = this.getClass().getClassLoader().loadClass( desname.toString()); descimpl = (XMLClassDescriptorImpl) descriptor.getConstructor( (Class[]) null).newInstance((Object[]) null); } catch (Exception e) { log.fatal("Source Generation Error!: Couldn't resolve descriptor for " + this.getClass().getName() + " was 'generate descriptors' set for castorbuilder.properties?"); return; } FieldDescriptor fields[] = descimpl.getFields(); for (int i = 0, j = fields.length; i < j; i++) { Class type = fields[i].getFieldType(); if (type.isArray()) { if (Vobject[].class.isAssignableFrom(type)) { try { Object val = fields[i].getHandler().getValue(this); if (val != null) { Vobject vals[] = (Vobject[]) val; for (int k = 0, l = vals.length; k < l; k++) { if (vals[k].__vorba == null) vals[k].__vorba = __vorba; // propagate IVorbaIdFactory if (vals[k].V_parent == null) vals[k].V_parent = this; // propagate parent reference to this // element. vals[k].__ensure_instance_ids(visited); } } } catch (Exception e) { log.error("Client error - could not access array " + type.getName() + " in " + this.getClass().getName(), e); } } } else if (Vobject.class.isAssignableFrom(type)) { try { FieldHandler fh = fields[i].getHandler(); Vobject rf = null; if (fh != null) { Object fval = fh.getValue(this); if (fval != null) { if (fval.getClass().isArray()) { // if (Vobject[].class.isAssignableFrom(type)) { try { Vobject vals[] = (Vobject[]) fval; for (int k = 0, l = vals.length; k < l; k++) { if (vals[k].__vorba == null) vals[k].__vorba = __vorba; // propagate IVorbaIdFactory if (vals[k].V_parent == null) vals[k].V_parent = this; // propagate parent reference to // this field object vals[k].__ensure_instance_ids(visited); } } catch (Exception e) { log.error("Client error - could not access (fhval)array " + type.getName() + " in " + this.getClass().getName(), e); } // } } else { rf = (Vobject) fval; log.debug("Got value for " + fields[i].getFieldName()); } } } else { // castor's mechanism doesn't work for this object... so...*/ // fuck around, fuck around, jump up jump up and get down! */ Object o = fields[i].getClassDescriptor(); if (o != null) { // XMLClassDescriptorImpl fclasdes = (XMLClassDescriptorImpl) o; String methname = "get" + fields[i].getFieldName(); Method fgetmeth = this.getClass().getMethod(methname, (Class[]) null); if (fgetmeth != null) { Object fval = fgetmeth.invoke(this, (Object[]) null); if (fval != null) rf = (Vobject) fval; } else { log.warn("Couldn't find " + this.getClass().getName() + "." + methname); } } } if (rf != null) { if (rf.__vorba == null) rf.__vorba = __vorba; // propagate IVorbaIdFactory if (rf.V_parent == null) rf.V_parent = this; // propagate parent reference rf.__ensure_instance_ids(visited); } } catch (Exception e) { log.error("Client error - could not access " + type.getName() + " in " + this.getClass().getName(), e); } } } } /** * @return the __parent */ public Vobject getV_parent() { return V_parent; } /** * @param __parent * the __parent to set */ protected void setV_parent(Vobject V_parent) { this.V_parent = V_parent; } /** * LhashValue - used for change detection between document updates. */ private long __l_hash = 0; /** * set the base LhashValue for this object * * @param checksum */ protected void __setInitHash(long checksum) { __l_hash = checksum; } /** * compute the final LhashValue as a difference between checksum and the * current base * * @param checksum */ protected void __setFinalHash(long checksum) { __l_hash = checksum - __l_hash; } /** * get the LhashValue for this object * * @return the difference in values passed to __setFinalHash less * __setInitHash */ protected long __getLHash() { return __l_hash; } }