You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/10/26 16:50:21 UTC

svn commit: r829838 - in /incubator/pivot/trunk: core/src/org/apache/pivot/beans/ tools/src/org/apache/pivot/tools/wtk/

Author: tvolkert
Date: Mon Oct 26 15:50:21 2009
New Revision: 829838

URL: http://svn.apache.org/viewvc?rev=829838&view=rev
Log:
Migrated BeanMonitor functionality into BeanDictionary, updates tools project per the migration

Added:
    incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionaryListener.java
      - copied, changed from r829797, incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitorListener.java
Removed:
    incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitor.java
    incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitorListener.java
Modified:
    incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionary.java
    incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/ComponentInspectorSkin.java
    incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/EventLogger.java

Modified: incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionary.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionary.java?rev=829838&r1=829837&r2=829838&view=diff
==============================================================================
--- incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionary.java (original)
+++ incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionary.java Mon Oct 26 15:50:21 2009
@@ -17,14 +17,23 @@
 package org.apache.pivot.beans;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.apache.pivot.collections.Dictionary;
-
+import org.apache.pivot.collections.HashMap;
+import org.apache.pivot.collections.HashSet;
+import org.apache.pivot.util.ListenerList;
+import org.apache.pivot.util.ThreadUtilities;
+import org.apache.pivot.util.Vote;
 
 /**
  * Exposes Java bean properties of an object via the {@link Dictionary}
@@ -131,14 +140,70 @@
         }
     }
 
+    private class BeanInvocationHandler implements InvocationHandler {
+        @Override
+        public Object invoke(Object proxy, Method event, Object[] arguments) throws Throwable {
+            String eventName = event.getName();
+            if (eventName.endsWith(PROPERTY_CHANGE_SUFFIX)) {
+                String propertyName = eventName.substring(0, eventName.length()
+                    - PROPERTY_CHANGE_SUFFIX.length());
+
+                if (notifyingProperties.contains(propertyName)) {
+                    beanDictionaryListeners.propertyChanged(BeanDictionary.this, propertyName);
+                }
+            }
+
+            Object result = null;
+            Class<?> returnType = event.getReturnType();
+            if (returnType == Vote.class) {
+                result = Vote.APPROVE;
+            } else if (returnType == Boolean.TYPE) {
+                result = false;
+            }
+
+            return result;
+        }
+    }
+
+    private static class BeanDictionaryListenerList extends ListenerList<BeanDictionaryListener>
+        implements BeanDictionaryListener {
+        @Override
+        public void beanChanged(BeanDictionary beanDictionary, Object previousBean) {
+            for (BeanDictionaryListener listener : this) {
+                listener.beanChanged(beanDictionary, previousBean);
+            }
+        }
+
+        @Override
+        public void propertyChanged(BeanDictionary beanDictionary, String propertyName) {
+            for (BeanDictionaryListener listener : this) {
+                listener.propertyChanged(beanDictionary, propertyName);
+            }
+        }
+    }
+
     private Object bean;
     private boolean ignoreReadOnlyProperties;
 
+    private HashMap<Class<?>, Object> beanListenerProxies = null;
+    private BeanInvocationHandler invocationHandler = null;
+    private HashSet<String> notifyingProperties = null;
+
+    private BeanDictionaryListenerList beanDictionaryListeners = null;
+
     public static final String GET_PREFIX = "get";
     public static final String IS_PREFIX = "is";
     public static final String SET_PREFIX = "set";
     public static final String FIELD_PREFIX = "~";
     public static final String LISTENERS_SUFFIX = "Listeners";
+    public static final String PROPERTY_CHANGE_SUFFIX = "Changed";
+
+    /**
+     * Creates a new bean dictionary that is not associated with a bean.
+     */
+    public BeanDictionary() {
+        this(null);
+    }
 
     /**
      * Creates a new bean dictionary.
@@ -157,22 +222,52 @@
      * The bean object to wrap.
      */
     public BeanDictionary(Object bean, boolean ignoreReadOnlyProperties) {
-        if (bean == null) {
-            throw new IllegalArgumentException("bean is null.");
-        }
-
-        this.bean = bean;
         this.ignoreReadOnlyProperties = ignoreReadOnlyProperties;
+        setBean(bean);
     }
 
     /**
      * Returns the bean object this dictionary wraps.
+     *
+     * @return
+     * The bean object, or <tt>null</tt> if no bean has been set.
      */
     public Object getBean() {
         return bean;
     }
 
     /**
+     * Sets the bean object that this dictionary will wrap.
+     * <p>
+     * <b>NOTE</b>: failing to clear the bean of a bean dictionary may result in
+     * memory leaks, as the bean object may maintain references to the bean
+     * dictionary as long as it is set.
+     *
+     * @param bean
+     * The bean object, or <tt>null</tt> to clear the bean.
+     */
+    public void setBean(Object bean) {
+        Object previousBean = this.bean;
+
+        if (bean != previousBean) {
+            if (beanDictionaryListeners != null
+                && previousBean != null) {
+                unregisterBeanListeners();
+            }
+
+            this.bean = bean;
+
+            if (beanDictionaryListeners != null) {
+                if (bean != null) {
+                    registerBeanListeners();
+                }
+
+                beanDictionaryListeners.beanChanged(this, previousBean);
+            }
+        }
+    }
+
+    /**
      * Invokes the getter method for the given property.
      *
      * @param key
@@ -184,6 +279,10 @@
      */
     @Override
     public Object get(String key) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         if (key == null) {
             throw new IllegalArgumentException("key is null.");
         }
