Skip to content

Commit b08d638

Browse files
committed
8262503: Support records in Dynalink
Reviewed-by: sundar
1 parent 21e7402 commit b08d638

File tree

8 files changed

+159
-12
lines changed

8 files changed

+159
-12
lines changed

src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
114114
this.assignableGuard = assignableGuard;
115115

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

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

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

210211
/**

src/jdk.dynalink/share/classes/jdk/dynalink/beans/AccessibleMembersLookup.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ Class<?>[] getInnerClasses() {
101101
return innerClasses.values().toArray(new Class<?>[0]);
102102
}
103103

104+
Method getAccessibleMethod(final Method m) {
105+
return methods.get(new MethodSignature(m));
106+
}
107+
104108
/**
105109
* A helper class that represents a method signature - name and argument types.
106110
*/

src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanIntrospector.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,47 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
6161
package jdk.dynalink.beans;
6262

6363
import java.lang.invoke.MethodHandle;
64-
import java.util.Collections;
64+
import java.lang.reflect.Method;
65+
import java.lang.reflect.RecordComponent;
66+
import java.security.AccessController;
67+
import java.security.PrivilegedAction;
68+
import java.util.Arrays;
69+
import java.util.Collection;
70+
import java.util.List;
6571
import java.util.Map;
72+
import java.util.Objects;
6673

