Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor javassist compiler: extract class CtClassBuilder #3424

Merged
merged 6 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -431,5 +431,17 @@ public static <K, V> Map<K, V> toMap(Map.Entry<K, V>[] entries) {
}
return map;
}

/**
* get simple class name from qualified class name
*/
public static String getSimpleClassName(String qualifiedName) {
if (null == qualifiedName) {
return null;
}

int i = qualifiedName.lastIndexOf('.');
wanghbxxxx marked this conversation as resolved.
Show resolved Hide resolved
return i < 0 ? qualifiedName : qualifiedName.substring(i + 1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.compiler.support;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

/**
* Javassist class info contains all the information used for JavassistCompiler, including:
* <p>
* class name, imported packages, super class name, implemented interfaces, constructors, fields, methods.
*/
public class JavassistClassInfo {
Copy link
Contributor

Choose a reason for hiding this comment

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

As once this class gets initialized then it is not getting modified, I would recommend to use builder for this and then have only getter or make it immutable.

have addInterface, addConstructor keep it the door open for accidental bug. What do you say?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

could you give more detailed information what shall i do, could be some pseudo code or java code.
thank you

Copy link
Contributor

Choose a reason for hiding this comment

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

it was saying having setter for collections e.g. setImports(List imports), setInterfaces(List ifaces) .
but looking at again it seems you are doing more than just setter. In this case I think it is ok to have this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks


private String className;

private String superClassName = "java.lang.Object";

private List<String> imports = new ArrayList<>();

private Map<String, String> fullNames = new HashMap<>();

private List<String> ifaces = new ArrayList<>();

private List<String> constructors = new ArrayList<>();

private List<String> fields = new ArrayList<>();

private List<String> methods = new ArrayList<>();

public String getClassName() {
return className;
}

public void setClassName(String className) {
this.className = className;
}

public String getSuperClassName() {
return superClassName;
}

public void setSuperClassName(String superClassName) {
this.superClassName = getQualifiedClassName(superClassName);
}

public List<String> getImports() {
return imports;
}

public void addImports(String pkg) {
int pi = pkg.lastIndexOf('.');
if (pi > 0) {
String pkgName = pkg.substring(0, pi);
this.imports.add(pkgName);
if (!pkg.endsWith(".*")) {
fullNames.put(pkg.substring(pi + 1), pkg);
}
}
}

public List<String> getInterfaces() {
return ifaces;
}

public void addInterface(String iface) {
this.ifaces.add(getQualifiedClassName(iface));
}

public List<String> getConstructors() {
return constructors;
}

public void addConstructor(String constructor) {
this.constructors.add(constructor);
}

public List<String> getFields() {
return fields;
}

public void addField(String field) {
this.fields.add(field);
}

public List<String> getMethods() {
return methods;
}

public void addMethod(String method) {
this.methods.add(method);
}

/**
* get full qualified class name
*
* @param className super class name, maybe qualified or not
*/
protected String getQualifiedClassName(String className) {
if (className.contains(".")) {
return className;
}

if (fullNames.containsKey(className)) {
return fullNames.get(className);
}

return ClassUtils.forName(imports.toArray(new String[0]), className).getName();
}

/**
* build CtClass object
*/
public CtClass build(ClassLoader classLoader) throws NotFoundException, CannotCompileException {
ClassPool pool = new ClassPool(true);
pool.appendClassPath(new LoaderClassPath(classLoader));

// create class
CtClass ctClass = pool.makeClass(className, pool.get(superClassName));

// add imported packages
imports.stream().forEach(pool::importPackage);

// add implemented interfaces
for (String iface : ifaces) {
ctClass.addInterface(pool.get(iface));
}

// add constructors
for (String constructor : constructors) {
ctClass.addConstructor(CtNewConstructor.make(constructor, ctClass));
}

// add fields
for (String field : fields) {
ctClass.addField(CtField.make(field, ctClass));
}

// add methods
for (String method : methods) {
ctClass.addMethod(CtNewMethod.make(method, ctClass));
}

return ctClass;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,9 @@

import org.apache.dubbo.common.utils.ClassHelper;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -49,77 +41,46 @@ public class JavassistCompiler extends AbstractCompiler {

@Override
public Class<?> doCompile(String name, String source) throws Throwable {
int i = name.lastIndexOf('.');
String className = i < 0 ? name : name.substring(i + 1);
ClassPool pool = new ClassPool(true);
pool.appendClassPath(new LoaderClassPath(ClassHelper.getCallerClassLoader(getClass())));
JavassistClassInfo info = new JavassistClassInfo();
info.setClassName(name);

// process imported classes
Matcher matcher = IMPORT_PATTERN.matcher(source);
List<String> importPackages = new ArrayList<String>();
Map<String, String> fullNames = new HashMap<String, String>();
while (matcher.find()) {
String pkg = matcher.group(1);
if (pkg.endsWith(".*")) {
String pkgName = pkg.substring(0, pkg.length() - 2);
pool.importPackage(pkgName);
importPackages.add(pkgName);
} else {
int pi = pkg.lastIndexOf('.');
if (pi > 0) {
String pkgName = pkg.substring(0, pi);
pool.importPackage(pkgName);
importPackages.add(pkgName);
fullNames.put(pkg.substring(pi + 1), pkg);
}
}
info.addImports(matcher.group(1).trim());
}
String[] packages = importPackages.toArray(new String[0]);

// process extended super class
matcher = EXTENDS_PATTERN.matcher(source);
CtClass cls;
if (matcher.find()) {
String extend = matcher.group(1).trim();
String extendClass;
if (extend.contains(".")) {
extendClass = extend;
} else if (fullNames.containsKey(extend)) {
extendClass = fullNames.get(extend);
} else {
extendClass = ClassUtils.forName(packages, extend).getName();
}
cls = pool.makeClass(name, pool.get(extendClass));
} else {
cls = pool.makeClass(name);
info.setSuperClassName(matcher.group(1).trim());
}

// process implemented interfaces
matcher = IMPLEMENTS_PATTERN.matcher(source);
if (matcher.find()) {
String[] ifaces = matcher.group(1).trim().split("\\,");
for (String iface : ifaces) {
iface = iface.trim();
String ifaceClass;
if (iface.contains(".")) {
ifaceClass = iface;
} else if (fullNames.containsKey(iface)) {
ifaceClass = fullNames.get(iface);
} else {
ifaceClass = ClassUtils.forName(packages, iface).getName();
}
cls.addInterface(pool.get(ifaceClass));
}
Arrays.stream(ifaces).forEach(i -> info.addInterface(i.trim()));
}
String body = source.substring(source.indexOf("{") + 1, source.length() - 1);

// process constructors, fields, methods
String body = source.substring(source.indexOf('{') + 1, source.length() - 1);
String[] methods = METHODS_PATTERN.split(body);
for (String method : methods) {
method = method.trim();
if (method.length() > 0) {
if (method.startsWith(className)) {
cls.addConstructor(CtNewConstructor.make("public " + method, cls));
} else if (FIELD_PATTERN.matcher(method).matches()) {
cls.addField(CtField.make("private " + method, cls));
} else {
cls.addMethod(CtNewMethod.make("public " + method, cls));
}
String className = ClassUtils.getSimpleClassName(name);
Arrays.stream(methods).map(String::trim).filter(m -> !m.isEmpty()).forEach(method-> {
if (method.startsWith(className)) {
info.addConstructor("public " + method);
} else if (FIELD_PATTERN.matcher(method).matches()) {
info.addField("private " + method);
} else {
info.addMethod("public " + method);
}
}
return cls.toClass(ClassHelper.getCallerClassLoader(getClass()), JavassistCompiler.class.getProtectionDomain());
});

// compile
ClassLoader classLoader = ClassHelper.getCallerClassLoader(getClass());
CtClass cls = info.build(classLoader);
return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ public void testGetGenericClass() {
public void testGetSizeMethod() {
Assertions.assertEquals("getLength()", ClassUtils.getSizeMethod(GenericClass3.class));
}

@Test
public void testGetSimpleClassName() {
Assertions.assertNull(ClassUtils.getSimpleClassName(null));
Assertions.assertEquals("Map", ClassUtils.getSimpleClassName(Map.class.getName()));
Assertions.assertEquals("Map", ClassUtils.getSimpleClassName(Map.class.getSimpleName()));
}

private interface GenericInterface<T> {
}
Expand Down