Skip to content

Commit dd7d545

Browse files
committed
svm: Add JVMCIReflectionUtil
1 parent 5bfb6cf commit dd7d545

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright (c) 2025, 2025, 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. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.util;
26+
27+
import java.util.Arrays;
28+
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.stream.Collectors;
31+
32+
import jdk.graal.compiler.debug.GraalError;
33+
import jdk.vm.ci.meta.JavaType;
34+
import jdk.vm.ci.meta.MetaAccessProvider;
35+
import jdk.vm.ci.meta.ResolvedJavaField;
36+
import jdk.vm.ci.meta.ResolvedJavaMethod;
37+
import jdk.vm.ci.meta.ResolvedJavaType;
38+
39+
/**
40+
* This class contains utility methods for commonly used reflection functionality based on JVMCI
41+
* reflection (i.e. {@code jdk.vm.ci.meta}) as opposed to core reflection (i.e.
42+
* {@code java.lang.reflect}).
43+
*/
44+
public final class JVMCIReflectionUtil {
45+
46+
/**
47+
* Gets the method declared by {@code declaringClass} named {@code name}. Like
48+
* {@link Class#getDeclaredMethod(String, Class...)}, this does not consider super classes or
49+
* interfaces.
50+
*
51+
* @param optional when {@code true}, an exception will be thrown if the method does not exist
52+
* @param declaringClass the class in which to look up the method
53+
* @param name the name of the method to look up
54+
* @param parameterTypes the parameter types of the method to look up
55+
* @return the resolved Java method object or {@code null} if no such method exits and
56+
* {@code optional} is {@code true}
57+
* @throws GraalError if multiple methods with the same name and signature exist in the
58+
* declaring class
59+
* @throws NoSuchMethodError if no such method exists and {@code optional} is {@code false}
60+
*/
61+
public static ResolvedJavaMethod getDeclaredMethod(boolean optional, ResolvedJavaType declaringClass, String name, ResolvedJavaType... parameterTypes) {
62+
var result = findMethod(declaringClass, declaringClass.getDeclaredMethods(false), name, parameterTypes);
63+
if (!optional && result == null) {
64+
throw new NoSuchMethodError("No method found for %s.%s(%s)".formatted(
65+
declaringClass.toClassName(),
66+
name,
67+
Arrays.stream(parameterTypes).map(ResolvedJavaType::toClassName).collect(Collectors.joining(", "))));
68+
}
69+
return result;
70+
}
71+
72+
/**
73+
* Shortcut for
74+
* {@link #getDeclaredMethod(boolean, ResolvedJavaType, String, ResolvedJavaType...)} that
75+
* converts the {@link Class} parameters to {@link ResolvedJavaType} using the provided
76+
* {@link MetaAccessProvider}.
77+
*/
78+
public static ResolvedJavaMethod getDeclaredMethod(boolean optional, MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, String name, Class<?>... parameterTypes) {
79+
var parameterJavaTypes = Arrays.stream(parameterTypes).map(metaAccess::lookupJavaType).toArray(ResolvedJavaType[]::new);
80+
return getDeclaredMethod(optional, declaringClass, name, parameterJavaTypes);
81+
}
82+
83+
/**
84+
* Shortcut for
85+
* {@link #getDeclaredMethod(boolean, MetaAccessProvider, ResolvedJavaType, String, Class...)}
86+
* with {@code optional} set to {@code false}.
87+
*/
88+
public static ResolvedJavaMethod getDeclaredMethod(MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, String name, Class<?>... parameterTypes) {
89+
return getDeclaredMethod(false, metaAccess, declaringClass, name, parameterTypes);
90+
}
91+
92+
/**
93+
* Gets the constructor declared by {@code declaringClass}. Like
94+
* {@link Class#getDeclaredConstructor(Class...)}, this does not consider super classes.
95+
*
96+
* @param optional when {@code true}, an exception will be thrown if the method does not exist
97+
* @param declaringClass the class in which to look up the constructor
98+
* @param parameterTypes the parameter types of the constructor to look up
99+
* @return the {@linkplain ResolvedJavaMethod resolved Java method} object representing the
100+
* requested constructor or {@code null} if no such constructor exits and
101+
* {@code optional} is {@code true}
102+
* @throws GraalError if multiple constructors with the same name and signature exist in the
103+
* declaring class
104+
* @throws NoSuchMethodError if no such constructor exists and {@code optional} is {@code false}
105+
*/
106+
public static ResolvedJavaMethod getDeclaredConstructor(boolean optional, ResolvedJavaType declaringClass, ResolvedJavaType... parameterTypes) {
107+
String name = "<init>";
108+
var result = findMethod(declaringClass, declaringClass.getDeclaredConstructors(false), name, parameterTypes);
109+
if (!optional && result == null) {
110+
throw new NoSuchMethodError("No constructor found for %s.%s(%s)".formatted(
111+
declaringClass.toClassName(),
112+
name,
113+
Arrays.stream(parameterTypes).map(ResolvedJavaType::toClassName).collect(Collectors.joining(", "))));
114+
}
115+
return result;
116+
}
117+
118+
/**
119+
* Shortcut for {@link #getDeclaredConstructor(boolean, ResolvedJavaType, ResolvedJavaType...)}
120+
* that converts the {@link Class} parameters to {@link ResolvedJavaType} using the provided
121+
* {@link MetaAccessProvider}.
122+
*/
123+
public static ResolvedJavaMethod getDeclaredConstructor(boolean optional, MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, Class<?>... parameterTypes) {
124+
var parameterJavaTypes = Arrays.stream(parameterTypes).map(metaAccess::lookupJavaType).toArray(ResolvedJavaType[]::new);
125+
return getDeclaredConstructor(optional, declaringClass, parameterJavaTypes);
126+
}
127+
128+
/**
129+
* Shortcut for
130+
* {@link #getDeclaredConstructor(boolean, MetaAccessProvider, ResolvedJavaType, Class...)} with
131+
* {@code optional} set to {@code false}.
132+
*/
133+
public static ResolvedJavaMethod getDeclaredConstructor(MetaAccessProvider metaAccess, ResolvedJavaType declaringClass, Class<?>... parameterTypes) {
134+
return getDeclaredConstructor(false, metaAccess, declaringClass, parameterTypes);
135+
}
136+
137+
private static ResolvedJavaMethod findMethod(ResolvedJavaType declaringClass, ResolvedJavaMethod[] methods, String name, ResolvedJavaType... parameterTypes) {
138+
ResolvedJavaMethod res = null;
139+
for (ResolvedJavaMethod m : methods) {
140+
if (!m.getName().equals(name)) {
141+
continue;
142+
}
143+
// ignore receiver type for comparison
144+
JavaType[] parameterList = m.getSignature().toParameterTypes(null);
145+
if (!Arrays.equals(parameterTypes, parameterList)) {
146+
continue;
147+
}
148+
if (res == null) {
149+
res = m;
150+
} else {
151+
throw new GraalError("More than one method with signature %s in %s", res.format("%h(%p)"), declaringClass.toClassName());
152+
}
153+
}
154+
return res;
155+
}
156+
157+
/**
158+
* Gets the field declared by {@code declaringClass} named {@code fieldName}. Like
159+
* {@link Class#getDeclaredField(String)}, this does not consider super classes or interfaces.
160+
* Unlike {@link Class#getDeclaredField(String)}, it does include
161+
* {@linkplain ResolvedJavaField#isInternal() internal} fields.
162+
*
163+
* @param optional when {@code true}, an exception will be thrown if the method does not exist
164+
* @param declaringClass the class in which to look up the field
165+
* @param fieldName the name of the field to look up
166+
* @return the resolved Java field object or {@code null} if no such field exists and
167+
* {@code optional} is {@code true}
168+
* @throws NoSuchFieldError if no field with the specified name exists in the declaring class
169+
* and {@code optional} is {@code false}
170+
* @throws GraalError if multiple fields with the same name exist in the declaring class
171+
*/
172+
public static ResolvedJavaField getDeclaredField(boolean optional, ResolvedJavaType declaringClass, String fieldName) {
173+
ResolvedJavaField[][] allFields = {declaringClass.getStaticFields(), declaringClass.getInstanceFields(false)};
174+
ResolvedJavaField found = null;
175+
for (ResolvedJavaField[] fields : allFields) {
176+
for (ResolvedJavaField field : fields) {
177+
if (field.getName().equals(fieldName)) {
178+
if (found != null) {
179+
throw new GraalError("More than one field named %s in %s", fieldName, declaringClass.toClassName());
180+
}
181+
found = field;
182+
}
183+
}
184+
}
185+
if (!optional && found == null) {
186+
throw new NoSuchFieldError(declaringClass.toClassName() + "." + fieldName);
187+
}
188+
return found;
189+
}
190+
191+
/**
192+
* Shortcut for {@link #getDeclaredField(boolean, ResolvedJavaType, String)} with
193+
* {@code optional} set to {@code false}.
194+
*/
195+
public static ResolvedJavaField getDeclaredField(ResolvedJavaType declaringClass, String fieldName) {
196+
return getDeclaredField(false, declaringClass, fieldName);
197+
}
198+
199+
/**
200+
* Returns a list containing all fields present within this type, including
201+
* {@linkplain ResolvedJavaField#isInternal() internal} fields. The returned List is
202+
* unmodifiable; calls to any mutator method will always cause
203+
* {@code UnsupportedOperationException} to be thrown.
204+
*/
205+
public static List<ResolvedJavaField> getAllFields(ResolvedJavaType declaringClass) {
206+
ResolvedJavaField[] staticFields = declaringClass.getStaticFields();
207+
ResolvedJavaField[] instanceFields = declaringClass.getInstanceFields(false);
208+
ResolvedJavaField[] allFields = new ResolvedJavaField[staticFields.length + instanceFields.length];
209+
System.arraycopy(staticFields, 0, allFields, 0, staticFields.length);
210+
System.arraycopy(instanceFields, 0, allFields, staticFields.length, instanceFields.length);
211+
return Collections.unmodifiableList(Arrays.asList(allFields));
212+
}
213+
}

0 commit comments

Comments
 (0)