6774
class BeanIntrospector extends FacetIntrospector {
75+
private final Class<?> clazz;
76+
6877
BeanIntrospector(final Class<?> clazz) {
6978
super(clazz, true);
79+
this.clazz = clazz;
7080
}
7181

7282
@Override
7383
Map<String, MethodHandle> getInnerClassGetters() {
74-
return Collections.emptyMap(); // NOTE: non-static inner classes are also on StaticClassIntrospector.
84+
return Map.of(); // NOTE: non-static inner classes are also on StaticClassIntrospector.
85+
}
86+
87+
@Override Collection<Method> getRecordComponentGetters() {
88+
if (clazz.isRecord()) {
89+
try {
90+
// Need to use doPrivileged as getRecordComponents is rather strict.
91+
final RecordComponent[] rcs = AccessController.doPrivileged(
92+
(PrivilegedAction<RecordComponent[]>) clazz::getRecordComponents);
93+
return Arrays.stream(rcs)
94+
.map(RecordComponent::getAccessor)
95+
.map(membersLookup::getAccessibleMethod)
96+
.filter(Objects::nonNull) // no accessible counterpart
97+
.toList();
98+
} catch (SecurityException e) {
99+
// We couldn't execute getRecordComponents.
100+
return List.of();
101+
}
102+
} else {
103+
return List.of();
104+
}
75105
}
76106

77107
@Override

src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeansLinker.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
7878
* calls to all objects that no other linker recognized. Specifically, this
7979
* linker will:
8080
* <ul>
81+
* <li>if the object is a {@link java.lang.Record record}, expose all public accessors of
82+
* record components as property getters for {@link StandardOperation#GET} operations
83+
* in the {@link StandardNamespace#PROPERTY} namespace;</li>
8184
* <li>expose all public methods of form {@code setXxx()}, {@code getXxx()},
8285
* and {@code isXxx()} as property setters and getters for
8386
* {@link StandardOperation#SET} and {@link StandardOperation#GET} operations in the
84-
* {@link StandardNamespace#PROPERTY} namespace;</li>
87+
* {@link StandardNamespace#PROPERTY} namespace, except for getters for properties
88+
* with names already handled by record component getters;</li>
8589
* <li>expose all public methods for retrieval for
8690
* {@link StandardOperation#GET} operation in the {@link StandardNamespace#METHOD} namespace;
8791
* the methods thus retrieved can then be invoked using {@link StandardOperation#CALL}.</li>

src/jdk.dynalink/share/classes/jdk/dynalink/beans/CheckRestrictedPackage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ static boolean isRestrictedClass(final Class<?> clazz) {
9292
final String pkgName = name.substring(0, i);
9393
final Module module = clazz.getModule();
9494
if (module != null && !module.isExported(pkgName)) {
95+
// Classes in unexported packages of modules are always restricted
9596
return true;
9697
}
9798

src/jdk.dynalink/share/classes/jdk/dynalink/beans/FacetIntrospector.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ abstract class FacetIntrospector {
9595
*/
9696
abstract Map<String, MethodHandle> getInnerClassGetters();
9797

98+
/**
99+
* Returns getter methods for record components.
100+
* @return getter methods for record components.
101+
*/
102+
abstract Collection<Method> getRecordComponentGetters();
103+
98104
/**
99105
* Returns the fields for the class facet.
100106
* @return the fields for the class facet.
@@ -141,7 +147,6 @@ Collection<Method> getMethods() {
141147
return membersLookup.getMethods();
142148
}
143149

144-
145150
MethodHandle unreflectGetter(final Field field) {
146151
return editMethodHandle(Lookup.PUBLIC.unreflectGetter(field));
147152
}

src/jdk.dynalink/share/classes/jdk/dynalink/beans/StaticClassIntrospector.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
6363
import java.lang.invoke.MethodHandle;
6464
import java.lang.invoke.MethodHandles;
6565
import java.lang.invoke.MethodType;
66+
import java.lang.reflect.Method;
67+
import java.util.Collection;
6668
import java.util.HashMap;
69+
import java.util.List;
6770
import java.util.Map;
6871

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

87+
@Override Collection<Method> getRecordComponentGetters() {
88+
return List.of();
89+
}
90+
8491
@Override
8592
MethodHandle editMethodHandle(final MethodHandle mh) {
8693
return editStaticMethodHandle(mh);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8262503
27+
* @summary Dynalink supports property getters for record components
28+
*/
29+
30+
import static jdk.dynalink.StandardNamespace.PROPERTY;
31+
import static jdk.dynalink.StandardOperation.GET;
32+
33+
import java.lang.invoke.MethodHandle;
34+
import java.lang.invoke.MethodHandles;
35+
import java.lang.invoke.MethodType;
36+
import java.util.List;
37+
import java.util.Set;
38+
import jdk.dynalink.CallSiteDescriptor;
39+
import jdk.dynalink.DynamicLinker;
40+
import jdk.dynalink.DynamicLinkerFactory;
41+
import jdk.dynalink.NoSuchDynamicMethodException;
42+
import jdk.dynalink.beans.BeansLinker;
43+
import jdk.dynalink.support.SimpleRelinkableCallSite;
44+
45+
public class BeanLinkerRecordsTest {
46+
public static record A(int num) {}
47+
48+
public interface I {
49+
int num();
50+
}
51+
52+
static record B(int num, int inaccessible) implements I {}
53+
54+
public static record C(int num) {
55+
public int num() { return 42; }
56+
public int getNum() { return 43; }
57+
}
58+
59+
public static void main(String[] args) throws Throwable {
60+
DynamicLinker linker = new DynamicLinkerFactory().createLinker();
61+
62+
MethodHandle get_num = linker.link(makePropertyGetter("num")).dynamicInvoker();
63+
64+
// Can access public record's component through its accessor
65+
assert(get_num.invoke(new A(100)).equals(100));
66+
67+
// Can access non-public record's component through accessor declared in public interface
68+
assert(get_num.invoke(new B(100, 200)).equals(100));
69+
70+
// Correctly selects overridden accessor; also ignores getXxx style getters
71+
assert(get_num.invoke(new C(100)).equals(42));
72+
73+
// Can not access non-public record's component without accessor declared in public interface
74+
MethodHandle get_inaccessible = linker.link(makePropertyGetter("inaccessible")).dynamicInvoker();
75+
try {
76+
get_inaccessible.invoke(new B(100, 200));
77+
throw new AssertionError(); // should've failed
78+
} catch (NoSuchDynamicMethodException e) {
79+
// This is expected
80+
}
81+
82+
// Record components show up in the list of readable instance property names.
83+
List.of(A.class, B.class, C.class).forEach(clazz -> {
84+
var propNames = BeansLinker.getReadableInstancePropertyNames(clazz);
85+
assert propNames.equals(Set.of("num", "class")): String.valueOf(propNames);
86+
});
87+
}
88+
89+
private static SimpleRelinkableCallSite makePropertyGetter(String name) {
90+
return new SimpleRelinkableCallSite(new CallSiteDescriptor(
91+
MethodHandles.publicLookup(),
92+
GET.withNamespace(PROPERTY).named(name),
93+
MethodType.methodType(Object.class, Object.class)));
94+
}
95+
}

0 commit comments

Comments
 (0)