Skip to content

Commit 1a45736

Browse files
committed
Add code contribution infrastructure
This commit adds an API that lets individual components contribute code, runtime hints, and protected access information. This ease the cases where code need to be written in a privileged package if necessary and let contributors provide hints for the code they generate. Closes gh-28030
1 parent d64f8c1 commit 1a45736

File tree

11 files changed

+867
-0
lines changed

11 files changed

+867
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.generator;
18+
19+
import org.springframework.aot.hint.RuntimeHints;
20+
import org.springframework.javapoet.support.MultiStatement;
21+
22+
/**
23+
* A code contribution that gathers the code, the {@linkplain RuntimeHints
24+
* runtime hints}, and the {@linkplain ProtectedElement protected elements}
25+
* that are necessary to execute it.
26+
*
27+
* @author Stephane Nicoll
28+
* @since 6.0
29+
*/
30+
public interface CodeContribution {
31+
32+
/**
33+
* Return the {@linkplain MultiStatement statements} that can be used to
34+
* append code.
35+
* @return the statements instance to use to contribute code
36+
*/
37+
MultiStatement statements();
38+
39+
/**
40+
* Return the {@linkplain RuntimeHints hints} to use to register
41+
* potential optimizations for contributed code.
42+
* @return the runtime hints
43+
*/
44+
RuntimeHints runtimeHints();
45+
46+
/**
47+
* Return the {@linkplain ProtectedAccess protected access} to use to
48+
* analyze any privileged access, if necessary.
49+
* @return the protected access
50+
*/
51+
ProtectedAccess protectedAccess();
52+
53+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.generator;
18+
19+
import org.springframework.aot.hint.RuntimeHints;
20+
import org.springframework.javapoet.support.MultiStatement;
21+
22+
/**
23+
* A default {@link CodeContribution} implementation.
24+
*
25+
* @author Stephane Nicoll
26+
* @since 6.0
27+
*/
28+
public class DefaultCodeContribution implements CodeContribution {
29+
30+
private final MultiStatement statements;
31+
32+
private final RuntimeHints runtimeHints;
33+
34+
private final ProtectedAccess protectedAccess;
35+
36+
37+
protected DefaultCodeContribution(MultiStatement statements, RuntimeHints runtimeHints,
38+
ProtectedAccess protectedAccess) {
39+
40+
this.statements = statements;
41+
this.runtimeHints = runtimeHints;
42+
this.protectedAccess = protectedAccess;
43+
}
44+
45+
/**
46+
* Create an instance with the {@link RuntimeHints} instance to use.
47+
* @param runtimeHints the runtime hints instance to use
48+
*/
49+
public DefaultCodeContribution(RuntimeHints runtimeHints) {
50+
this(new MultiStatement(), runtimeHints, new ProtectedAccess());
51+
}
52+
53+
@Override
54+
public MultiStatement statements() {
55+
return this.statements;
56+
}
57+
58+
@Override
59+
public RuntimeHints runtimeHints() {
60+
return this.runtimeHints;
61+
}
62+
63+
@Override
64+
public ProtectedAccess protectedAccess() {
65+
return this.protectedAccess;
66+
}
67+
68+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.generator;
18+
19+
import java.lang.reflect.Constructor;
20+
import java.lang.reflect.Executable;
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.Member;
23+
import java.lang.reflect.Method;
24+
import java.lang.reflect.Modifier;
25+
import java.util.ArrayList;
26+
import java.util.HashSet;
27+
import java.util.List;
28+
import java.util.Set;
29+
import java.util.function.Function;
30+
31+
import org.springframework.core.ResolvableType;
32+
import org.springframework.lang.Nullable;
33+
import org.springframework.util.ClassUtils;
34+
35+
/**
36+
* Gather the need of non-public access and determine the priviledged package
37+
* to use, if necessary.
38+
*
39+
* @author Stephane Nicoll
40+
* @since 6.0
41+
*/
42+
public class ProtectedAccess {
43+
44+
private final List<ProtectedElement> elements;
45+
46+
public ProtectedAccess() {
47+
this.elements = new ArrayList<>();
48+
}
49+
50+
/**
51+
* Specify whether the protected elements registered in this instance are
52+
* accessible from the specified package name.
53+
* @param packageName the target package name
54+
* @return {@code true} if the registered access can be safely used from
55+
* the specified package name
56+
*/
57+
public boolean isAccessible(String packageName) {
58+
return getProtectedElements(packageName).isEmpty();
59+
}
60+
61+
/**
62+
* Return the privileged package name to use for the specified package
63+
* name, or {@code null} if none is required.
64+
* @param packageName the target package name to use
65+
* @return the privileged package name to use, or {@code null}
66+
* @throws ProtectedAccessException if a single privileged package cannot
67+
* be identified
68+
* @see #isAccessible(String)
69+
*/
70+
@Nullable
71+
public String getPrivilegedPackageName(String packageName) throws ProtectedAccessException {
72+
List<ProtectedElement> protectedElements = getProtectedElements(packageName);
73+
if (protectedElements.isEmpty()) {
74+
return null;
75+
}
76+
List<String> packageNames = protectedElements.stream()
77+
.map(element -> element.getType().toClass().getPackageName())
78+
.distinct().toList();
79+
if (packageNames.size() == 1) {
80+
return packageNames.get(0);
81+
}
82+
throw new ProtectedAccessException("Multiple packages require a privileged access: "
83+
+ packageNames, protectedElements);
84+
}
85+
86+
private List<ProtectedElement> getProtectedElements(String packageName) {
87+
List<ProtectedElement> matches = new ArrayList<>();
88+
for (ProtectedElement element : this.elements) {
89+
if (!element.getType().toClass().getPackage().getName().equals(packageName)) {
90+
matches.add(element);
91+
}
92+
}
93+
return matches;
94+
}
95+
96+
/**
97+
* Analyze the specified {@linkplain ResolvableType type}, including its
98+
* full type signature.
99+
* @param type the type to analyze
100+
*/
101+
public void analyze(ResolvableType type) {
102+
if (isProtected(type)) {
103+
registerProtectedType(type, null);
104+
}
105+
}
106+
107+
/**
108+
* Analyze accessing the specified {@link Member} using the default
109+
* {@linkplain Options#DEFAULTS options}.
110+
* @param member the member to analyze
111+
*/
112+
public void analyze(Member member) {
113+
analyze(member, Options.DEFAULTS);
114+
}
115+
116+
/**
117+
* Analyze accessing the specified {@link Member} using the specified
118+
* {@link Options options}.
119+
* @param member the member to analyze
120+
* @param options the options to use
121+
*/
122+
public void analyze(Member member, Options options) {
123+
if (isProtected(member.getDeclaringClass())) {
124+
registerProtectedType(member.getDeclaringClass(), member);
125+
}
126+
if (!options.useReflection && isProtected(member.getModifiers())) {
127+
registerProtectedType(member.getDeclaringClass(), member);
128+
}
129+
if (member instanceof Field field) {
130+
ResolvableType fieldType = ResolvableType.forField(field);
131+
if (options.assignReturnType && isProtected(fieldType)) {
132+
registerProtectedType(fieldType, field);
133+
}
134+
}
135+
else if (member instanceof Constructor<?> constructor) {
136+
analyzeParameterTypes(constructor, i ->
137+
ResolvableType.forConstructorParameter(constructor, i));
138+
}
139+
else if (member instanceof Method method) {
140+
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
141+
if (!options.assignReturnType && isProtected(returnType)) {
142+
registerProtectedType(returnType, method);
143+
}
144+
analyzeParameterTypes(method, i -> ResolvableType.forMethodParameter(method, i));
145+
}
146+
}
147+
148+
private void analyzeParameterTypes(Executable executable, Function<Integer,
149+
ResolvableType> parameterTypeFactory) {
150+
151+
for (int i = 0; i < executable.getParameters().length; i++) {
152+
ResolvableType parameterType = parameterTypeFactory.apply(i);
153+
if (isProtected(parameterType)) {
154+
registerProtectedType(parameterType, executable);
155+
}
156+
}
157+
}
158+
159+
boolean isProtected(ResolvableType resolvableType) {
160+
return isProtected(new HashSet<>(), resolvableType);
161+
}
162+
163+
private boolean isProtected(Set<ResolvableType> seen, ResolvableType target) {
164+
if (seen.contains(target)) {
165+
return false;
166+
}
167+
seen.add(target);
168+
ResolvableType nonProxyTarget = target.as(ClassUtils.getUserClass(target.toClass()));
169+
if (isProtected(nonProxyTarget.toClass())) {
170+
return true;
171+
}
172+
Class<?> declaringClass = nonProxyTarget.toClass().getDeclaringClass();
173+
if (declaringClass != null) {
174+
if (isProtected(declaringClass)) {
175+
return true;
176+
}
177+
}
178+
if (nonProxyTarget.hasGenerics()) {
179+
for (ResolvableType generic : nonProxyTarget.getGenerics()) {
180+
return isProtected(seen, generic);
181+
}
182+
}
183+
return false;
184+
}
185+
186+
private boolean isProtected(Class<?> type) {
187+
Class<?> candidate = ClassUtils.getUserClass(type);
188+
return isProtected(candidate.getModifiers());
189+
}
190+
191+
private boolean isProtected(int modifiers) {
192+
return !Modifier.isPublic(modifiers);
193+
}
194+
195+
private void registerProtectedType(ResolvableType type, @Nullable Member member) {
196+
this.elements.add(ProtectedElement.of(type, member));
197+
}
198+
199+
private void registerProtectedType(Class<?> type, Member member) {
200+
registerProtectedType(ResolvableType.forClass(type), member);
201+
}
202+
203+
/**
204+
* Options to use to analyze if invoking a {@link Member} requires
205+
* privileged access.
206+
*/
207+
public static class Options {
208+
209+
/**
210+
* Default options that does fallback to reflection and does not
211+
* assign the default type.
212+
*/
213+
public static final Options DEFAULTS = new Options();
214+
215+
private final boolean useReflection;
216+
217+
private final boolean assignReturnType;
218+
219+
/**
220+
* Create a new instance with the specified options.
221+
* @param useReflection whether the writer can automatically use
222+
* reflection to invoke a protected member if it is not public
223+
* @param assignReturnType whether the writer needs to assign the
224+
* return type, or if it is irrelevant
225+
*/
226+
public Options(boolean useReflection, boolean assignReturnType) {
227+
this.useReflection = useReflection;
228+
this.assignReturnType = assignReturnType;
229+
}
230+
231+
private Options() {
232+
this(true, false);
233+
}
234+
235+
}
236+
237+
}

0 commit comments

Comments
 (0)