Skip to content

Commit 7a53a1b

Browse files
wanghbxxxxbeiwei30
authored andcommitted
refactor adaptive extension class code creation: extract class AdaptiveClassCodeGenerator (apache#3419)
* refactor adaptive extension class code creation: extract createAdaptiveExtensionClassCode to class AdaptiveClassCodeGenerator * add some comment * add license and comment * remove main method
1 parent e9e176b commit 7a53a1b

File tree

2 files changed

+382
-201
lines changed

2 files changed

+382
-201
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.dubbo.common.extension;
18+
19+
import java.lang.reflect.Method;
20+
import java.lang.reflect.Modifier;
21+
import java.lang.reflect.Parameter;
22+
import java.util.Arrays;
23+
import java.util.stream.Collectors;
24+
import java.util.stream.IntStream;
25+
26+
import org.apache.dubbo.common.URL;
27+
import org.apache.dubbo.common.logger.Logger;
28+
import org.apache.dubbo.common.logger.LoggerFactory;
29+
import org.apache.dubbo.common.utils.StringUtils;
30+
31+
/**
32+
* Code generator for Adaptive class
33+
*/
34+
public class AdaptiveClassCodeGenerator {
35+
36+
private static final Logger logger = LoggerFactory.getLogger(AdaptiveClassCodeGenerator.class);
37+
38+
private static final String CLASSNAME_INVOCATION = "org.apache.dubbo.rpc.Invocation";
39+
40+
private static final String CODE_PACKAGE = "package %s;\n";
41+
42+
private static final String CODE_IMPORTS = "import %s;\n";
43+
44+
private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n";
45+
46+
private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n";
47+
48+
private static final String CODE_METHOD_ARGUMENT = "%s arg%d";
49+
50+
private static final String CODE_METHOD_THROWS = "throws %s";
51+
52+
private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n";
53+
54+
private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n";
55+
56+
private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n";
57+
58+
private static final String CODE_EXT_NAME_NULL_CHECK = "if(extName == null) "
59+
+ "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n";
60+
61+
private static final String CODE_INVOCATION_ARGUMENT_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); "
62+
+ "String methodName = arg%d.getMethodName();\n";
63+
64+
65+
private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);\n";
66+
67+
private final Class<?> type;
68+
69+
private String defaultExtName;
70+
71+
public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
72+
this.type = type;
73+
this.defaultExtName = defaultExtName;
74+
}
75+
76+
/**
77+
* test if given type has at least one method annotated with <code>SPI</code>
78+
*/
79+
private boolean hasAdaptiveMethod() {
80+
return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class));
81+
}
82+
83+
/**
84+
* generate and return class code
85+
*/
86+
public String generate() {
87+
// no need to generate adaptive class since there's no adaptive method found.
88+
if (!hasAdaptiveMethod()) {
89+
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
90+
}
91+
92+
StringBuilder code = new StringBuilder();
93+
code.append(generatePackageInfo());
94+
code.append(generateImports());
95+
code.append(generateClassDeclaration());
96+
97+
Method[] methods = type.getMethods();
98+
for (Method method : methods) {
99+
code.append(generateMethod(method));
100+
}
101+
code.append("}");
102+
103+
if (logger.isDebugEnabled()) {
104+
logger.debug(code.toString());
105+
}
106+
return code.toString();
107+
}
108+
109+
/**
110+
* generate package info
111+
*/
112+
private String generatePackageInfo() {
113+
return String.format(CODE_PACKAGE, type.getPackage().getName());
114+
}
115+
116+
/**
117+
* generate imports
118+
*/
119+
private String generateImports() {
120+
return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
121+
}
122+
123+
/**
124+
* generate class declaration
125+
*/
126+
private String generateClassDeclaration() {
127+
return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
128+
}
129+
130+
/**
131+
* generate method not annotated with Adaptive with throwing unsupported exception
132+
*/
133+
private String generateUnsupported(Method method) {
134+
return String.format(CODE_UNSUPPORTED, method, type.getName());
135+
}
136+
137+
/**
138+
* get index of parameter with type URL
139+
*/
140+
private int getUrlTypeIndex(Method method) {
141+
int urlTypeIndex = -1;
142+
Class<?>[] pts = method.getParameterTypes();
143+
for (int i = 0; i < pts.length; ++i) {
144+
if (pts[i].equals(URL.class)) {
145+
urlTypeIndex = i;
146+
break;
147+
}
148+
}
149+
return urlTypeIndex;
150+
}
151+
152+
/**
153+
* generate method declaration
154+
*/
155+
private String generateMethod(Method method) {
156+
String methodReturnType = method.getReturnType().getCanonicalName();
157+
String methodName = method.getName();
158+
String methodContent = generateMethodContent(method);
159+
String methodArgs = generateMethodArguments(method);
160+
String methodThrows = generateMethodThrows(method);
161+
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
162+
}
163+
164+
/**
165+
* generate method arguments
166+
*/
167+
private String generateMethodArguments(Method method) {
168+
Class<?>[] pts = method.getParameterTypes();
169+
return IntStream.range(0, pts.length)
170+
.mapToObj(i -> String.format(CODE_METHOD_ARGUMENT, pts[i].getCanonicalName(), i))
171+
.collect(Collectors.joining(", "));
172+
}
173+
174+
/**
175+
* generate method throws
176+
*/
177+
private String generateMethodThrows(Method method) {
178+
Class<?>[] ets = method.getExceptionTypes();
179+
if (ets.length > 0) {
180+
String list = Arrays.stream(ets).map(Class::getCanonicalName).collect(Collectors.joining(", "));
181+
return String.format(CODE_METHOD_THROWS, list);
182+
} else {
183+
return "";
184+
}
185+
}
186+
187+
/**
188+
* generate method URL argument null check
189+
*/
190+
private String generateUrlNullCheck(int index) {
191+
return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index);
192+
}
193+
194+
/**
195+
* generate method content
196+
*/
197+
private String generateMethodContent(Method method) {
198+
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
199+
StringBuilder code = new StringBuilder(512);
200+
if (adaptiveAnnotation == null) {
201+
return generateUnsupported(method);
202+
} else {
203+
int urlTypeIndex = getUrlTypeIndex(method);
204+
205+
// found parameter in URL type
206+
if (urlTypeIndex != -1) {
207+
// Null Point check
208+
code.append(generateUrlNullCheck(urlTypeIndex));
209+
} else {
210+
// did not find parameter in URL type
211+
code.append(generateUrlAssignmentIndirectly(method));
212+
}
213+
214+
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
215+
216+
boolean hasInvocation = hasInvocationArgument(method);
217+
218+
code.append(generateInvocationArgumentNullCheck(method));
219+
220+
code.append(generateExtNameAssignment(value, hasInvocation));
221+
// check extName == null?
222+
code.append(generateExtNameNullCheck(value));
223+
224+
code.append(generateExtensionAssignment());
225+
226+
// return statement
227+
code.append(generateReturnAndInovation(method));
228+
}
229+
230+
return code.toString();
231+
}
232+
233+
/**
234+
* generate code for variable extName null check
235+
*/
236+
private String generateExtNameNullCheck(String[] value) {
237+
return String.format(CODE_EXT_NAME_NULL_CHECK, type.getName(), Arrays.toString(value));
238+
}
239+
240+
/**
241+
* generate extName assigment code
242+
*/
243+
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
244+
// TODO: refactor it
245+
String getNameCode = null;
246+
for (int i = value.length - 1; i >= 0; --i) {
247+
if (i == value.length - 1) {
248+
if (null != defaultExtName) {
249+
if (!"protocol".equals(value[i])) {
250+
if (hasInvocation) {
251+
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
252+
} else {
253+
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
254+
}
255+
} else {
256+
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
257+
}
258+
} else {
259+
if (!"protocol".equals(value[i])) {
260+
if (hasInvocation) {
261+
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
262+
} else {
263+
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
264+
}
265+
} else {
266+
getNameCode = "url.getProtocol()";
267+
}
268+
}
269+
} else {
270+
if (!"protocol".equals(value[i])) {
271+
if (hasInvocation) {
272+
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
273+
} else {
274+
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
275+
}
276+
} else {
277+
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
278+
}
279+
}
280+
}
281+
282+
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
283+
}
284+
285+
/**
286+
* @return
287+
*/
288+
private String generateExtensionAssignment() {
289+
return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
290+
}
291+
292+
/**
293+
* generate method invocation statement and return it if necessary
294+
*/
295+
private String generateReturnAndInovation(Method method) {
296+
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";
297+
298+
String args = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.joining(", "));
299+
300+
return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
301+
}
302+
303+
/**
304+
* test if method has argument of type <code>Invocation</code>
305+
*/
306+
private boolean hasInvocationArgument(Method method) {
307+
Class<?>[] pts = method.getParameterTypes();
308+
return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName()));
309+
}
310+
311+
/**
312+
* generate code to test argument of type <code>Invocation</code> is null
313+
*/
314+
private String generateInvocationArgumentNullCheck(Method method) {
315+
Class<?>[] pts = method.getParameterTypes();
316+
return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName()))
317+
.mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i))
318+
.findFirst().orElse("");
319+
}
320+
321+
/**
322+
* get value of adaptive annotation or if empty return splitted simple name
323+
*/
324+
private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
325+
String[] value = adaptiveAnnotation.value();
326+
// value is not set, use the value generated from class name as the key
327+
if (value.length == 0) {
328+
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
329+
value = new String[]{splitName};
330+
}
331+
return value;
332+
}
333+
334+
/**
335+
* get parameter with type <code>URL</code> from method parameter:
336+
* <p>
337+
* test if parameter has method which returns type <code>URL</code>
338+
* <p>
339+
* if not found, throws IllegalStateException
340+
*/
341+
private String generateUrlAssignmentIndirectly(Method method) {
342+
Class<?>[] pts = method.getParameterTypes();
343+
344+
// find URL getter method
345+
for (int i = 0; i < pts.length; ++i) {
346+
for (Method m : pts[i].getMethods()) {
347+
String name = m.getName();
348+
if ((name.startsWith("get") || name.length() > 3)
349+
&& Modifier.isPublic(m.getModifiers())
350+
&& !Modifier.isStatic(m.getModifiers())
351+
&& m.getParameterTypes().length == 0
352+
&& m.getReturnType() == URL.class) {
353+
return generateGetUrlNullCheck(i, pts[i], name);
354+
}
355+
}
356+
}
357+
358+
// getter method not found, throw
359+
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
360+
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
361+
362+
}
363+
364+
/**
365+
* 1, test if argi is null
366+
* 2, test if argi.getXX() returns null
367+
* 3, assign url with argi.getXX()
368+
*/
369+
private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
370+
// Null point check
371+
StringBuilder code = new StringBuilder();
372+
code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
373+
index, type.getName()));
374+
code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
375+
index, method, type.getName(), method));
376+
377+
code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
378+
return code.toString();
379+
}
380+
381+
}

0 commit comments

Comments
 (0)