diff --git a/README b/README new file mode 100644 index 0000000..12a4f5d --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +# TODO Items +* [ ] Convert Authority to a Guard +* [ ] No-Match behavior? +* [ ] Some serious code-smell refactorings :-) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6fd521a..3bb6efb 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,13 @@ 0.0.1-SNAPSHOT timebox http://maven.apache.org + + + Apache License 2.0 + http://www.apache.org/licenses/LICENSE-2.0.html + repo + + diff --git a/src/main/java/com/ning/timebox/Gather.java b/src/main/java/com/ning/timebox/Gather.java new file mode 100644 index 0000000..7d1b740 --- /dev/null +++ b/src/main/java/com/ning/timebox/Gather.java @@ -0,0 +1,9 @@ +package com.ning.timebox; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Gather +{ +} diff --git a/src/main/java/com/ning/timebox/GuardHouse.java b/src/main/java/com/ning/timebox/GuardHouse.java index 594a7a9..2145a57 100644 --- a/src/main/java/com/ning/timebox/GuardHouse.java +++ b/src/main/java/com/ning/timebox/GuardHouse.java @@ -7,4 +7,5 @@ public interface GuardHouse { Predicate buildMethodPredicate(Annotation a, Object handler, Method m); Predicate buildArgumentPredicate(Annotation a, Object handler, Method m, int argumentIndex); + Predicate buildGatherPredicate(Annotation a, Object handler, Method m, Class expectedType, int argumentIndex); } diff --git a/src/main/java/com/ning/timebox/GuardMethodGuardHouse.java b/src/main/java/com/ning/timebox/GuardMethodGuardHouse.java index b61fdaf..1cb0f91 100644 --- a/src/main/java/com/ning/timebox/GuardMethodGuardHouse.java +++ b/src/main/java/com/ning/timebox/GuardMethodGuardHouse.java @@ -44,7 +44,7 @@ public Predicate buildArgumentPredicate(Annotation a, final Object targe String method_name = ((GuardMethod) a).value(); final Method guard_method; try { - guard_method = target.getClass().getMethod(method_name, m.getParameterTypes()); + guard_method = target.getClass().getMethod(method_name, m.getParameterTypes()[argumentIndex]); } catch (NoSuchMethodException e) { throw new IllegalStateException("no method with correct signature matches " + method_name, e); @@ -73,4 +73,44 @@ public boolean test(Object arg) } }; } + + public Predicate buildGatherPredicate(Annotation a, + final Object target, + Method m, + Class expectedType, + int argumentIndex) + { + String method_name = ((GuardMethod) a).value(); + final Method guard_method; + try { + guard_method = target.getClass().getMethod(method_name, expectedType); + } + catch (NoSuchMethodException e) { + throw new IllegalStateException("no method with correct signature matches " + method_name, e); + } + + if (!(Boolean.class.equals(guard_method.getReturnType()) + || boolean.class.equals(guard_method.getReturnType()))) + { + throw new IllegalStateException("guard method, " + method_name + " must return boolean"); + } + + return new Predicate() + { + + public boolean test(Object arg) + { + try { + return (Boolean) guard_method.invoke(target, arg); + } + catch (IllegalAccessException e) { + throw new IllegalStateException(e); + } + catch (InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + }; + + } } diff --git a/src/main/java/com/ning/timebox/Handler.java b/src/main/java/com/ning/timebox/Handler.java new file mode 100644 index 0000000..e5aad02 --- /dev/null +++ b/src/main/java/com/ning/timebox/Handler.java @@ -0,0 +1,176 @@ +package com.ning.timebox; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class Handler +{ + private final Object[] values; + private final long[] authorities; + private final Object target; + private final Method method; + private final Class[] types; + private final List> methodTests = new ArrayList>(); + private final List> parameterTests; + + // null means do not gather + private final Class[] gatheredTypes; + + public Handler(Factory factory, Object target, Method method) + { + this.types = method.getParameterTypes(); + this.target = target; + this.method = method; + this.values = new Object[types.length]; + this.authorities = new long[types.length]; + this.gatheredTypes = new Class[types.length]; + this.parameterTests = new ArrayList>(method.getParameterTypes().length); + + for (Annotation annotation : method.getAnnotations()) { + for (Class iface : annotation.getClass().getInterfaces()) { + if (iface.isAnnotationPresent(GuardAnnotation.class)) { + GuardAnnotation sp = iface.getAnnotation(GuardAnnotation.class); + final GuardHouse p; + try { + p = factory.instantiate(sp.value()); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + methodTests.add(p.buildMethodPredicate(annotation, target, method)); + } + } + } + + for (int i = 0; i < method.getParameterTypes().length; i++) { + parameterTests.add(new ArrayList()); + } + + // now prefill authorities to required authority - 1, + // so that when needed authoity comes in, it is at higher + Annotation[][] param_annos = method.getParameterAnnotations(); + for (int i = 0; i < param_annos.length; i++) { + // loop through each parameter + authorities[i] = Long.MIN_VALUE; + + GatherData gd = isGather(param_annos[i], i); + if (gd.isGather) { + this.values[gd.gatherIndex] = new ArrayList(); + this.gatheredTypes[i] = gd.gatherType; + } + + for (Annotation annotation : param_annos[i]) { + + if (annotation instanceof Authority) { + authorities[i] = ((Authority) annotation).value(); + } + + for (Class iface : annotation.getClass().getInterfaces()) { + if (iface.isAnnotationPresent(GuardAnnotation.class)) { + GuardAnnotation sp = iface.getAnnotation(GuardAnnotation.class); + final GuardHouse p; + try { + p = factory.instantiate(sp.value()); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + final Predicate pred; + if (gd.isGather) { + pred = p.buildGatherPredicate(annotation, target, method, gd.gatherType, i); + } + else { + pred = p.buildArgumentPredicate(annotation, target, method, i); + } + parameterTests.get(i).add(pred); + } + } + } + } + } + + + private class GatherData + { + boolean isGather = false; + Class gatherType; + int gatherIndex; + } + + private GatherData isGather(Annotation[] annos, int parameterIndex) + { + GatherData gd = new GatherData(); + gd.gatherIndex = parameterIndex; + for (Annotation annotation : annos) { + if (annotation instanceof Gather) { + Class param_type = method.getParameterTypes()[parameterIndex]; + if (!Collection.class.isAssignableFrom(param_type)) { + throw new IllegalArgumentException("Can only @Gather against Collection"); + } + + // I hate always having to do this + ParameterizedType gen_type = (ParameterizedType) method.getGenericParameterTypes()[parameterIndex]; + gd.gatherType = (Class) gen_type.getActualTypeArguments()[0]; + gd.isGather = true; + break; + } + } + return gd; + } + + public void provide(Class type, Object value, long authority) + { + for (int i = 0; i < types.length; i++) { + if (types[i].isAssignableFrom(type) + && authorities[i] <= authority + && testParameterPredicates(value, parameterTests.get(i))) + { + values[i] = value; + authorities[i] = authority; + return; + } + + if (gatheredTypes[i] != null + && gatheredTypes[i].isAssignableFrom(type) + && testParameterPredicates(value, parameterTests.get(i))) + { + ((Collection) values[i]).add(value); + } + } + } + + private boolean testParameterPredicates(Object value, Collection predicates) + { + for (Predicate predicate : predicates) { + if (!predicate.test(value)) { + return false; + } + } + return true; + } + + public boolean isSatisfied() + { + for (Object value : values) { + if (value == null) { + return false; + } + } + for (Predicate test : methodTests) { + if (!test.test(values)) { + return false; + } + } + return true; + } + + public void handle() throws InvocationTargetException, IllegalAccessException + { + method.invoke(target, values); + } +} diff --git a/src/main/java/com/ning/timebox/TimeBox.java b/src/main/java/com/ning/timebox/TimeBox.java index d2d4eaf..fc8757a 100644 --- a/src/main/java/com/ning/timebox/TimeBox.java +++ b/src/main/java/com/ning/timebox/TimeBox.java @@ -1,13 +1,9 @@ package com.ning.timebox; -import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; import java.util.Comparator; -import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; @@ -28,11 +24,9 @@ public int compare(Integer first, Integer second) private final Semaphore flag = new Semaphore(0); private final int highestPriority; - private final Factory factory; public TimeBox(Factory factory, Object handler) { - this.factory = factory; int hp = Integer.MIN_VALUE; final Method[] methods = handler.getClass().getDeclaredMethods(); for (Method method : methods) { @@ -41,7 +35,7 @@ public TimeBox(Factory factory, Object handler) if (priority > hp) { hp = priority; } - Handler h = new Handler(handler, method); + Handler h = new Handler(factory, handler, method); if (handlers.containsKey(priority)) { throw new IllegalArgumentException(format("multiple reactor methods have priority %d", priority)); } @@ -96,116 +90,4 @@ public boolean react(long number, TimeUnit unit) throws InterruptedException, In return false; } - - private class Handler - { - private final Object[] values; - private final long[] authorities; - private final Object target; - private final Method method; - private final Class[] types; - private final List> methodTests = new ArrayList>(); - private final List> parameterTests; - - public Handler(Object target, Method method) - { - this.types = method.getParameterTypes(); - this.target = target; - this.method = method; - this.values = new Object[types.length]; - this.authorities = new long[types.length]; - this.parameterTests = new ArrayList>(method.getParameterTypes().length); - - - for (Annotation annotation : method.getAnnotations()) { - for (Class iface : annotation.getClass().getInterfaces()) { - if (iface.isAnnotationPresent(GuardAnnotation.class)) { - GuardAnnotation sp = iface.getAnnotation(GuardAnnotation.class); - final GuardHouse p; - try { - p = factory.instantiate(sp.value()); - } - catch (Exception e) { - throw new IllegalStateException(e); - } - methodTests.add(p.buildMethodPredicate(annotation, target, method)); - } - } - } - - for (int i = 0; i < method.getParameterTypes().length; i++) { - parameterTests.add(new ArrayList()); - } - - // now prefill authorities to required authority - 1, - // so that when needed authoity comes in, it is at higher - Annotation[][] param_annos = method.getParameterAnnotations(); - for (int i = 0; i < param_annos.length; i++) { - authorities[i] = Long.MIN_VALUE; - for (Annotation annotation : param_annos[i]) { - if (annotation instanceof Authority) { - authorities[i] = ((Authority) annotation).value(); - } - - - for (Class iface : annotation.getClass().getInterfaces()) { - if (iface.isAnnotationPresent(GuardAnnotation.class)) { - GuardAnnotation sp = iface.getAnnotation(GuardAnnotation.class); - final GuardHouse p; - try { - p = factory.instantiate(sp.value()); - } - catch (Exception e) { - throw new IllegalStateException(e); - } - parameterTests.get(i).add(p.buildArgumentPredicate(annotation, target, method, i)); - } - } - } - } - } - - public void provide(Class type, Object value, long authority) - { - for (int i = 0; i < types.length; i++) { - if (types[i].isAssignableFrom(type) - && authorities[i] <= authority - && testParameterPredicates(value, parameterTests.get(i))) { - values[i] = value; - authorities[i] = authority; - return; - } - } - } - - private boolean testParameterPredicates(Object value, Collection predicates) - { - for (Predicate predicate : predicates) { - if (!predicate.test(value)) { - return false; - } - } - return true; - } - - public boolean isSatisfied() - { - for (Object value : values) { - if (value == null) { - return false; - } - } - for (Predicate test : methodTests) { - if (!test.test(values)) { - return false; - } - } - return true; - } - - public void handle() throws InvocationTargetException, IllegalAccessException - { - method.invoke(target, values); - } - } } diff --git a/src/main/java/com/ning/timebox/clojure/ClojurePredicator.java b/src/main/java/com/ning/timebox/clojure/ClojurePredicator.java index 58ce822..07c5bd4 100644 --- a/src/main/java/com/ning/timebox/clojure/ClojurePredicator.java +++ b/src/main/java/com/ning/timebox/clojure/ClojurePredicator.java @@ -73,7 +73,7 @@ public boolean test(Object arg) return (Boolean) FALSY.invoke(fn, arg); } catch (Exception e) { - throw new UnsupportedOperationException("Not Yet Implemented!"); + throw new IllegalArgumentException(e); } } }; @@ -82,4 +82,13 @@ public boolean test(Object arg) throw new RuntimeException(e); } } + + public Predicate buildGatherPredicate(Annotation a, + Object handler, + Method m, + Class expectedType, + int argumentIndex) + { + return buildArgumentPredicate(a, handler, m, argumentIndex); + } } diff --git a/src/main/java/com/ning/timebox/ruby/RubyPredicator.java b/src/main/java/com/ning/timebox/ruby/RubyPredicator.java index 2b06adb..438091c 100644 --- a/src/main/java/com/ning/timebox/ruby/RubyPredicator.java +++ b/src/main/java/com/ning/timebox/ruby/RubyPredicator.java @@ -58,4 +58,13 @@ public boolean test(Object arg) } }; } + + public Predicate buildGatherPredicate(Annotation a, + Object handler, + Method m, + Class expectedType, + int argumentIndex) + { + return buildArgumentPredicate(a, handler, m, argumentIndex); + } } diff --git a/src/test/java/com/ning/timebox/TestGather.java b/src/test/java/com/ning/timebox/TestGather.java new file mode 100644 index 0000000..67d4be7 --- /dev/null +++ b/src/test/java/com/ning/timebox/TestGather.java @@ -0,0 +1,166 @@ +package com.ning.timebox; + +import com.ning.timebox.clojure.CLJ; +import com.ning.timebox.ruby.Rb; +import junit.framework.TestCase; + +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + + +public class TestGather extends TestCase +{ + public void testFoo() throws Exception + { + final AtomicInteger flag = new AtomicInteger(0); + final TimeBox box = new TimeBox(new Object() + { + @Priority(3) + public void best(@Gather Collection dogs) + { + flag.set(dogs.size()); + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() + { + public void run() + { + box.provide(new Dog("Bean")); + box.provide(new Dog("Bouncer")); + latch.countDown(); + } + }).start(); + latch.await(); + + assertTrue(box.react(100, TimeUnit.MILLISECONDS)); + assertEquals(2, flag.get()); + } + + public void testBar() throws Exception + { + final AtomicInteger flag = new AtomicInteger(0); + final TimeBox box = new TimeBox(new Object() + { + @Priority(3) + public void collectPuppies(@Gather @Rb("|d| d.age < 2") Collection dogs) + { + flag.set(dogs.size()); + } + + public Boolean isBouncer(Dog dog) + { + return "Bouncer".equals(dog.getName()); + } + + }); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() + { + public void run() + { + box.provide(new Dog("Bean", 14)); + box.provide(new Dog("Mac", 1)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertTrue(box.react(100, TimeUnit.MILLISECONDS)); + assertEquals(1, flag.get()); + } + + public void testGuardWithRuby() throws Exception + { + final AtomicInteger flag = new AtomicInteger(0); + final TimeBox box = new TimeBox(new Object() + { + @Priority(3) + public void collectPuppies(@Gather @Rb("|d| d.age < 2") Collection dogs) + { + flag.set(dogs.size()); + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() + { + public void run() + { + box.provide(new Dog("Bean", 14)); + box.provide(new Dog("Mac", 1)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertTrue(box.react(100, TimeUnit.MILLISECONDS)); + assertEquals(1, flag.get()); + } + + public void testGuardWithClojure() throws Exception + { + final AtomicInteger flag = new AtomicInteger(0); + final TimeBox box = new TimeBox(new Object() + { + @Priority(3) + public void collectPuppies(@Gather @CLJ("#(< (.getAge %) 2)") Collection dogs) + { + flag.set(dogs.size()); + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() + { + public void run() + { + box.provide(new Dog("Bean", 14)); + box.provide(new Dog("Mac", 1)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertTrue(box.react(100, TimeUnit.MILLISECONDS)); + assertEquals(1, flag.get()); + } + + public void testGuardWithGuardMethod() throws Exception + { + final AtomicInteger flag = new AtomicInteger(0); + final TimeBox box = new TimeBox(new Object() + { + @Priority(3) + public void collectPuppies(@Gather @GuardMethod("isPuppy") Collection dogs) + { + flag.set(dogs.size()); + } + + public Boolean isPuppy(Dog dog) + { + return dog.getAge() < 2; + } + + }); + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() + { + public void run() + { + box.provide(new Dog("Bean", 14)); + box.provide(new Dog("Mac", 1)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertTrue(box.react(100, TimeUnit.MILLISECONDS)); + assertEquals(1, flag.get()); + } +}