diff --git a/.travis.yml b/.travis.yml index e313ff1..d3cea56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,8 @@ sudo: false language: java jdk: - - oraclejdk7 - - openjdk7 - - openjdk6 + - oraclejdk8 + - openjdk8 cache: directories: diff --git a/pom.xml b/pom.xml index 271c906..9aa85d9 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,8 @@ 1.6 1.6 + 1.8 + 1.8 true true true @@ -101,6 +103,7 @@ + org.apache.felix maven-bundle-plugin @@ -115,6 +118,7 @@ + + org.apache.maven.plugins maven-resources-plugin @@ -140,6 +145,7 @@ UTF-8 + org.apache.maven.plugins maven-surefire-plugin @@ -148,6 +154,7 @@ **/*Test.java + org.apache.maven.plugins maven-javadoc-plugin @@ -158,6 +165,7 @@ -Xdoclint:all -Xdoclint:-missing + org.apache.maven.plugins maven-jar-plugin @@ -184,6 +192,7 @@ 256m + org.eluder.coveralls coveralls-maven-plugin diff --git a/src/main/java/com/samskivert/mustache/DefaultCollector.java b/src/main/java/com/samskivert/mustache/DefaultCollector.java index 13d1eec..9e3a8f6 100644 --- a/src/main/java/com/samskivert/mustache/DefaultCollector.java +++ b/src/main/java/com/samskivert/mustache/DefaultCollector.java @@ -10,7 +10,9 @@ import java.lang.reflect.Method; import java.util.AbstractList; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -41,6 +43,7 @@ public class DefaultCollector extends BasicCollector Mustache.VariableFetcher fetcher = super.createFetcher(ctx, name); if (fetcher != null) return fetcher; + // first check for a getter which provides the value Class cclass = ctx.getClass(); final Method m = getMethod(cclass, name); if (m != null) { @@ -51,6 +54,7 @@ public class DefaultCollector extends BasicCollector }; } + // next check for a getter which provides the value final Field f = getField(cclass, name); if (f != null) { return new Mustache.VariableFetcher() { @@ -60,6 +64,17 @@ public class DefaultCollector extends BasicCollector }; } + // finally check for a default interface method which provides the value (this is left to + // last because it's much more expensive and hopefully something already matched above) + final Method im = getIfaceMethod(cclass, name); + if (im != null) { + return new Mustache.VariableFetcher() { + public Object get (Object ctx, String name) throws Exception { + return im.invoke(ctx); + } + }; + } + return null; } @@ -69,15 +84,38 @@ public class DefaultCollector extends BasicCollector } protected Method getMethod (Class clazz, String name) { + // first check up the superclass chain + for (Class cc = clazz; cc != null && cc != Object.class; cc = cc.getSuperclass()) { + Method m = getMethodOn(cc, name); + if (m != null) return m; + } + return null; + } + + protected Method getIfaceMethod (Class clazz, String name) { + // enumerate the transitive closure of all interfaces implemented by clazz + Set> ifaces = new LinkedHashSet>(); + for (Class cc = clazz; cc != null && cc != Object.class; cc = cc.getSuperclass()) { + addIfaces(ifaces, cc, false); + } + // now search those in the order that we found them + for (Class iface : ifaces) { + Method m = getMethodOn(iface, name); + if (m != null) return m; + } + return null; + } + + private void addIfaces (Set> ifaces, Class clazz, boolean isIface) { + if (isIface) ifaces.add(clazz); + for (Class iface : clazz.getInterfaces()) addIfaces(ifaces, iface, true); + } + + protected Method getMethodOn (Class clazz, String name) { Method m; try { m = clazz.getDeclaredMethod(name); - if (!m.getReturnType().equals(void.class)) { - if (!m.isAccessible()) { - m.setAccessible(true); - } - return m; - } + if (!m.getReturnType().equals(void.class)) return makeAccessible(m); } catch (Exception e) { // fall through } @@ -85,12 +123,7 @@ public class DefaultCollector extends BasicCollector String upperName = Character.toUpperCase(name.charAt(0)) + name.substring(1); try { m = clazz.getDeclaredMethod("get" + upperName); - if (!m.getReturnType().equals(void.class)) { - if (!m.isAccessible()) { - m.setAccessible(true); - } - return m; - } + if (!m.getReturnType().equals(void.class)) return makeAccessible(m); } catch (Exception e) { // fall through } @@ -98,23 +131,19 @@ public class DefaultCollector extends BasicCollector try { m = clazz.getDeclaredMethod("is" + upperName); if (m.getReturnType().equals(boolean.class) || - m.getReturnType().equals(Boolean.class)) { - if (!m.isAccessible()) { - m.setAccessible(true); - } - return m; - } + m.getReturnType().equals(Boolean.class)) return makeAccessible(m); } catch (Exception e) { // fall through } - Class sclass = clazz.getSuperclass(); - if (sclass != Object.class && sclass != null) { - return getMethod(clazz.getSuperclass(), name); - } return null; } + private Method makeAccessible (Method m) { + if (!m.isAccessible()) m.setAccessible(true); + return m; + } + protected Field getField (Class clazz, String name) { Field f; try { diff --git a/src/test/java/com/samskivert/mustache/MustacheTest.java b/src/test/java/com/samskivert/mustache/MustacheTest.java index 67c5881..c67968d 100644 --- a/src/test/java/com/samskivert/mustache/MustacheTest.java +++ b/src/test/java/com/samskivert/mustache/MustacheTest.java @@ -41,6 +41,22 @@ public class MustacheTest }); } + public interface HasDefault { + default String getFoo () { return "bar"; } + } + public interface Interloper extends HasDefault { + default String getFoo () { return "bang"; } + } + @Test public void testDefaultMethodVariable () { + test("bar", "{{foo}}", new HasDefault() { + }); + test("bang", "{{foo}}", new Interloper() { + }); + test("bong", "{{foo}}", new Interloper() { + public String getFoo () { return "bong"; } + }); + } + @Test public void testPropertyVariable () { test("bar", "{{foo}}", new Object() { String getFoo () { return "bar"; }