package org.oddjob.persist; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.swing.ImageIcon; import org.oddjob.Describeable; import org.oddjob.Iconic; import org.oddjob.Stateful; import org.oddjob.Structural; import org.oddjob.arooa.ArooaSession; import org.oddjob.describe.UniversalDescriber; import org.oddjob.images.IconEvent; import org.oddjob.images.IconListener; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; import org.oddjob.structural.StructuralEvent; import org.oddjob.structural.StructuralListener; /** * Capture as much serializable information about a job * hierarchy as possible. This serialized form of the hierarchy * is know as it's silhouette. * * @author rob * */ public class SilhouetteFactory { /** * Create a silhouette of the subject and it's children. * * @param subject * @param session * * @return */ public Object create(Object subject, ArooaSession session) { String name = subject.toString(); Map<String, String> description = new UniversalDescriber( session).describe(subject); List<Class<?>> interfaces = new ArrayList<Class<?>>(); interfaces.add(Describeable.class); StateEvent lastJobStateEvent = null; if (subject instanceof Stateful) { Stateful stateful = (Stateful) subject; lastJobStateEvent = stateful.lastStateEvent(); interfaces.add(Stateful.class); } Object[] children = null; // Session will only be null in tests. if (subject instanceof Structural && session != null) { Structural structural = (Structural) subject; ChildCatcher childCatcher = new ChildCatcher(session); structural.addStructuralListener(childCatcher); structural.removeStructuralListener(childCatcher); children = childCatcher.getChildren(); interfaces.add(Structural.class); } IconInfo iconInfo = null; if (subject instanceof Iconic) { Iconic iconic = (Iconic) subject; IconCapture capture = new IconCapture(); iconic.addIconListener(capture); iconic.removeIconListener(capture); iconInfo = capture.getIconInfo(); interfaces.add(Iconic.class); } Silhouette silhouette = new Silhouette(name, description); Object proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), interfaces.toArray(new Class[interfaces.size()]), silhouette); if (lastJobStateEvent != null) { silhouette.setLastStateEvent(new StateEvent( (Stateful) proxy, lastJobStateEvent.getState(), lastJobStateEvent.getTime(), lastJobStateEvent.getException())); } if (children != null) { StructuralEvent[] structuralEvents = new StructuralEvent[children.length]; for (int i = 0; i < structuralEvents.length; ++i) { StructuralEvent event = new StructuralEvent( (Structural) proxy, children[i], i); structuralEvents[i] = event; } silhouette.setChildren(structuralEvents); } if (iconInfo != null) { silhouette.setIconInfo( new IconEvent((Iconic) proxy, iconInfo.getIconId()), iconInfo.getIcon()); } return proxy; } } /** * The serialized form. * */ class Silhouette implements InvocationHandler, Serializable, Describeable, Structural, Stateful, Iconic { private static final long serialVersionUID = 201004092014050800L; private final Map<String, String> description; private final String name; private volatile StructuralEvent[] structuralEvents; private volatile transient StateEvent lastStateEvent; private volatile IconEvent iconEvent; private volatile ImageIcon iconTip; Silhouette(String name, Map<String, String> description) { this.name = name; this.description = description; } void setChildren(StructuralEvent[] children) { this.structuralEvents = children; } void setLastStateEvent(StateEvent lastJobStateEvent) { this.lastStateEvent = lastJobStateEvent; } void setIconInfo(IconEvent iconEvent, ImageIcon iconTip) { this.iconEvent = iconEvent; this.iconTip = iconTip; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method ourMethod = getClass().getMethod( method.getName(), method.getParameterTypes()); return ourMethod.invoke(this, args); } @Override public Map<String, String> describe() { return description; } @Override public void addStateListener(StateListener listener) { listener.jobStateChange(lastStateEvent); } @Override public void removeStateListener(StateListener listener) { } @Override public StateEvent lastStateEvent() { return lastStateEvent; } @Override public void addStructuralListener(StructuralListener listener) { for (int i = 0; i < structuralEvents.length; ++i) { listener.childAdded(structuralEvents[i]); } } @Override public void removeStructuralListener(StructuralListener listener) { } @Override public void addIconListener(IconListener listener) { listener.iconEvent(iconEvent); } @Override public void removeIconListener(IconListener listener) { } @Override public ImageIcon iconForId(String id) { return iconTip; } @Override public String toString() { return name; } public boolean equals(Object other) { if (!(other instanceof Proxy)) { return false; } return this == Proxy.getInvocationHandler(other); } /** * Custom serialisation. */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(lastStateEvent == null ? null : lastStateEvent.serializable()); } /** * Custom serialisation. */ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); StateEvent.SerializableNoSource savedEvent = (StateEvent.SerializableNoSource) s.readObject(); if (savedEvent == null) { this.lastStateEvent = null; } else { this.lastStateEvent = new StateEvent(this, savedEvent.getState(), savedEvent.getTime(), savedEvent.getException()); } } } /** * For catching child events. * * @author rob * */ class ChildCatcher implements StructuralListener { private final ArooaSession session; private final List<Object> childHelper = new ArrayList<Object>(); private boolean childNotOurs; public ChildCatcher(ArooaSession session) { this.session = session; } Object[] getChildren() { if (childNotOurs) { return new Object[0]; } else { return childHelper.toArray(new Object[childHelper.size()]); } } @Override public void childAdded(StructuralEvent event) { Object child = event.getChild(); if (session.getComponentPool().contextFor(child) == null) { childNotOurs = true; } else { Object childSilhouette = new SilhouetteFactory().create(child, session); childHelper.add(event.getIndex(), childSilhouette); } } @Override public void childRemoved(StructuralEvent event) { childHelper.remove(event.getIndex()); } } /** * For capturing Icon info. * */ class IconInfo { private final String iconId; private final ImageIcon icon; IconInfo(String iconId, ImageIcon iconTip) { this.iconId = iconId; this.icon = iconTip; } public String getIconId() { return iconId; } public ImageIcon getIcon() { return icon; } } /** * For capturing Icon info. */ class IconCapture implements IconListener { private IconInfo iconInfo; @Override public void iconEvent(IconEvent e) { iconInfo = new IconInfo(e.getIconId(), e.getSource().iconForId(e.getIconId())); } public IconInfo getIconInfo() { return iconInfo; } }