/* * ObjectRecorder, a class that takes records of an objects state using * reflection. * Copyright (c) 2004 - 2011 Achim Westermann, Achim.Westermann@gmx.de. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * If you modify or optimize the code in a useful way please let me know. * Achim.Westermann@gmx.de */ package info.monitorenter.reflection; import info.monitorenter.util.TimeStampedValue; import info.monitorenter.util.collections.IRingBuffer; import info.monitorenter.util.collections.RingBufferArrayFast; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.LinkedList; import javax.naming.directory.NoSuchAttributeException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; /** * The <code>ObjectRecorder</code> takes records(inspections) of an objects * state using reflection and accessibility- framework. * <p> * * It's strategy is to: <br/> * * <pre> * - try to set any field accessible. * - try to get the value of the field. * - if not succeed: try to invoke a bean- conform getter. * - if NoSuchMethod, it's useless (no implementation of MagicClazz here). * </pre> * * <p> * * Furthermore the <code>ObjectRecorder</code> has a history - size (buffer) and * an adjustable distance between each inspection. * <p> * * @author <a href='mailto:Achim.Westermann@gmx.de'>Achim Westermann </a> * * @version $Revision: 1.10 $ */ public class ObjectRecorder extends Thread { /** * Data container for the inspection of the internal intance. * <p> * * @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a> * * * @version $Revision: 1.10 $ */ public final class ObjectInspection { /** Time stamp of the inspection. */ protected long m_time; /** The values taken on the inspection. */ private final LinkedList<Object> m_values; /** * Creates an instance linked to the outer recorder. * <p> * */ protected ObjectInspection() { this.m_time = new java.util.Date().getTime(); this.m_values = new LinkedList<Object>(); } /** * Adds an inspected value to this inspection. * <p> * * @param value * an inspected value of this inspection. */ protected void add(final Object value) { this.m_values.add(value); } /** * Get the value for the attribute at the given index. * <p> * * @param index * the index of the inspected value according to the order it was * found on the instance by {@link Class#getDeclaredFields()}. * <p> * * @return the value for the attribute at the given index. */ public Object get(final int index) { return this.m_values.get(index); } /** * Returns the time stamp in ms of this inspection. * <p> * * @return the time stamp in ms of this inspection. */ public long getTime() { return this.m_time; } /** * Removes the inspected value from this inspection. * <p> * * The value is identified by means of * {@link Object#equals(java.lang.Object)}. * <p> * * @param value * the inspected value from this inspection. */ protected void remove(final Object value) { this.m_values.remove(value); } /** * Returns a pretty print of this inspection. * <p> * * @return a pretty print of this inspection. * * @see java.lang.Object#toString() */ @Override public String toString() { final StringBuffer ret = new StringBuffer("\nObjectInspection:\n"); ret.append("-----------------\n"); ret.append("Inspected: ").append(ObjectRecorder.this.getInspected().toString()).append("\n"); ret.append("time: ").append(this.m_time).append("\n"); for (int i = ObjectRecorder.this.m_fields.length - 1; i >= 0; i--) { ret.append(ObjectRecorder.this.m_fields[i].getName()).append(": ").append( this.m_values.get(i).toString()).append("\n"); } return ret.toString(); } } /** Verbosity constant. */ protected static final boolean VERBOSE = false; /** Fast buffer to store recorded fiels. */ protected IRingBuffer<ObjectRecorder.ObjectInspection> m_buffer = new RingBufferArrayFast<ObjectRecorder.ObjectInspection>( 100); /** The listeners on this recorder. */ protected EventListenerList m_changeListeners = new EventListenerList(); /** The fields to inspect on the instance. */ protected Field[] m_fields; /** * The time - interval between to inspections of the Object. */ protected long m_interval; /** The instance to inspect. */ protected Object m_toinspect; /** * Creates an instance that will inspect the given Object in the given time * interval. * <p> * * @param toinspect * the instance to inspect. * * @param interval * the interval of inspection in ms. */ public ObjectRecorder(final Object toinspect, final long interval) { this.m_interval = interval; this.m_toinspect = toinspect; this.setDaemon(true); // getting the field names. this.m_fields = toinspect.getClass().getDeclaredFields(); this.start(); } /** * Adds a change listener that will be informed about new recordings of the * inspected instances. * <p> * * @param x * the change listener that will be informed about new recordings of * the inspected instances. */ public void addChangeListener(final ChangeListener x) { this.m_changeListeners.add(ChangeListener.class, x); // x.stateChanged(new ChangeEvent(this)); // Aufruf des neuen // ChangeListeners um zu aktualisieren. } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } final ObjectRecorder other = (ObjectRecorder) obj; if (this.m_buffer == null) { if (other.m_buffer != null) { return false; } } else if (!this.m_buffer.equals(other.m_buffer)) { return false; } if (this.m_changeListeners == null) { if (other.m_changeListeners != null) { return false; } } else if (!this.m_changeListeners.equals(other.m_changeListeners)) { return false; } if (!Arrays.equals(this.m_fields, other.m_fields)) { return false; } if (this.m_interval != other.m_interval) { return false; } if (this.m_toinspect == null) { if (other.m_toinspect != null) { return false; } } else if (!this.m_toinspect.equals(other.m_toinspect)) { return false; } return true; } /** * Informs the listeners about a change of this instance. * <p> * */ protected void fireChange() { final ChangeEvent ce = new ChangeEvent(this); final Object[] listeners = this.m_changeListeners.getListenerList(); for (int i = listeners.length - 1; i >= 0; i -= 2) { final ChangeListener cl = (ChangeListener) listeners[i]; cl.stateChanged(ce); } } /** * The History returned by this Method represents the past of the field * specified by attributeName. It starts from low index with the newest values * taken from the inspected Object and ends with the oldest. * * @param attributeName * field name of the internal instance to inspect. * * @return An array filled with TimeStampedValues that represent the past of * the last inspections of the field with attributeName. * * @throws NoSuchAttributeException * if the attribute / field described by the given argument does not * exist on the internal Object to instpect. * * @see ObjectRecorder#getInspected() */ public TimeStampedValue[] getAttributeHistory(final String attributeName) throws NoSuchAttributeException { // search for the field int attribindex = -1; for (int i = this.m_fields.length - 1; i >= 0; i--) { if (this.m_fields[i].getName().equals(attributeName)) { attribindex = i; break; } } if (attribindex == -1) { throw new NoSuchAttributeException("The Attribute with the name: " + attributeName + " does not exist in " + this.m_toinspect.getClass().getName()); } final int stop = this.m_buffer.size(); final TimeStampedValue[] ret = new TimeStampedValue[stop]; synchronized (this.m_buffer) { for (final ObjectInspection tmp : this.m_buffer) { int i = 0; ret[i++] = new TimeStampedValue(tmp.getTime(), tmp.get(attribindex)); } } return ret; } /** * Returns the names of the fields to inspect. * <p> * * @return the names of the fields to inspect. */ public String[] getAttributeNames() { final String[] ret = new String[this.m_fields.length]; for (int i = 0; i < this.m_fields.length; i++) { ret[i] = this.m_fields[i].getName(); } return ret; } /** * Returns the inspected instance. * <p> * * @return the inspected instance. */ public Object getInspected() { return this.m_toinspect; } /** * Returns the last recorded value taken from the given field along with the * time stamp identifying the time this value was recored. * <p> * * @param fieldname * the field whose value was recorded. * * @return the last recorded value taken from the given field along with the * time stamp identifying the time this value was recored. * * @throws NoSuchAttributeException * if no such field exists on the Object to inspect. * */ public TimeStampedValue getLastValue(final String fieldname) throws NoSuchAttributeException { // search for the field int attribindex = -1; for (int i = this.m_fields.length - 1; i >= 0; i--) { if (this.m_fields[i].getName().equals(fieldname)) { attribindex = i; break; } } if (attribindex == -1) { throw new NoSuchAttributeException("The Attribute with the name: " + fieldname + " does not exist in " + this.m_toinspect.getClass().getName()); } final ObjectInspection tmp = this.m_buffer.getYoungest(); return new TimeStampedValue(tmp.getTime(), tmp.get(attribindex)); } /** * Returns the internal fifo buffer that stores the * {@link ObjectRecorder.ObjectInspection} instances that have been done. * <p> * * @return the internal fifo buffer that stores the * {@link ObjectRecorder.ObjectInspection} instances that have been * done. */ public IRingBuffer<ObjectRecorder.ObjectInspection> getRingBuffer() { return this.m_buffer; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.m_buffer == null) ? 0 : this.m_buffer.hashCode()); result = prime * result + ((this.m_changeListeners == null) ? 0 : this.m_changeListeners.hashCode()); result = prime * result + Arrays.hashCode(this.m_fields); result = prime * result + (int) (this.m_interval ^ (this.m_interval >>> 32)); result = prime * result + ((this.m_toinspect == null) ? 0 : this.m_toinspect.hashCode()); return result; } /** * Makes a record of the state of the object specified in the constructor. The * new record is stored in a RingBuffer and contains all retrieveable values * of the Object specified in the constructor. Reflection is used to get the * values. If a field is private it's value is tried to be taken from the * Object by invoking a getter - method conform with the bean - specification: * The name of the method has to be "get" followed by the name of the field * with first letter upper case. */ public void inspect() { final ObjectInspection newentry = new ObjectInspection(); for (final Field mField : this.m_fields) { if (ObjectRecorder.VERBOSE) { System.out.println(this.getClass().getName() + " inpspecting " + mField.getName() + " of " + this.m_toinspect.getClass().getName() + "."); } try { mField.setAccessible(true); newentry.add(mField.get(this.m_toinspect)); } catch (final IllegalAccessException e) { if (ObjectRecorder.VERBOSE) { System.err.println(this.getClass().getName() + ".inspect(): No public access to " + mField.getName() + " of " + this.m_toinspect.getClass().getName()); } // Try to invoke bean- conform getter method. String fieldname = mField.getName(); final char[] fieldnm = fieldname.toCharArray(); fieldnm[0] = Character.toUpperCase(fieldnm[0]); fieldname = new String(fieldnm); final String methodname = new StringBuffer("get").append(fieldname).toString(); // name of method constructed. Now invoke it. try { final Method toinvoke = this.m_toinspect.getClass().getDeclaredMethod(methodname, new Class[] {}); newentry.add(toinvoke.invoke(this.m_toinspect, new Object[] {})); } catch (final NoSuchMethodException f) { if (ObjectRecorder.VERBOSE) { System.err.println(this.getClass().getName() + ".inspect(): Failure at getting field " + mField.getName() + " by trying to invoke a method: " + methodname); } } catch (final SecurityException g) { g.printStackTrace(); } catch (final IllegalAccessException h) { h.printStackTrace(); } catch (final InvocationTargetException l) { l.printStackTrace(); } } } this.m_buffer.add(newentry); this.fireChange(); } /** * Removes the given listener for changes of the inpsected instance. * <p> * * @param x * the listener to remove. */ public void removeChangeListener(final ChangeListener x) { this.m_changeListeners.remove(ChangeListener.class, x); } /** * * @see java.lang.Runnable#run() */ @Override public void run() { while (true) { try { Thread.sleep(this.m_interval); } catch (final InterruptedException e) { // nop } this.inspect(); } } /** * Define the amount of recorded states of the Object to inspect that remain * in memory. * <p> * * Default value is 100. * <p> * * @param length * the amount of recorded states of the Object to inspect that remain * in memory. */ public void setHistoryLength(final int length) { this.m_buffer.setBufferSize(length); } /** * Sets the interval for inpection of the instance to inspect in ms. * <p> * * @param sleeptime * the interval for inpection of the instance to inspect in ms. * * @see ObjectRecorder#ObjectRecorder(Object, long) */ public void setInterval(final long sleeptime) { this.m_interval = sleeptime; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return this.m_buffer.toString(); } }