@@ -237,6 +336,10 @@
      */
     @Override
     public Object put(String key, Object value) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         if (key == null) {
             throw new IllegalArgumentException("key is null.");
         }
@@ -324,6 +427,10 @@
      */
     @Override
     public boolean containsKey(String key) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         if (key == null) {
             throw new IllegalArgumentException("key is null.");
         }
@@ -344,6 +451,10 @@
      */
     @Override
     public boolean isEmpty() {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         return !iterator().hasNext();
     }
 
@@ -357,10 +468,41 @@
      * <tt>true</tt> if the property is read-only; <tt>false</tt>, otherwise.
      */
     public boolean isReadOnly(String key) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         return isReadOnly(bean.getClass(), key);
     }
 
     /**
+     * Tells whether or not the specified property fires change events.
+     *
+     * @param key
+     * The property name.
+     *
+     * @return
+     * <tt>true</tt> if the property fires change events; <tt>false</tt>
+     * otherwise.
+     */
+    public boolean isNotifying(String key) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
+        if (beanDictionaryListeners == null) {
+            beanDictionaryListeners = new BeanDictionaryListenerList();
+            beanListenerProxies = new HashMap<Class<?>, Object>();
+            invocationHandler = new BeanInvocationHandler();
+            notifyingProperties = new HashSet<String>();
+
+            registerBeanListeners();
+        }
+
+        return notifyingProperties.contains(key);
+    }
+
+    /**
      * Returns the type of a property.
      *
      * @param key
@@ -370,6 +512,10 @@
      * The type of the property.
      */
     public Class<?> getType(String key) {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         return getType(bean.getClass(), key);
     }
 
@@ -381,6 +527,10 @@
      */
     @Override
     public Iterator<String> iterator() {
+        if (bean == null) {
+            throw new IllegalStateException("bean is null.");
+        }
+
         return new PropertyIterator();
     }
 
