Skip to content

Commit

Permalink
refactor javassist compiler: extract class CtClassBuilder (#3424)
Browse files Browse the repository at this point in the history
* refactor JavassistCompiler

* rename variable names

* reformat code

* refactor: prepend modifier of constructor, field and method outside the
JavassistClassInfo

* add null for ClassUtils.getSimpleClassName

* rename JavassistClassInfo to CtClassBuilder
  • Loading branch information
wanghbxxxx authored and beiwei30 committed Feb 11, 2019
1 parent 7a53a1b commit caac0b5
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 68 deletions.
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('.');
return i < 0 ? qualifiedName : qualifiedName.substring(i + 1);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* 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;

/**
* CtClassBuilder is builder for CtClass
* <p>
* contains all the information, including:
* <p>
* class name, imported packages, super class name, implemented interfaces, constructors, fields, methods.
*/
public class CtClassBuilder {

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())));
CtClassBuilder builder = new CtClassBuilder();
builder.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);
}
}
builder.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);
builder.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 -> builder.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)) {
builder.addConstructor("public " + method);
} else if (FIELD_PATTERN.matcher(method).matches()) {
builder.addField("private " + method);
} else {
builder.addMethod("public " + method);
}
}
return cls.toClass(ClassHelper.getCallerClassLoader(getClass()), JavassistCompiler.class.getProtectionDomain());
});

// compile
ClassLoader classLoader = ClassHelper.getCallerClassLoader(getClass());
CtClass cls = builder.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

0 comments on commit caac0b5

Please sign in to comment.