Author: tchemit
Date: 2010-03-23 11:56:00 +0100 (Tue, 23 Mar 2010)
New Revision: 1800
Log:
Evolution #410: Can listen modifications of a DefaultApplicationContext via the PCS api
Modified:
trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultApplicationContext.java
trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultJAXXContext.java
trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/JAXXContextEntryDef.java
trunk/jaxx-runtime/src/test/java/jaxx/runtime/context/DefaultApplicationContextTest.java
Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultApplicationContext.java
===================================================================
--- trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultApplicationContext.java 2010-03-22 14:05:21 UTC (rev 1799)
+++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultApplicationContext.java 2010-03-23 10:56:00 UTC (rev 1800)
@@ -20,10 +20,16 @@
*/
package jaxx.runtime.context;
-import jaxx.runtime.*;
+import jaxx.runtime.JAXXContext;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
-import java.lang.annotation.*;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -31,87 +37,85 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
/**
* The default context to be used for an application.
+ * <p/>
+ * This extends the {@link DefaultJAXXContext} and add a possibility to
+ * auto-instanciate some classes asked via {@link #getContextValue(Class)} and
+ * {@link #getContextValue(Class, String)} methods.
+ * <p/>
+ * To registred a such class, just annotate your class with {@link AutoLoad}.
*
- * This extends the {@link DefaultJAXXContext} and add a possibility to auto-instanciate
- * some classes asked via {@link #getContextValue(java.lang.Class)} and
- * {@link #getContextValue(java.lang.Class, java.lang.String)} methods.
- *
- * To registred a such class, just annotate your class with {@link AutoLoad}.
- *
* @author chemit
+ * @see DefaultJAXXContext
* @since 1.2
- * @see DefaultJAXXContext
*/
public class DefaultApplicationContext extends DefaultJAXXContext {
/**
- * A class annotated @AutoLoad is used by context to auto
- * instanciate the class when required.
- *
+ * A class annotated @AutoLoad is used by context to auto instanciate
+ * the class when required.
+ * <p/>
* Note : A such class always have a public default constructor.
*
- * @author chemit
+ * @author chemit
* @version 1.0, 21/02/09
* @since 1.2
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface AutoLoad {
+ public @interface AutoLoad {
//TODO use this to add a method to init
String initMethod() default "";
}
/**
- * A class annotated @MethodAccess is used by context to obtain the value
- * of an entry via a declared method.
- *
- * Note : A such class must have a method called {@link #methodName()} with
+ * A class annotated @MethodAccess is used by context to obtain the
+ * value of an entry via a declared method.
+ * <p/>
+ * Note : A such class must have a method called {@link #methodName()} with
* a single String parameter.
*
- * @author chemit
+ * @author chemit
* @version 1.0, 21/02/09
* @since 1.2
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
- public static @interface MethodAccess {
+ public @interface MethodAccess {
/**
- * Define a forward to a target class. When the target class
- * will be asked with method {@link JAXXContext#getContextValue(java.lang.Class, java.lang.String)}
+ * Define a forward to a target class. When the target class will be
+ * asked with method {@link JAXXContext#getContextValue(Class, String)}
* it will be delegating to this class.
*
* @return the forwarded class
*/
Class<?> target() default Object.class;
- /**
- * @return the name of the method to access data
- */
+ /** @return the name of the method to access data */
String methodName();
}
+
+ /** Map of forwarded classes (key) to classes (values). */
+ protected Map<Class<?>, Class<?>> forwards;
+
/**
- * Map of forwarded classes (key) to classes (values).
+ * Map of entries to watch associated with the property to fires if a
+ * modification was found.
*/
- protected Map<Class<?>, Class<?>> forwards;
+ protected Map<JAXXContextEntryDef<?>, String> entryListened;
public DefaultApplicationContext() {
- super();
forwards = new HashMap<Class<?>, Class<?>>();
pcs = new PropertyChangeSupport(this);
}
-// public DefaultApplicationContext(JAXXObject ui) {
-// super();
-// }
/** to use log facility, just put in your code: log.info(\"...\"); */
- static private final Log log = LogFactory.getLog(DefaultApplicationContext.class);
+ static private final Log log =
+ LogFactory.getLog(DefaultApplicationContext.class);
/** to manage properties modifications */
protected PropertyChangeSupport pcs;
@@ -130,7 +134,8 @@
// always call the forwarder with no name
value = getContextValue(realClass, null);
if (log.isDebugEnabled()) {
- log.debug("detect forward from " + clazz + " to " + realClass + " (" + value + ")");
+ log.debug("detect forward from " + clazz + " to " + realClass +
+ " (" + value + ")");
}
} else {
@@ -149,10 +154,12 @@
}
if (name != null) {
- throw new IllegalArgumentException("an " + AutoLoad.class.getName() + " can not have a named context but was call with this one : " + name);
+ throw new IllegalArgumentException(
+ "an " + AutoLoad.class.getName() + " can not have " +
+ "a named context but was call with this one : " + name);
}
value = newInstance(clazz);
- if (!anno.initMethod().trim().isEmpty()){
+ if (!anno.initMethod().trim().isEmpty()) {
// apply method on class
newAccess(clazz, value, anno.initMethod().trim());
}
@@ -167,14 +174,15 @@
if (access != null) {
if (name == null) {
- if (access.target() != Object.class) {
+ if (!Object.class.equals(access.target())) {
// register forward
Class<?> targetClass = access.target();
if (!forwards.containsKey(targetClass)) {
// register forward
forwards.put(targetClass, clazz);
if (log.isDebugEnabled()) {
- log.debug("register forward from " + targetClass + " to " + clazz);
+ log.debug("register forward from " + targetClass +
+ " to " + clazz);
}
}
}
@@ -187,31 +195,109 @@
}
@Override
- public <T> void removeContextValue(Class<T> klazz, String name) {
+ public <T> void removeContextValue(Class<T> klass, String name) {
Entry<Class<?>, Class<?>> entry;
- if (name == null && forwards.containsValue(klazz)) {
+ if (name == null && forwards.containsValue(klass)) {
// remove forward
- Iterator<Entry<Class<?>, Class<?>>> itr = forwards.entrySet().iterator();
+ Iterator<Entry<Class<?>, Class<?>>> itr =
+ forwards.entrySet().iterator();
while (itr.hasNext()) {
entry = itr.next();
- if (entry.getValue().equals(klazz)) {
+ if (entry.getValue().equals(klass)) {
itr.remove();
if (log.isDebugEnabled()) {
- log.debug("removed forward from " + entry.getKey() + " to " + klazz);
+ log.debug("removed forward from " + entry.getKey() +
+ " to " + klass);
}
break;
}
}
}
//FIXME should update forwards state
- super.removeContextValue(klazz, name);
+ JAXXContextEntryDef<?> entryToDel = getEntry(klass, name);
+ Object t = remove0(klass, name);
+ if (log.isDebugEnabled()) {
+ log.debug("removed object = " + t);
+ }
+ if (t != null && entryToDel != null) {
+ // a value was removed
+ // notify listeners
+ fireEntryChanged(entryToDel, t, null);
+ }
+// super.removeContextValue(klass, name);
}
+ @Override
+ public <T> void setContextValue(T o, String name) {
+// super.setContextValue(o, name);
+ if (name == null && PARENT_CONTEXT_ENTRY.accept2(o.getClass(), null)) {
+ setParentContext((JAXXContext) o);
+ return;
+ }
+ JAXXContextEntryDef<?> entry = getKey(name, o.getClass());
+ // first remove entry
+ Object oldValue = remove0(o.getClass(), name);
+ if (oldValue != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("remove value " + oldValue.getClass() + " for " +
+ entry);
+ }
+ }
+ // then can put safely
+ data.put(entry, o);
+ if (log.isDebugEnabled()) {
+ log.debug("[" + name + "] set value for entry " + entry);
+ }
+ // firezs
+ fireEntryChanged(entry.getKlass(), name, oldValue, o);
+ }
+
+ /**
+ * To add a listen modification of the given entry in the context.
+ *
+ * @param entry the entry to listen
+ * @param name the property name to fire if necessary
+ * @param listener the listener to notify if entry has changed
+ * @since 2.0.1
+ */
+ public void addPropertyChangeListener(JAXXContextEntryDef<?> entry,
+ String name,
+ PropertyChangeListener listener) {
+ if (entryListened == null) {
+ entryListened = new HashMap<JAXXContextEntryDef<?>, String>();
+ }
+ entryListened.put(entry, name);
+ if (log.isDebugEnabled()) {
+ log.debug("[" + name + "] for " + entry);
+ }
+ pcs.addPropertyChangeListener(name, listener);
+ }
+
+ /**
+ * To remove a listen modification of the given entry in the context.
+ *
+ * @param entry the entry to listen
+ * @param name the property name to fire if necessary
+ * @param listener the listener to notify if entry has changed
+ * @since 2.0.1
+ */
+ public void removePropertyChangeListener(JAXXContextEntryDef<?> entry,
+ String name,
+ PropertyChangeListener listener) {
+ if (entryListened == null) {
+ entryListened = new HashMap<JAXXContextEntryDef<?>, String>();
+ }
+ entryListened.remove(entry);
+ pcs.removePropertyChangeListener(name, listener);
+ }
+
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
- public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+
+ public void addPropertyChangeListener(String propertyName,
+ PropertyChangeListener listener) {
pcs.addPropertyChangeListener(propertyName, listener);
}
@@ -219,7 +305,8 @@
pcs.removePropertyChangeListener(listener);
}
- public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+ public void removePropertyChangeListener(String propertyName,
+ PropertyChangeListener listener) {
pcs.removePropertyChangeListener(propertyName, listener);
}
@@ -227,7 +314,8 @@
return pcs.hasListeners(propertyName);
}
- public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
+ public synchronized PropertyChangeListener[] getPropertyChangeListeners(
+ String propertyName) {
return pcs.getPropertyChangeListeners(propertyName);
}
@@ -235,8 +323,78 @@
return pcs.getPropertyChangeListeners();
}
- protected Object newInstance(Class<?> clazz) throws IllegalArgumentException {
+ protected void fireEntryChanged(Class<?> klass,
+ String name,
+ Object oldValue,
+ Object newValue) {
+ // a value was removed
+ if (entryListened != null) {
+ // look if the entry was listened
+ if (log.isTraceEnabled()) {
+ log.trace("data size : " + data.size());
+ for (Entry<JAXXContextEntryDef<?>, Object> e : data.entrySet()) {
+ log.trace(e.getKey());
+ }
+ }
+ JAXXContextEntryDef<?> entry2 = getEntry(klass, name);
+ if (log.isDebugEnabled()) {
+ log.debug("[" + name + "] : try to find a changer for " + entry2);
+ }
+ if (entry2 != null) {
+ // entry find directly on this context
+ String propertyName = entryListened.get(entry2);
+ if (log.isTraceEnabled()) {
+ log.trace("registred property name = " + propertyName);
+ }
+ if (propertyName != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("will notify modification on " + entry2);
+ }
+ // fires the removed
+ firePropertyChange(propertyName, oldValue, newValue);
+ }
+ }
+ }
+ }
+
+ protected void fireEntryChanged(JAXXContextEntryDef<?> entryDef,
+ Object oldValue,
+ Object newValue) {
+ // a value was removed
+ if (entryListened != null) {
+ // look if the entry was listened
+
+ if (log.isTraceEnabled()) {
+ log.trace("data size : " + data.size());
+ for (Entry<JAXXContextEntryDef<?>, Object> e : data.entrySet()) {
+ log.trace(e.getKey());
+ }
+ }
+// JAXXContextEntryDef<?> entry2 = getEntry(klass, name);
+// if (log.isDebugEnabled()) {
+// log.debug("[" + name + "] : try to find a changer for " + entry2);
+// }
+ if (entryDef != null) {
+ // entry find directly on this context
+ String propertyName = entryListened.get(entryDef);
+ if (log.isTraceEnabled()) {
+ log.trace("registred property name = " + propertyName);
+ }
+ if (propertyName != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("will notify modification on " + entryDef);
+ }
+ // fires the removed
+ firePropertyChange(propertyName, oldValue, newValue);
+ }
+ }
+ }
+ }
+
+ protected Object newInstance(Class<?> clazz) throws
+ IllegalArgumentException {
+
Object value;
Constructor<?> constructor;
@@ -244,7 +402,8 @@
constructor = clazz.getConstructor();
// auto instanciate the class
if (constructor == null) {
- throw new IllegalArgumentException(clazz + " has no public constructor");
+ throw new IllegalArgumentException(
+ clazz + " has no public constructor");
}
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException(ex);
@@ -264,7 +423,11 @@
return value;
}
- protected Object newAccess(Class<?> clazz, Object parent, String methodName, String name) throws IllegalArgumentException {
+ protected Object newAccess(
+ Class<?> clazz,
+ Object parent,
+ String methodName,
+ String name) throws IllegalArgumentException {
Object value;
try {
Method m = clazz.getMethod(methodName, String.class);
@@ -281,7 +444,10 @@
}
}
- protected Object newAccess(Class<?> clazz, Object parent, String methodName) throws IllegalArgumentException {
+ protected Object newAccess(
+ Class<?> clazz,
+ Object parent,
+ String methodName) throws IllegalArgumentException {
Object value;
try {
Method m = clazz.getMethod(methodName);
@@ -298,7 +464,9 @@
}
}
- protected void firePropertyChange(String name, Object oldValue, Object newValue) {
+ protected void firePropertyChange(String name,
+ Object oldValue,
+ Object newValue) {
pcs.firePropertyChange(name, oldValue, newValue);
}
}
Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultJAXXContext.java
===================================================================
--- trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultJAXXContext.java 2010-03-22 14:05:21 UTC (rev 1799)
+++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/DefaultJAXXContext.java 2010-03-23 10:56:00 UTC (rev 1800)
@@ -158,44 +158,62 @@
}
protected JAXXContextEntryDef<?> getKey(String name, Class<?> klass) {
+ //FIXME-TC20100322 : must change this to have a store, we instanciate
+ // a new object each time we wants to deal with the context...
return JAXXUtil.newContextEntryDef(name, klass);
}
@SuppressWarnings({"unchecked"})
- protected <T> T remove0(Class<T> klazz, String name) {
- if (PARENT_CONTEXT_ENTRY.accept(klazz, name)) {
+ protected <T> T remove0(Class<T> klass, String name) {
+ if (PARENT_CONTEXT_ENTRY.accept(klass, name)) {
JAXXContext old = getParentContext();
setParentContext(null);
return (T) old;
}
- JAXXContextEntryDef<?> entry = null;
- for (JAXXContextEntryDef<?> entryDef : data.keySet()) {
- if (entryDef.accept(klazz, name)) {
- entry = entryDef;
- break;
- }
- }
+ JAXXContextEntryDef<?> entry = getEntry(klass, name);
+// JAXXContextEntryDef<?> entry = null;
+// for (JAXXContextEntryDef<?> entryDef : data.keySet()) {
+// if (entryDef.accept(klass, name)) {
+// entry = entryDef;
+// break;
+// }
+// }
if (entry != null) {
return (T) data.remove(entry);
}
- if (JAXXContext.class.equals(klazz)) {
+ if (JAXXContext.class.equals(klass)) {
return null;
}
- // try in parentContext
+
+ // try on parent
JAXXContext parent = getParentContext();
if (parent == null) {
+ // no parent, stop now, can not found entry
return null;
}
if (parent instanceof DefaultJAXXContext) {
- return ((DefaultJAXXContext) parent).remove0(klazz, name);
+ // try now on the parent
+ return ((DefaultJAXXContext) parent).remove0(klass, name);
}
-
+
+ // can not find the entry anywhere, so says that nothing was removed
return null;
}
+ protected JAXXContextEntryDef<?> getEntry(Class<?> klass, String name) {
+ JAXXContextEntryDef<?> entry = null;
+ for (JAXXContextEntryDef<?> entryDef : data.keySet()) {
+ if (entryDef.accept(klass, name)) {
+ entry = entryDef;
+ break;
+ }
+ }
+ return entry;
+ }
+
protected JAXXContext getParentContext() {
return parentContext;
}
Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/JAXXContextEntryDef.java
===================================================================
--- trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/JAXXContextEntryDef.java 2010-03-22 14:05:21 UTC (rev 1799)
+++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/context/JAXXContextEntryDef.java 2010-03-23 10:56:00 UTC (rev 1800)
@@ -96,7 +96,6 @@
JAXXContextEntryDef<?> that = (JAXXContextEntryDef<?>) o;
return klass.equals(that.klass) &&
!(name != null ? !name.equals(that.name) : that.name != null);
-
}
@Override
@@ -106,7 +105,7 @@
}
public boolean accept(Class<?> klass, String name) {
- if (klass == Object.class && this.klass != Object.class) {
+ if (Object.class.equals(klass) && !Object.class.equals(this.klass)) {
// try on name only
return this.name != null && name != null && this.name.equals(name);
}
Modified: trunk/jaxx-runtime/src/test/java/jaxx/runtime/context/DefaultApplicationContextTest.java
===================================================================
--- trunk/jaxx-runtime/src/test/java/jaxx/runtime/context/DefaultApplicationContextTest.java 2010-03-22 14:05:21 UTC (rev 1799)
+++ trunk/jaxx-runtime/src/test/java/jaxx/runtime/context/DefaultApplicationContextTest.java 2010-03-23 10:56:00 UTC (rev 1800)
@@ -20,24 +20,37 @@
*/
package jaxx.runtime.context;
+import jaxx.runtime.JAXXUtil;
import jaxx.runtime.context.DefaultApplicationContext.AutoLoad;
import jaxx.runtime.context.DefaultApplicationContext.MethodAccess;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.*;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Arrays;
+import java.util.List;
+
import static org.junit.Assert.*;
-/**
- *
- * @author tchemit <chemit(a)codelutin.com>
- */
+/** @author tchemit <chemit(a)codelutin.com> */
public class DefaultApplicationContextTest {
+ /** Logger */
+ private static final Log log =
+ LogFactory.getLog(DefaultApplicationContextTest.class);
+
static int helloCount;
+
static int helloGetCount;
+ static final JAXXContextEntryDef<String> STRING_ENTRY =
+ JAXXUtil.newContextEntryDef("myStringEntryKey", String.class);
+
+ static final JAXXContextEntryDef<List<String>> LIST_STRING_ENTRY =
+ JAXXUtil.newListContextEntryDef("myListEntryKey");
+
@AutoLoad
@MethodAccess(methodName = "hello", target = String.class)
public static class Hello {
@@ -51,6 +64,7 @@
return "hello " + name;
}
}
+
DefaultApplicationContext context;
@BeforeClass
@@ -130,4 +144,70 @@
context.getContextValue(Hello.class);
assertEquals(2, helloCount);
}
+
+ static int yoCount;
+
+ @Test
+ public void testEntryListener() {
+
+ PropertyChangeListener listener = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (log.isInfoEnabled()) {
+ log.info("changed detected on " + evt.getSource());
+ }
+ yoCount++;
+ }
+ };
+ context.addPropertyChangeListener(STRING_ENTRY, "myKey", listener);
+
+ STRING_ENTRY.setContextValue(context, "myValue");
+
+ Assert.assertEquals(1, yoCount);
+
+ STRING_ENTRY.removeContextValue(context);
+
+ Assert.assertEquals(2, yoCount);
+
+ context.removePropertyChangeListener(STRING_ENTRY, "myKey", listener);
+
+ // test that nothing changed now
+
+ STRING_ENTRY.setContextValue(context, "myValue");
+
+ Assert.assertEquals(2, yoCount);
+
+ STRING_ENTRY.removeContextValue(context);
+
+ Assert.assertEquals(2, yoCount);
+
+ // test with a list entry
+
+ context.addPropertyChangeListener(LIST_STRING_ENTRY, "myKey2", listener);
+
+ LIST_STRING_ENTRY.setContextValue(context, Arrays.asList("myValue"));
+
+ Assert.assertEquals(3, yoCount);
+
+ LIST_STRING_ENTRY.removeContextValue(context);
+
+ Assert.assertEquals(4, yoCount);
+
+
+ // test that nothing changed now
+
+ context.removePropertyChangeListener(LIST_STRING_ENTRY, "myKey2", listener);
+
+ LIST_STRING_ENTRY.setContextValue(context, Arrays.asList("myValue2"));
+
+ Assert.assertEquals(4, yoCount);
+
+ LIST_STRING_ENTRY.removeContextValue(context);
+
+ Assert.assertEquals(4, yoCount);
+
+
+ }
+
+
}