Author: tchemit Date: 2009-02-21 18:17:21 +0000 (Sat, 21 Feb 2009) New Revision: 1238 Added: jaxx/trunk/jaxx-runtime-api/src/main/java/jaxx/runtime/DefaultApplicationContext.java jaxx/trunk/jaxx-runtime-api/src/test/java/jaxx/runtime/DefaultApplicationContextTest.java Modified: jaxx/trunk/jaxx-runtime-api/changelog.txt Log: introduce DefaultApplicationContext iwth annotation system. Modified: jaxx/trunk/jaxx-runtime-api/changelog.txt =================================================================== --- jaxx/trunk/jaxx-runtime-api/changelog.txt 2009-02-20 14:33:52 UTC (rev 1237) +++ jaxx/trunk/jaxx-runtime-api/changelog.txt 2009-02-21 18:17:21 UTC (rev 1238) @@ -1,3 +1,6 @@ +1.2 ??? 2009???? + * 2009021 [chemit] - introduce DefaultApplicationContext iwth annotation system. + 1.1 chemit 20090220 * 20090124 [chemit] - add methods to retreave icons from UIManager in Util class * 20090123 [chemit] - add a simple createIcon method in Util classe (to create an icon with no path prefix) Added: jaxx/trunk/jaxx-runtime-api/src/main/java/jaxx/runtime/DefaultApplicationContext.java =================================================================== --- jaxx/trunk/jaxx-runtime-api/src/main/java/jaxx/runtime/DefaultApplicationContext.java (rev 0) +++ jaxx/trunk/jaxx-runtime-api/src/main/java/jaxx/runtime/DefaultApplicationContext.java 2009-02-21 18:17:21 UTC (rev 1238) @@ -0,0 +1,222 @@ +package jaxx.runtime; + +import java.lang.annotation.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +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. + * + * 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 + * @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. + * + * Note : A such class always have a public default constructor. + * + * @author chemit + * @version 1.0, 21/02/09 + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @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 single String parameter. + * + * @author chemit + * @version 1.0, 21/02/09 + * @since 1.2 + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface MethodAccess { + + /** + * Define a forward to a target class. When the target class + * will be asked with method {@link #getContextValue(java.lang.Class, java.lang.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 + */ + String methodName(); + } + /** + * Map of forwarded classes (key) to classes (values). + */ + protected Map<Class, Class> forwards; + + public DefaultApplicationContext() { + super(); + forwards = new HashMap<Class, Class>(); + } + + public DefaultApplicationContext(JAXXObject ui) { + super(ui); + } + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private final Log log = LogFactory.getLog(DefaultApplicationContext.class); + + @SuppressWarnings({"unchecked"}) + @Override + public <T> T getContextValue(Class<T> clazz, String name) { + Object value = null; + MethodAccess access; + + Class<?> realClass; + + if (forwards.containsKey(clazz)) { + // this is a forward class + realClass = forwards.get(clazz); + // always call the forwarder with no name + value = getContextValue(realClass, null); + if (log.isDebugEnabled()) { + log.debug("detect forward from " + clazz + " to " + realClass + " (" + value + ")"); + } + + } else { + realClass = clazz; + value = super.getContextValue(realClass, name); + } + + if (value == null) { + AutoLoad anno = clazz.getAnnotation(AutoLoad.class); + + if (anno == null) { + // no annotation, so do nothing + return null; + } + + if (name != null) { + throw new IllegalArgumentException("an " + AutoLoad.class.getName() + " can not have a named context but was call with this one : " + name); + } + value = newInstance(clazz); + if (log.isDebugEnabled()) { + log.debug("new instance " + clazz + " : " + value); + } + // save new instance + setContextValue(value, null); + } + + access = realClass.getAnnotation(MethodAccess.class); + + if (access != null) { + if (name == null) { + if (access.target() != Object.class) { + // 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); + } + } + } + } else { + // specialized access + value = newAccess(realClass, value, access.methodName(), name); + } + } + return (T) value; + } + + @Override + public <T> void removeContextValue(Class<T> klazz, String name) { + Entry<Class, Class> entry; + if (name == null && forwards.containsValue(klazz)) { + // remove forward + Iterator<Entry<Class, Class>> itr = forwards.entrySet().iterator(); + while (itr.hasNext()) { + entry = itr.next(); + if (entry.getValue().equals(klazz)) { + itr.remove(); + if (log.isDebugEnabled()) { + log.debug("removed forward from " + entry.getKey() + " to " + klazz); + } + break; + } + } + } + //FIXME should update forwards state + super.removeContextValue(klazz, name); + } + + protected Object newInstance(Class<?> clazz) throws IllegalArgumentException { + + Object value = null; + + Constructor<?> constructor = null; + try { + constructor = clazz.getConstructor(); + // auto instanciate the class + if (constructor == null) { + throw new IllegalArgumentException(clazz + " has no public constructor"); + } + } catch (NoSuchMethodException ex) { + throw new IllegalArgumentException(ex); + } catch (SecurityException ex) { + throw new IllegalArgumentException(ex); + } + try { + value = constructor.newInstance(); + + } catch (InstantiationException ex) { + throw new IllegalArgumentException(ex); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } catch (InvocationTargetException ex) { + throw new IllegalArgumentException(ex); + } + return value; + } + + protected Object newAccess(Class<?> clazz, Object parent, String methodName, String name) throws IllegalArgumentException { + Object value; + try { + Method m = clazz.getMethod(methodName, String.class); + value = m.invoke(parent, name); + return value; + } catch (NoSuchMethodException ex) { + throw new IllegalArgumentException(ex); + } catch (SecurityException ex) { + throw new IllegalArgumentException(ex); + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException(ex); + } catch (InvocationTargetException ex) { + throw new IllegalArgumentException(ex); + } + } +} Added: jaxx/trunk/jaxx-runtime-api/src/test/java/jaxx/runtime/DefaultApplicationContextTest.java =================================================================== --- jaxx/trunk/jaxx-runtime-api/src/test/java/jaxx/runtime/DefaultApplicationContextTest.java (rev 0) +++ jaxx/trunk/jaxx-runtime-api/src/test/java/jaxx/runtime/DefaultApplicationContextTest.java 2009-02-21 18:17:21 UTC (rev 1238) @@ -0,0 +1,117 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jaxx.runtime; + +import jaxx.runtime.DefaultApplicationContext.AutoLoad; +import jaxx.runtime.DefaultApplicationContext.MethodAccess; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author tony + */ +public class DefaultApplicationContextTest { + + static int helloCount; + static int helloGetCount; + + @AutoLoad + @MethodAccess(methodName = "hello", target = String.class) + public static class Hello { + + public Hello() { + helloCount++; + } + + public String hello(String name) { + helloGetCount++; + return "hello " + name; + } + } + DefaultApplicationContext context; + + @BeforeClass + public static void setUpClass() throws Exception { + helloCount = 0; + helloGetCount = 0; + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + context = new DefaultApplicationContext(); + assertEquals(0, helloCount); + assertEquals(0, helloGetCount); + } + + @After + public void tearDown() { + context = null; + helloCount = helloGetCount = 0; + } + + @Test(expected = IllegalArgumentException.class) + public void testAutoLoadNamed() { + context.getContextValue(Hello.class, "fakeName"); + } + + @Test + public void testAutoLoad() { + Hello hello = context.getContextValue(Hello.class); + assertNotNull(hello); + assertEquals(1, helloCount); + + Hello hello2 = context.getContextValue(Hello.class); + assertNotNull(hello2); + assertEquals(1, helloCount); + assertEquals(hello, hello2); + } + + @Test + public void testForward() { + context.getContextValue(Hello.class); + assertEquals(1, helloCount); + String response = context.getContextValue(String.class, "John"); + assertNotNull(response); + assertEquals(1, helloGetCount); + assertEquals(new Hello().hello("John"), response); + } + + @Test + public void testRemove() { + String response; + + context.getContextValue(Hello.class); + assertEquals(1, helloCount); + assertEquals(1, context.forwards.size()); + response = context.getContextValue(String.class, "John"); + assertNotNull(response); + + context.removeContextValue(Hello.class); + assertEquals(0, context.forwards.size()); + response = context.getContextValue(String.class, "John"); + assertEquals(1, helloCount); + assertNull(response); + + // reinstanciate the service + context.getContextValue(Hello.class); + assertEquals(2, helloCount); + assertEquals(1, context.forwards.size()); + + // no effect with a name + context.removeContextValue(Hello.class, "fake"); + assertEquals(1, context.forwards.size()); + context.getContextValue(Hello.class); + assertEquals(2, helloCount); + } +} \ No newline at end of file