Support getters from default methods in interfaces.
Fortunately this did not require using any JDK 1.8-specific APIs, just searching (via reflection) through the set of interfaces implemented by a class in addition to its super-classes. One downside is that the transitive set of all implemented interfaces could be much larger than the super-class chain, but this is the last thing we search, so in theory we only even get here if we were about to fail to find the getter entirely. This also means that our tests have to be compiled and run with Java 8, because I was not in the mood to figure out how to do all the complex Maven jockeying to have a special JDK8 profile and disable JDK8 tests in the normal profile and re-enable them in the JDK8 profile and blah blah blah, just shoot me now.
This commit is contained in:
+2
-3
@@ -3,9 +3,8 @@ sudo: false
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk7
|
- oraclejdk8
|
||||||
- openjdk7
|
- openjdk8
|
||||||
- openjdk6
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|||||||
@@ -91,6 +91,8 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<source>1.6</source>
|
<source>1.6</source>
|
||||||
<target>1.6</target>
|
<target>1.6</target>
|
||||||
|
<testSource>1.8</testSource>
|
||||||
|
<testTarget>1.8</testTarget>
|
||||||
<fork>true</fork>
|
<fork>true</fork>
|
||||||
<showDeprecation>true</showDeprecation>
|
<showDeprecation>true</showDeprecation>
|
||||||
<showWarnings>true</showWarnings>
|
<showWarnings>true</showWarnings>
|
||||||
@@ -101,6 +103,7 @@
|
|||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.felix</groupId>
|
<groupId>org.apache.felix</groupId>
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
@@ -115,6 +118,7 @@
|
|||||||
</instructions>
|
</instructions>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<!-- disabled due to src/main/gwt related hoseage
|
<!-- disabled due to src/main/gwt related hoseage
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
@@ -132,6 +136,7 @@
|
|||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
@@ -140,6 +145,7 @@
|
|||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
@@ -148,6 +154,7 @@
|
|||||||
<includes><include>**/*Test.java</include></includes>
|
<includes><include>**/*Test.java</include></includes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-javadoc-plugin</artifactId>
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
@@ -158,6 +165,7 @@
|
|||||||
<additionalparam>-Xdoclint:all -Xdoclint:-missing</additionalparam>
|
<additionalparam>-Xdoclint:all -Xdoclint:-missing</additionalparam>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
@@ -184,6 +192,7 @@
|
|||||||
<maxmem>256m</maxmem>
|
<maxmem>256m</maxmem>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.eluder.coveralls</groupId>
|
<groupId>org.eluder.coveralls</groupId>
|
||||||
<artifactId>coveralls-maven-plugin</artifactId>
|
<artifactId>coveralls-maven-plugin</artifactId>
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import java.lang.reflect.Method;
|
|||||||
|
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,6 +43,7 @@ public class DefaultCollector extends BasicCollector
|
|||||||
Mustache.VariableFetcher fetcher = super.createFetcher(ctx, name);
|
Mustache.VariableFetcher fetcher = super.createFetcher(ctx, name);
|
||||||
if (fetcher != null) return fetcher;
|
if (fetcher != null) return fetcher;
|
||||||
|
|
||||||
|
// first check for a getter which provides the value
|
||||||
Class<?> cclass = ctx.getClass();
|
Class<?> cclass = ctx.getClass();
|
||||||
final Method m = getMethod(cclass, name);
|
final Method m = getMethod(cclass, name);
|
||||||
if (m != null) {
|
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);
|
final Field f = getField(cclass, name);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
return new Mustache.VariableFetcher() {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +84,38 @@ public class DefaultCollector extends BasicCollector
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Method getMethod (Class<?> clazz, String name) {
|
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<Class<?>> ifaces = new LinkedHashSet<Class<?>>();
|
||||||
|
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<Class<?>> 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;
|
Method m;
|
||||||
try {
|
try {
|
||||||
m = clazz.getDeclaredMethod(name);
|
m = clazz.getDeclaredMethod(name);
|
||||||
if (!m.getReturnType().equals(void.class)) {
|
if (!m.getReturnType().equals(void.class)) return makeAccessible(m);
|
||||||
if (!m.isAccessible()) {
|
|
||||||
m.setAccessible(true);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
@@ -85,12 +123,7 @@ public class DefaultCollector extends BasicCollector
|
|||||||
String upperName = Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
String upperName = Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
||||||
try {
|
try {
|
||||||
m = clazz.getDeclaredMethod("get" + upperName);
|
m = clazz.getDeclaredMethod("get" + upperName);
|
||||||
if (!m.getReturnType().equals(void.class)) {
|
if (!m.getReturnType().equals(void.class)) return makeAccessible(m);
|
||||||
if (!m.isAccessible()) {
|
|
||||||
m.setAccessible(true);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
@@ -98,23 +131,19 @@ public class DefaultCollector extends BasicCollector
|
|||||||
try {
|
try {
|
||||||
m = clazz.getDeclaredMethod("is" + upperName);
|
m = clazz.getDeclaredMethod("is" + upperName);
|
||||||
if (m.getReturnType().equals(boolean.class) ||
|
if (m.getReturnType().equals(boolean.class) ||
|
||||||
m.getReturnType().equals(Boolean.class)) {
|
m.getReturnType().equals(Boolean.class)) return makeAccessible(m);
|
||||||
if (!m.isAccessible()) {
|
|
||||||
m.setAccessible(true);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// fall through
|
// fall through
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> sclass = clazz.getSuperclass();
|
|
||||||
if (sclass != Object.class && sclass != null) {
|
|
||||||
return getMethod(clazz.getSuperclass(), name);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Method makeAccessible (Method m) {
|
||||||
|
if (!m.isAccessible()) m.setAccessible(true);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
protected Field getField (Class<?> clazz, String name) {
|
protected Field getField (Class<?> clazz, String name) {
|
||||||
Field f;
|
Field f;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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 public void testPropertyVariable () {
|
||||||
test("bar", "{{foo}}", new Object() {
|
test("bar", "{{foo}}", new Object() {
|
||||||
String getFoo () { return "bar"; }
|
String getFoo () { return "bar"; }
|
||||||
|
|||||||
Reference in New Issue
Block a user