From 3196f6d5507aa854054a1c617a259ffc05f42534 Mon Sep 17 00:00:00 2001 From: Alex Bertram Date: Thu, 21 Jan 2016 14:07:15 +0100 Subject: [PATCH] (Partially) fixes #140: Allow access to getClass() and instance members of java.lang.Class This enables the code for example: import(java.util.HashMap) ageMap <- HashMap$new() print(ageMap$class$name) --- .../java/org/renjin/invoke/ClassBindings.java | 2 +- .../invoke/reflection/ClassBindingImpl.java | 3 +- .../reflection/ClassDefinitionBinding.java | 30 +++++++++++++++---- .../invoke/reflection/ClassBindingTest.java | 12 +++----- .../invoke/reflection/TypeConverterTest.java | 1 + .../java/org/renjin/primitives/JvmiTest.java | 10 +++++-- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/renjin/invoke/ClassBindings.java b/core/src/main/java/org/renjin/invoke/ClassBindings.java index 1407b50280..f8998c1f2f 100644 --- a/core/src/main/java/org/renjin/invoke/ClassBindings.java +++ b/core/src/main/java/org/renjin/invoke/ClassBindings.java @@ -9,7 +9,7 @@ public class ClassBindings { public static ClassBinding getClassDefinitionBinding(Class instance) { - return new ClassDefinitionBinding(ClassBindingImpl.get(instance)); + return new ClassDefinitionBinding(instance, ClassBindingImpl.get(instance)); } public static ClassBinding getClassBinding(Class aClass) { diff --git a/core/src/main/java/org/renjin/invoke/reflection/ClassBindingImpl.java b/core/src/main/java/org/renjin/invoke/reflection/ClassBindingImpl.java index f855b778ad..e3f934a0d1 100644 --- a/core/src/main/java/org/renjin/invoke/reflection/ClassBindingImpl.java +++ b/core/src/main/java/org/renjin/invoke/reflection/ClassBindingImpl.java @@ -47,8 +47,7 @@ private ClassBindingImpl(Class clazz) { for(Method method : clazz.getMethods()) { - if((method.getModifiers() & Modifier.PUBLIC) != 0 && - method.getDeclaringClass() != Object.class) { + if((method.getModifiers() & Modifier.PUBLIC) != 0) { if((method.getModifiers() & Modifier.STATIC) != 0 ) { staticMethods.put(Symbol.get(method.getName()), method); diff --git a/core/src/main/java/org/renjin/invoke/reflection/ClassDefinitionBinding.java b/core/src/main/java/org/renjin/invoke/reflection/ClassDefinitionBinding.java index 4d7732d762..72d88fd502 100644 --- a/core/src/main/java/org/renjin/invoke/reflection/ClassDefinitionBinding.java +++ b/core/src/main/java/org/renjin/invoke/reflection/ClassDefinitionBinding.java @@ -5,14 +5,25 @@ import org.renjin.invoke.ClassBinding; import org.renjin.sexp.Symbol; +/** + * Binding for an instance of type {@code java.lang.Class} + * + *

Provides bindings both for static methods of the class described by + * {@code classInstance}, as well as the methods and properties of the actual {@code java.lang.Class} object + * such as {@link Class#getName()} or {@link Class#hashCode()}

+ */ public class ClassDefinitionBinding implements ClassBinding { private static final Symbol NEW = Symbol.get("new"); + private Class classInstance; private final ClassBindingImpl classBinding; + private final ClassBindingImpl javaLangClassBinding; - public ClassDefinitionBinding(ClassBindingImpl classBinding) { + public ClassDefinitionBinding(Class classInstance, ClassBindingImpl classBinding) { + this.classInstance = classInstance; this.classBinding = classBinding; + this.javaLangClassBinding = ClassBindingImpl.get(Class.class); } @Override @@ -21,12 +32,21 @@ public MemberBinding getMemberBinding(Symbol name) { return classBinding.getConstructorBinding(); } + // First check to see if this symbol matches a static binding + // for the class being described, for example java.util.HashMap final MemberBinding staticMember = classBinding.getStaticMember(name); - if(staticMember == null) { - throw new EvalException("Class %s has no static member named '%s'", - classBinding.getBoundClass().getName(), name); + if(staticMember != null) { + return staticMember; } + + final MemberBinding instanceMember = javaLangClassBinding.getMemberBinding(name); + if(instanceMember != null) { + return instanceMember; + } + + throw new EvalException("Class %s has no static member named '%s', nor does java.lang.Class have an " + + "instance member named '%s'", + classBinding.getBoundClass().getName(), name, name); - return staticMember; } } diff --git a/core/src/test/java/org/renjin/invoke/reflection/ClassBindingTest.java b/core/src/test/java/org/renjin/invoke/reflection/ClassBindingTest.java index 2930fe67e2..b8a39608a7 100644 --- a/core/src/test/java/org/renjin/invoke/reflection/ClassBindingTest.java +++ b/core/src/test/java/org/renjin/invoke/reflection/ClassBindingTest.java @@ -1,15 +1,12 @@ package org.renjin.invoke.reflection; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; - import org.junit.Test; import org.renjin.EvalTestCase; import org.renjin.sexp.Symbol; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + public class ClassBindingTest extends EvalTestCase { @@ -38,8 +35,7 @@ public void bindingTest() { assertThat(binding.getMembers(), hasItems(Symbol.get("name"), Symbol.get("fooberize"))); - assertThat(binding.getMembers(), not(hasItem(Symbol.get("wait")))); - assertThat(binding.getMembers(), not(hasItem(Symbol.get("notifyAll")))); + assertThat(binding.getMembers(), not(hasItem(Symbol.get("foo")))); assertThat(binding.getMembers(), hasItem(Symbol.get("setFoo"))); diff --git a/core/src/test/java/org/renjin/invoke/reflection/TypeConverterTest.java b/core/src/test/java/org/renjin/invoke/reflection/TypeConverterTest.java index cd30d5089f..9cd8039cdc 100644 --- a/core/src/test/java/org/renjin/invoke/reflection/TypeConverterTest.java +++ b/core/src/test/java/org/renjin/invoke/reflection/TypeConverterTest.java @@ -80,4 +80,5 @@ public void specilConvertTest() { // assertThat(eval("if(instance$cakeConverted==cake) TRUE else FALSE"),logicalVectorOf(Logical.TRUE)); } + } diff --git a/core/src/test/java/org/renjin/primitives/JvmiTest.java b/core/src/test/java/org/renjin/primitives/JvmiTest.java index ba5a0c08f3..f12d77a3bc 100644 --- a/core/src/test/java/org/renjin/primitives/JvmiTest.java +++ b/core/src/test/java/org/renjin/primitives/JvmiTest.java @@ -127,7 +127,7 @@ public void sapplyOnLists() throws IOException { eval("import(org.renjin.primitives.MyBean)"); eval("x <- MyBean$new()"); - assertThat( eval("sapply(x$childBeans, function(x) x$count)"), equalTo(c_i(42,42))); + assertThat(eval("sapply(x$childBeans, function(x) x$count)"), equalTo(c_i(42, 42))); } @Test @@ -139,5 +139,11 @@ public void longIsNotMangled() { } - + @Test + public void classProperty() { + eval("import(java.util.HashMap)"); + eval("ageMap <- HashMap$new()"); + + assertThat(eval("ageMap$class$name"), equalTo(c("java.util.HashMap"))); + } }