Skip to content

Commit

Permalink
8262503: Support records in Dynalink
Browse files Browse the repository at this point in the history
Reviewed-by: sundar
  • Loading branch information
szegedi committed Mar 30, 2021
1 parent 21e7402 commit b08d638
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
this.assignableGuard = assignableGuard;

final FacetIntrospector introspector = createFacetIntrospector();
// Add record component getters
for (final Method rcg: introspector.getRecordComponentGetters()) {
setPropertyGetter(rcg, 0);
}
// Add methods and properties
for(final Method method: introspector.getMethods()) {
final String name = method.getName();
Expand All @@ -137,9 +141,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
for(final Field field: introspector.getFields()) {
final String name = field.getName();
// Only add a property getter when one is not defined already as a getXxx()/isXxx() method.
if(!propertyGetters.containsKey(name)) {
setPropertyGetter(name, introspector.unreflectGetter(field), ValidationType.EXACT_CLASS);
}
setPropertyGetter(name, introspector.unreflectGetter(field), ValidationType.EXACT_CLASS);
if(!(Modifier.isFinal(field.getModifiers()) || propertySetters.containsKey(name))) {
addMember(name, new SimpleDynamicMethod(introspector.unreflectSetter(field), clazz, name),
propertySetters);
Expand All @@ -148,10 +150,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {

// Add inner classes, but only those for which we don't hide a property with it
for(final Map.Entry<String, MethodHandle> innerClassSpec: introspector.getInnerClassGetters().entrySet()) {
final String name = innerClassSpec.getKey();
if(!propertyGetters.containsKey(name)) {
setPropertyGetter(name, innerClassSpec.getValue(), ValidationType.EXACT_CLASS);
}
setPropertyGetter(innerClassSpec.getKey(), innerClassSpec.getValue(), ValidationType.EXACT_CLASS);
}
}

Expand Down Expand Up @@ -204,7 +203,9 @@ private static Set<String> getUnmodifiableKeys(final Map<String, ?> m) {
* @param validationType the validation type for the property
*/
private void setPropertyGetter(final String name, final SingleDynamicMethod handle, final ValidationType validationType) {
propertyGetters.put(name, new AnnotatedDynamicMethod(handle, validationType));
if (!propertyGetters.containsKey(name)) {
propertyGetters.put(name, new AnnotatedDynamicMethod(handle, validationType));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ Class<?>[] getInnerClasses() {
return innerClasses.values().toArray(new Class<?>[0]);
}

Method getAccessibleMethod(final Method m) {
return methods.get(new MethodSignature(m));
}

/**
* A helper class that represents a method signature - name and argument types.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,47 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
package jdk.dynalink.beans;

import java.lang.invoke.MethodHandle;
import java.util.Collections;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;

class BeanIntrospector extends FacetIntrospector {
private final Class<?> clazz;

BeanIntrospector(final Class<?> clazz) {
super(clazz, true);
this.clazz = clazz;
}

@Override
Map<String, MethodHandle> getInnerClassGetters() {
return Collections.emptyMap(); // NOTE: non-static inner classes are also on StaticClassIntrospector.
return Map.of(); // NOTE: non-static inner classes are also on StaticClassIntrospector.
}

@Override Collection<Method> getRecordComponentGetters() {
if (clazz.isRecord()) {
try {
// Need to use doPrivileged as getRecordComponents is rather strict.
final RecordComponent[] rcs = AccessController.doPrivileged(
(PrivilegedAction<RecordComponent[]>) clazz::getRecordComponents);
return Arrays.stream(rcs)
.map(RecordComponent::getAccessor)
.map(membersLookup::getAccessibleMethod)
.filter(Objects::nonNull) // no accessible counterpart
.toList();
} catch (SecurityException e) {
// We couldn't execute getRecordComponents.
return List.of();
}
} else {
return List.of();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,14 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* calls to all objects that no other linker recognized. Specifically, this
* linker will:
* <ul>
* <li>if the object is a {@link java.lang.Record record}, expose all public accessors of
* record components as property getters for {@link StandardOperation#GET} operations
* in the {@link StandardNamespace#PROPERTY} namespace;</li>
* <li>expose all public methods of form {@code setXxx()}, {@code getXxx()},
* and {@code isXxx()} as property setters and getters for
* {@link StandardOperation#SET} and {@link StandardOperation#GET} operations in the
* {@link StandardNamespace#PROPERTY} namespace;</li>
* {@link StandardNamespace#PROPERTY} namespace, except for getters for properties
* with names already handled by record component getters;</li>
* <li>expose all public methods for retrieval for
* {@link StandardOperation#GET} operation in the {@link StandardNamespace#METHOD} namespace;
* the methods thus retrieved can then be invoked using {@link StandardOperation#CALL}.</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ static boolean isRestrictedClass(final Class<?> clazz) {
final String pkgName = name.substring(0, i);
final Module module = clazz.getModule();
if (module != null && !module.isExported(pkgName)) {
// Classes in unexported packages of modules are always restricted
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ abstract class FacetIntrospector {
*/
abstract Map<String, MethodHandle> getInnerClassGetters();

/**
* Returns getter methods for record components.
* @return getter methods for record components.
*/
abstract Collection<Method> getRecordComponentGetters();

/**
* Returns the fields for the class facet.
* @return the fields for the class facet.
Expand Down Expand Up @@ -141,7 +147,6 @@ Collection<Method> getMethods() {
return membersLookup.getMethods();
}


MethodHandle unreflectGetter(final Field field) {
return editMethodHandle(Lookup.PUBLIC.unreflectGetter(field));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class StaticClassIntrospector extends FacetIntrospector {
Expand All @@ -81,6 +84,10 @@ Map<String, MethodHandle> getInnerClassGetters() {
return map;
}

@Override Collection<Method> getRecordComponentGetters() {
return List.of();
}

@Override
MethodHandle editMethodHandle(final MethodHandle mh) {
return editStaticMethodHandle(mh);
Expand Down
95 changes: 95 additions & 0 deletions test/jdk/jdk/dynalink/BeanLinkerRecordsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8262503
* @summary Dynalink supports property getters for record components
*/

import static jdk.dynalink.StandardNamespace.PROPERTY;
import static jdk.dynalink.StandardOperation.GET;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.List;
import java.util.Set;
import jdk.dynalink.CallSiteDescriptor;
import jdk.dynalink.DynamicLinker;
import jdk.dynalink.DynamicLinkerFactory;
import jdk.dynalink.NoSuchDynamicMethodException;
import jdk.dynalink.beans.BeansLinker;
import jdk.dynalink.support.SimpleRelinkableCallSite;

public class BeanLinkerRecordsTest {
public static record A(int num) {}

public interface I {
int num();
}

static record B(int num, int inaccessible) implements I {}

public static record C(int num) {
public int num() { return 42; }
public int getNum() { return 43; }
}

public static void main(String[] args) throws Throwable {
DynamicLinker linker = new DynamicLinkerFactory().createLinker();

MethodHandle get_num = linker.link(makePropertyGetter("num")).dynamicInvoker();

// Can access public record's component through its accessor
assert(get_num.invoke(new A(100)).equals(100));

// Can access non-public record's component through accessor declared in public interface
assert(get_num.invoke(new B(100, 200)).equals(100));

// Correctly selects overridden accessor; also ignores getXxx style getters
assert(get_num.invoke(new C(100)).equals(42));

// Can not access non-public record's component without accessor declared in public interface
MethodHandle get_inaccessible = linker.link(makePropertyGetter("inaccessible")).dynamicInvoker();
try {
get_inaccessible.invoke(new B(100, 200));
throw new AssertionError(); // should've failed
} catch (NoSuchDynamicMethodException e) {
// This is expected
}

// Record components show up in the list of readable instance property names.
List.of(A.class, B.class, C.class).forEach(clazz -> {
var propNames = BeansLinker.getReadableInstancePropertyNames(clazz);
assert propNames.equals(Set.of("num", "class")): String.valueOf(propNames);
});
}

private static SimpleRelinkableCallSite makePropertyGetter(String name) {
return new SimpleRelinkableCallSite(new CallSiteDescriptor(
MethodHandles.publicLookup(),
GET.withNamespace(PROPERTY).named(name),
MethodType.methodType(Object.class, Object.class)));
}
}

1 comment on commit b08d638

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.