@@ -426,6 +576,142 @@
     }
 
     /**
+     * Registers event listeners on the bean so that the dictionary can fire
+     * property change events and report which properties can fire change
+     * events.
+     */
+    private void registerBeanListeners() {
+        Method[] methods = bean.getClass().getMethods();
+
+        for (int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+
+            if (ListenerList.class.isAssignableFrom(method.getReturnType())
+                && (method.getModifiers() & Modifier.STATIC) == 0) {
+                ParameterizedType genericType = (ParameterizedType)method.getGenericReturnType();
+                Type[] typeArguments = genericType.getActualTypeArguments();
+
+                if (typeArguments.length == 1) {
+                    Class<?> listenerInterface = (Class<?>)typeArguments[0];
+
+                    if (!listenerInterface.isInterface()) {
+                        throw new RuntimeException(listenerInterface.getName()
+                            + " is not an interface.");
+                    }
+
+                    Method[] interfaceMethods = listenerInterface.getMethods();
+                    for (int j = 0; j < interfaceMethods.length; j++) {
+                        Method interfaceMethod = interfaceMethods[j];
+                        String interfaceMethodName = interfaceMethod.getName();
+
+                        if (interfaceMethodName.endsWith(PROPERTY_CHANGE_SUFFIX)) {
+                            String propertyName = interfaceMethodName.substring(0,
+                                interfaceMethodName.length() - PROPERTY_CHANGE_SUFFIX.length());
+
+                            if (containsKey(propertyName)) {
+                                notifyingProperties.add(propertyName);
+                            }
+                        }
+                    }
+
+                    // Get the listener list
+                    Object listenerList;
+                    try {
+                        listenerList = method.invoke(bean);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    // Get the listener for this interface
+                    Object listener = beanListenerProxies.get(listenerInterface);
+                    if (listener == null) {
+                        listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
+                            new Class[]{listenerInterface}, invocationHandler);
+                        beanListenerProxies.put(listenerInterface, listener);
+                    }
+
+                    // Add the listener
+                    Class<?> listenerListClass = listenerList.getClass();
+                    Method addMethod;
+                    try {
+                        addMethod = listenerListClass.getMethod("add",
+                            new Class<?>[] {Object.class});
+                    } catch (NoSuchMethodException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    try {
+                        addMethod.invoke(listenerList, new Object[] {listener});
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Un-registers event listeners on the bean.
+     */
+    private void unregisterBeanListeners() {
+        Method[] methods = bean.getClass().getMethods();
+
+        for (int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+
+            if (ListenerList.class.isAssignableFrom(method.getReturnType())
+                && (method.getModifiers() & Modifier.STATIC) == 0) {
+                ParameterizedType genericType = (ParameterizedType)method.getGenericReturnType();
+                Type[] typeArguments = genericType.getActualTypeArguments();
+
+                if (typeArguments.length == 1) {
+                    Class<?> listenerInterface = (Class<?>)typeArguments[0];
+
+                    // Get the listener list
+                    Object listenerList;
+                    try {
+                        listenerList = method.invoke(bean);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    // Get the listener for this interface
+                    Object listener = beanListenerProxies.get(listenerInterface);
+                    if (listener == null) {
+                        throw new IllegalStateException("Listener proxy is null.");
+                    }
+
+                    // Remove the listener
+                    Class<?> listenerListClass = listenerList.getClass();
+                    Method removeMethod;
+                    try {
+                        removeMethod = listenerListClass.getMethod("remove",
+                            new Class<?>[] {Object.class});
+                    } catch (NoSuchMethodException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    try {
+                        removeMethod.invoke(listenerList, new Object[] {listener});
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    }
+                }
+            }
+        }
+
+        notifyingProperties.clear();
+    }
+
+    /**
      * Tests the read-only state of a property. Note that is no such property
      * exists, this method will return <tt>true</tt> (it will <u>not</u> throw
      * an exception).
@@ -678,4 +964,19 @@
 
         return coercedValue;
     }
+
+    public ListenerList<BeanDictionaryListener> getBeanDictionaryListeners() {
+        if (beanDictionaryListeners == null) {
+            beanDictionaryListeners = new BeanDictionaryListenerList();
+            beanListenerProxies = new HashMap<Class<?>, Object>();
+            invocationHandler = new BeanInvocationHandler();
+            notifyingProperties = new HashSet<String>();
+
+            if (bean != null) {
+                registerBeanListeners();
+            }
+        }
+
+        return beanDictionaryListeners;
+    }
 }

Copied: incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionaryListener.java (from r829797, incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitorListener.java)
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionaryListener.java?p2=incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionaryListener.java&p1=incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitorListener.java&r1=829797&r2=829838&rev=829838&view=diff
==============================================================================
--- incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanMonitorListener.java (original)
+++ incubator/pivot/trunk/core/src/org/apache/pivot/beans/BeanDictionaryListener.java Mon Oct 26 15:50:21 2009
@@ -19,48 +19,35 @@
 import java.lang.reflect.Method;
 
 /**
- * Bean monitor listener interface.
+ * Bean dictionary listener interface.
  */
-public interface BeanMonitorListener {
+public interface BeanDictionaryListener {
     /**
-     * Bean monitor listener adapter.
+     * Bean dictionary listener adapter.
      */
-    public static class Adapter implements BeanMonitorListener {
+    public static class Adapter implements BeanDictionaryListener {
         @Override
-        public void sourceChanged(BeanMonitor beanMonitor, Object previousSource) {
+        public void beanChanged(BeanDictionary beanDictionary, Object previousBean) {
         }
 
         @Override
-        public void eventFired(BeanMonitor beanMonitor, Method event, Object[] arguments) {
-        }
-
-        @Override
-        public void propertyChanged(BeanMonitor beanMonitor, String propertyName) {
+        public void propertyChanged(BeanDictionary beanDictionary, String propertyName) {
         }
     }
 
     /**
-     * Called when a bean monitor's source has changed.
-     *
-     * @param beanMonitor
-     * @param previousSource
-     */
-    public void sourceChanged(BeanMonitor beanMonitor, Object previousSource);
-
-    /**
-     * Called when an event has been fired by the bean monitor's source.
+     * Called when a bean dictionary's bean has changed.
      *
-     * @param beanMonitor
-     * @param event
-     * @param arguments
+     * @param beanDictionary
+     * @param previousBean
      */
-    public void eventFired(BeanMonitor beanMonitor, Method event, Object[] arguments);
+    public void beanChanged(BeanDictionary beanDictionary, Object previousBean);
 
     /**
-     * Called when a property of the bean monitor's source has changed.
+     * Called when a property of the bean dictionary's bean has changed.
      *
-     * @param beanMonitor
+     * @param beanDictionary
      * @param propertyName
      */
-    public void propertyChanged(BeanMonitor beanMonitor, String propertyName);
+    public void propertyChanged(BeanDictionary beanDictionary, String propertyName);
 }

Modified: incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/ComponentInspectorSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/ComponentInspectorSkin.java?rev=829838&r1=829837&r2=829838&view=diff
==============================================================================
--- incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/ComponentInspectorSkin.java (original)
+++ incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/ComponentInspectorSkin.java Mon Oct 26 15:50:21 2009
@@ -21,8 +21,7 @@
 import java.util.Comparator;
 
 import org.apache.pivot.beans.BeanDictionary;
-import org.apache.pivot.beans.BeanMonitor;
-import org.apache.pivot.beans.BeanMonitorListener;
+import org.apache.pivot.beans.BeanDictionaryListener;
 import org.apache.pivot.collections.ArrayList;
 import org.apache.pivot.collections.EnumList;
 import org.apache.pivot.collections.HashMap;
@@ -81,8 +80,7 @@
     @WTKX private Form propertiesForm = null;
     @WTKX private BoxPane stylesPane = null;
 
-    private BeanDictionary beanDictionary = null;
-    private BeanMonitor beanMonitor = new BeanMonitor();
+    private BeanDictionary beanDictionary = new BeanDictionary();
 
     private HashMap<String, Component> inspectorComponents = new HashMap<String, Component>();
 
@@ -91,9 +89,9 @@
         new PropertySourceTypeComparator();
 
     public ComponentInspectorSkin() {
-        beanMonitor.getBeanMonitorListeners().add(new BeanMonitorListener.Adapter() {
+        beanDictionary.getBeanDictionaryListeners().add(new BeanDictionaryListener.Adapter() {
             @Override
-            public void propertyChanged(BeanMonitor beanMonitor, String propertyName) {
+            public void propertyChanged(BeanDictionary beanDictionary, String propertyName) {
                 Class<?> propertyType = beanDictionary.getType(propertyName);
 
                 if (propertyType == Boolean.TYPE) {
@@ -178,18 +176,15 @@
 
         Component source = componentInspector.getSource();
 
-        beanDictionary = null;
-        beanMonitor.setSource(source);
+        beanDictionary.setBean(source);
 
         if (source != null) {
-            beanDictionary = new BeanDictionary(source);
-
             Class<?> sourceType = source.getClass();
             HashMap<Class<?>, List<String>> propertyBuckets =
                 new HashMap<Class<?>, List<String>>(propertySourceTypeComparator);
 
             for (String propertyName : beanDictionary) {
-                if (beanMonitor.isNotifyingProperty(propertyName)
+                if (beanDictionary.isNotifying(propertyName)
                     && !beanDictionary.isReadOnly(propertyName)) {
                     Method method = BeanDictionary.getGetterMethod(sourceType, propertyName);
                     Class<?> declaringClass = method.getDeclaringClass();

Modified: incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/EventLogger.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/EventLogger.java?rev=829838&r1=829837&r2=829838&view=diff
==============================================================================
--- incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/EventLogger.java (original)
+++ incubator/pivot/trunk/tools/src/org/apache/pivot/tools/wtk/EventLogger.java Mon Oct 26 15:50:21 2009
@@ -16,19 +16,68 @@
  */
 package org.apache.pivot.tools.wtk;
 
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.Comparator;
 
-import org.apache.pivot.beans.BeanMonitor;
-import org.apache.pivot.beans.BeanMonitorListener;
+import org.apache.pivot.collections.ArrayList;
 import org.apache.pivot.collections.Group;
+import org.apache.pivot.collections.HashMap;
 import org.apache.pivot.collections.HashSet;
 import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.collections.immutable.ImmutableList;
 import org.apache.pivot.collections.immutable.ImmutableSet;
 import org.apache.pivot.util.ListenerList;
+import org.apache.pivot.util.ThreadUtilities;
+import org.apache.pivot.util.Vote;
 import org.apache.pivot.wtk.Component;
 import org.apache.pivot.wtk.Container;
 
 public class EventLogger extends Container {
+    private static class EventComparator implements Comparator<Method> {
+        @Override
+        public int compare(Method event1, Method event2) {
+            int result = 0;
+
+            Class<?> listenerInterface1 = event1.getDeclaringClass();
+            Class<?> listenerInterface2 = event2.getDeclaringClass();
+
+            if (listenerInterface1 != listenerInterface2) {
+                result = listenerInterface1.getName().compareTo(listenerInterface2.getName());
+            }
+
+            if (result == 0) {
+                result = event1.getName().compareTo(event2.getName());
+            }
+
+            return result;
+        }
+    }
+
+    private class LoggerInvocationHandler implements InvocationHandler {
+        @Override
+        public Object invoke(Object proxy, Method event, Object[] arguments) throws Throwable {
+            if (includeEvents.contains(event)) {
+                eventLoggerListeners.eventFired(EventLogger.this, event, arguments);
+            }
+
+            Object result = null;
+            Class<?> returnType = event.getReturnType();
+            if (returnType == Vote.class) {
+                result = Vote.APPROVE;
+            } else if (returnType == Boolean.TYPE) {
+                result = false;
+            }
+
+            return result;
+        }
+    }
+
     private static class EventLoggerListenerList extends ListenerList<EventLoggerListener>
         implements EventLoggerListener {
         @Override
@@ -60,21 +109,12 @@
         }
     }
 
-    private BeanMonitorListener beanMonitorHandler = new BeanMonitorListener.Adapter() {
-        @Override
-        public void sourceChanged(BeanMonitor meanMonitor, Object previousSource) {
-            eventLoggerListeners.sourceChanged(EventLogger.this, (Component)previousSource);
-        }
+    private Component source = null;
 
-        @Override
-        public void eventFired(BeanMonitor meanMonitor, Method event, Object[] arguments) {
-            if (includeEvents.contains(event)) {
-                eventLoggerListeners.eventFired(EventLogger.this, event, arguments);
-            }
-        }
-    };
+    private HashMap<Class<?>, Object> eventListenerProxies = new HashMap<Class<?>, Object>();
+    private LoggerInvocationHandler loggerInvocationHandler = new LoggerInvocationHandler();
 
-    private BeanMonitor beanMonitor = new BeanMonitor();
+    private ArrayList<Method> declaredEvents = new ArrayList<Method>(new EventComparator());
 
     private HashSet<Method> includeEvents = new HashSet<Method>();
 
@@ -85,21 +125,34 @@
     }
 
     public EventLogger(Component source) {
-        beanMonitor.getBeanMonitorListeners().add(beanMonitorHandler);
         setSource(source);
         setSkin(new EventLoggerSkin());
     }
 
     public Component getSource() {
-        return (Component)beanMonitor.getSource();
+        return source;
     }
 
     public void setSource(Component source) {
-        beanMonitor.setSource(source);
+        Component previousSource = this.source;
+
+        if (source != previousSource) {
+            this.source = source;
+
+            if (previousSource != null) {
+                unregisterEventListeners(previousSource);
+            }
+
+            if (source != null) {
+                registerEventListeners(source);
+            }
+
+            eventLoggerListeners.sourceChanged(this, previousSource);
+        }
     }
 
     public Sequence<Method> getDeclaredEvents() {
-        return beanMonitor.getDeclaredEvents();
+        return new ImmutableList<Method>(declaredEvents);
     }
 
     public Group<Method> getIncludeEvents() {
@@ -124,6 +177,79 @@
         return includeEvents.contains(event);
     }
 
+    private void registerEventListeners(Component source) {
+        declaredEvents.clear();
+
+        Method[] methods = source.getClass().getMethods();
+
+        for (int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+
+            if (ListenerList.class.isAssignableFrom(method.getReturnType())
+                && (method.getModifiers() & Modifier.STATIC) == 0) {
+                ParameterizedType genericType = (ParameterizedType)method.getGenericReturnType();
+                Type[] typeArguments = genericType.getActualTypeArguments();
+
+                if (typeArguments.length == 1) {
+                    Class<?> listenerInterface = (Class<?>)typeArguments[0];
+
+                    if (!listenerInterface.isInterface()) {
+                        throw new RuntimeException(listenerInterface.getName()
+                            + " is not an interface.");
+                    }
+
+                    Method[] interfaceMethods = listenerInterface.getMethods();
+                    for (int j = 0; j < interfaceMethods.length; j++) {
+                        Method interfaceMethod = interfaceMethods[j];
+                        declaredEvents.add(interfaceMethod);
+                    }
+
+                    // Get the listener list
+                    Object listenerList;
+                    try {
+                        listenerList = method.invoke(source);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    // Get the listener for this interface
+                    Object listener = eventListenerProxies.get(listenerInterface);
+                    if (listener == null) {
+                        listener = Proxy.newProxyInstance(ThreadUtilities.getClassLoader(),
+                            new Class[]{listenerInterface}, loggerInvocationHandler);
+                        eventListenerProxies.put(listenerInterface, listener);
+                    }
+
+                    // Add the listener
+                    Class<?> listenerListClass = listenerList.getClass();
+                    Method addMethod;
+                    try {
+                        addMethod = listenerListClass.getMethod("add",
+                            new Class<?>[] {Object.class});
+                    } catch (NoSuchMethodException exception) {
+                        throw new RuntimeException(exception);
+                    }
+
+                    try {
+                        addMethod.invoke(listenerList, new Object[] {listener});
+                    } catch (IllegalAccessException exception) {
+                        throw new RuntimeException(exception);
+                    } catch (InvocationTargetException exception) {
+                        throw new RuntimeException(exception);
+                    }
+                }
+            }
+        }
+    }
+
+    private void unregisterEventListeners(Component source) {
+        declaredEvents.clear();
+
+        // TODO
+    }
+
     public ListenerList<EventLoggerListener> getEventLoggerListeners() {
         return eventLoggerListeners;
     }