diff --git a/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java b/src/main/java/com/alibaba/testable/generator/CallSuperMethodStatementGenerator.java similarity index 71% rename from src/main/java/com/alibaba/testable/generator/CallSuperMethod.java rename to src/main/java/com/alibaba/testable/generator/CallSuperMethodStatementGenerator.java index 08caef3..e52bbe6 100644 --- a/src/main/java/com/alibaba/testable/generator/CallSuperMethod.java +++ b/src/main/java/com/alibaba/testable/generator/CallSuperMethodStatementGenerator.java @@ -14,25 +14,17 @@ import java.util.List; * * @author flin */ -public class CallSuperMethod { +public class CallSuperMethodStatementGenerator { - private final String className; - private final JCTree.JCMethodDecl method; - - public CallSuperMethod(String className, JCTree.JCMethodDecl method) { - this.className = className; - this.method = method; - } - - public Statement[] invoke() { + public Statement[] fetch(String className, JCTree.JCMethodDecl method) { if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - return reflectCall(); + return reflectCall(className, method); } else { - return commonCall(); + return commonCall(method); } } - private Statement[] commonCall() { + private Statement[] commonCall(JCTree.JCMethodDecl method) { List args = new ArrayList<>(); StringBuilder code = new StringBuilder(); List placeholders = new ArrayList<>(); @@ -45,25 +37,25 @@ public class CallSuperMethod { code.append(".").append(method.name); } code.append("(").append(StringUtil.join(placeholders, ", ")).append(")"); - return new Statement[] { returnStatement(new Statement(code.toString(), args.toArray())) }; + return new Statement[] { returnStatement(method, new Statement(code.toString(), args.toArray())) }; } - private Statement[] reflectCall() { + private Statement[] reflectCall(String className, JCTree.JCMethodDecl method) { List statements = new ArrayList<>(); - statements.add(getMethodStatement()); + statements.add(getMethodStatement(className, method)); statements.add(setAccessibleStatement()); - statements.add(returnStatement(invokeStatement())); + statements.add(returnStatement(method, invokeStatement(method))); return statements.toArray(new Statement[0]); } - private Statement returnStatement(Statement statement) { - if (method.restype != null && !method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { + private Statement returnStatement(JCTree.JCMethodDecl method, Statement statement) { + if (method.restype != null && !method.restype.toString().equals(ConstPool.TYPE_VOID)) { statement.setLine("return " + statement.getLine()); } return statement; } - private Statement getMethodStatement() { + private Statement getMethodStatement(String className, JCTree.JCMethodDecl method) { List args = new ArrayList<>(); StringBuilder code = new StringBuilder(); code.append("$T m = "); @@ -81,9 +73,9 @@ public class CallSuperMethod { return new Statement("m.setAccessible(true)", new Object[0]); } - private Statement invokeStatement() { + private Statement invokeStatement(JCTree.JCMethodDecl method) { StringBuilder code = new StringBuilder(); - if (!method.restype.toString().equals(ConstPool.CONSTRUCTOR_VOID)) { + if (!method.restype.toString().equals(ConstPool.TYPE_VOID)) { code.append("(").append(method.restype).append(")"); } code.append("m.invoke(this"); diff --git a/src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java b/src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java new file mode 100644 index 0000000..3963f42 --- /dev/null +++ b/src/main/java/com/alibaba/testable/generator/TestableClassGenerator.java @@ -0,0 +1,116 @@ +package com.alibaba.testable.generator; + +import com.alibaba.testable.generator.model.Statement; +import com.alibaba.testable.translator.TestableClassTranslator; +import com.alibaba.testable.util.ConstPool; +import com.squareup.javapoet.*; +import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.tree.JCTree; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Generate testable class code + * + * @author flin + */ +public class TestableClassGenerator { + + private final JavacTrees trees; + + public TestableClassGenerator(JavacTrees trees) { + this.trees = trees; + } + + public String fetch(Element clazz, String packageName, String className) { + JCTree tree = trees.getTree(clazz); + TestableClassTranslator translator = new TestableClassTranslator(); + tree.accept(translator); + + List methodSpecs = new ArrayList<>(); + for (JCTree.JCMethodDecl method : translator.getMethods()) { + if (isNoncallableMethod(method)) { + continue; + } + if (isConstructorMethod(method)) { + methodSpecs.add(buildConstructorMethod(clazz, method)); + } else { + methodSpecs.add(buildMemberMethod(clazz, method)); + } + } + + TypeSpec.Builder builder = TypeSpec.classBuilder(className) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .superclass(clazz.asType()); + for (MethodSpec m : methodSpecs) { + builder.addMethod(m); + } + TypeSpec testableClass = builder.build(); + JavaFile javaFile = JavaFile.builder(packageName, testableClass).build(); + return javaFile.toString(); + } + + private MethodSpec buildMemberMethod(Element classElement, JCTree.JCMethodDecl method) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(method.name.toString()) + .addModifiers(toPublicFlags(method.getModifiers())) + .returns(TypeName.get(((Type.MethodType)method.sym.type).restype)); + for (JCTree.JCVariableDecl p : method.getParameters()) { + builder.addParameter(getParameterSpec(p)); + } + if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { + builder.addException(Exception.class); + } else { + builder.addAnnotation(Override.class); + for (JCTree.JCExpression exception : method.getThrows()) { + builder.addException(TypeName.get(exception.type)); + } + } + addStatements(builder, classElement, method); + return builder.build(); + } + + private MethodSpec buildConstructorMethod(Element classElement, JCTree.JCMethodDecl method) { + MethodSpec.Builder builder = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC); + for (JCTree.JCVariableDecl p : method.getParameters()) { + builder.addParameter(getParameterSpec(p)); + } + addStatements(builder, classElement, method); + return builder.build(); + } + + private void addStatements(MethodSpec.Builder builder, Element classElement, JCTree.JCMethodDecl method) { + String className = classElement.getSimpleName().toString(); + Statement[] statements = new CallSuperMethodStatementGenerator().fetch(className, method); + for (Statement s : statements) { + builder.addStatement(s.getLine(), s.getParams()); + } + } + + private boolean isConstructorMethod(JCTree.JCMethodDecl method) { + return method.name.toString().equals(ConstPool.CONSTRUCTOR_NAME); + } + + private boolean isNoncallableMethod(JCTree.JCMethodDecl method) { + return method.getModifiers().getFlags().contains(Modifier.ABSTRACT); + } + + private Set toPublicFlags(JCTree.JCModifiers modifiers) { + Set flags = new HashSet<>(modifiers.getFlags()); + flags.remove(Modifier.PRIVATE); + flags.remove(Modifier.PROTECTED); + flags.add(Modifier.PUBLIC); + return flags; + } + + private ParameterSpec getParameterSpec(JCTree.JCVariableDecl type) { + return ParameterSpec.builder(TypeName.get(type.sym.type), type.name.toString()).build(); + } + +} diff --git a/src/main/java/com/alibaba/testable/processor/TestableProcessor.java b/src/main/java/com/alibaba/testable/processor/TestableProcessor.java index bc28764..e741ba8 100644 --- a/src/main/java/com/alibaba/testable/processor/TestableProcessor.java +++ b/src/main/java/com/alibaba/testable/processor/TestableProcessor.java @@ -1,13 +1,9 @@ package com.alibaba.testable.processor; import com.alibaba.testable.annotation.Testable; -import com.alibaba.testable.generator.CallSuperMethod; -import com.alibaba.testable.generator.model.Statement; -import com.alibaba.testable.translator.TestableClassTranslator; +import com.alibaba.testable.generator.TestableClassGenerator; import com.alibaba.testable.translator.TestableFieldTranslator; import com.alibaba.testable.util.ConstPool; -import com.squareup.javapoet.*; -import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import javax.annotation.processing.RoundEnvironment; @@ -15,14 +11,11 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.Set; /** @@ -52,101 +45,20 @@ public class TestableProcessor extends BaseProcessor { private void processClassElement(Element clazz) { String packageName = elementUtils.getPackageOf(clazz).getQualifiedName().toString(); - String testableTypeName = clazz.getSimpleName().toString().replace(".", "_") + "Testable"; + String testableTypeName = getTestableClassName(clazz.getSimpleName()); String fullQualityTypeName = packageName + "." + testableTypeName; try { JavaFileObject jfo = filter.createSourceFile(fullQualityTypeName); Writer writer = jfo.openWriter(); - writer.write(createTestableClass(clazz, packageName, testableTypeName)); + writer.write(new TestableClassGenerator(trees).fetch(clazz, packageName, testableTypeName)); writer.close(); } catch (IOException e) { e.printStackTrace(); } } - private String createTestableClass(Element clazz, String packageName, String className) { - JCTree tree = trees.getTree(clazz); - TestableClassTranslator translator = new TestableClassTranslator(); - tree.accept(translator); - - List methodSpecs = new ArrayList<>(); - for (JCTree.JCMethodDecl method : translator.getMethods()) { - if (isNoncallableMethod(method)) { - continue; - } - if (isConstructorMethod(method)) { - buildConstructorMethod(clazz, methodSpecs, method); - } else { - buildMemberMethod(clazz, methodSpecs, method); - } - } - - TypeSpec.Builder builder = TypeSpec.classBuilder(className) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .superclass(clazz.asType()); - for (MethodSpec m : methodSpecs) { - builder.addMethod(m); - } - TypeSpec testableClass = builder.build(); - JavaFile javaFile = JavaFile.builder(packageName, testableClass).build(); - return javaFile.toString(); - } - - private void buildMemberMethod(Element classElement, List methodSpecs, JCTree.JCMethodDecl method) { - MethodSpec.Builder builder = MethodSpec.methodBuilder(method.name.toString()) - .addModifiers(toPublicFlags(method.getModifiers())) - .returns(TypeName.get(((Type.MethodType)method.sym.type).restype)); - for (JCTree.JCVariableDecl p : method.getParameters()) { - builder.addParameter(getParameterSpec(p)); - } - if (method.getModifiers().getFlags().contains(Modifier.PRIVATE)) { - builder.addException(Exception.class); - } else { - builder.addAnnotation(Override.class); - for (JCTree.JCExpression exception : method.getThrows()) { - builder.addException(TypeName.get(exception.type)); - } - } - addStatements(builder, classElement, method); - methodSpecs.add(builder.build()); - } - - private void buildConstructorMethod(Element classElement, List methodSpecs, - JCTree.JCMethodDecl method) { - MethodSpec.Builder builder = MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC); - for (JCTree.JCVariableDecl p : method.getParameters()) { - builder.addParameter(getParameterSpec(p)); - } - addStatements(builder, classElement, method); - methodSpecs.add(builder.build()); - } - - private void addStatements(MethodSpec.Builder builder, Element classElement, JCTree.JCMethodDecl method) { - Statement[] statements = new CallSuperMethod(classElement.getSimpleName().toString(), method).invoke(); - for (Statement s : statements) { - builder.addStatement(s.getLine(), s.getParams()); - } - } - - private boolean isConstructorMethod(JCTree.JCMethodDecl method) { - return method.name.toString().equals(ConstPool.CONSTRUCTOR_NAME); - } - - private boolean isNoncallableMethod(JCTree.JCMethodDecl method) { - return method.getModifiers().getFlags().contains(Modifier.ABSTRACT); - } - - private Set toPublicFlags(JCTree.JCModifiers modifiers) { - Set flags = new HashSet<>(modifiers.getFlags()); - flags.remove(Modifier.PRIVATE); - flags.remove(Modifier.PROTECTED); - flags.add(Modifier.PUBLIC); - return flags; - } - - private ParameterSpec getParameterSpec(JCTree.JCVariableDecl type) { - return ParameterSpec.builder(TypeName.get(type.sym.type), type.name.toString()).build(); + private String getTestableClassName(Name className) { + return className.toString().replace(".", "_") + ConstPool.TESTABLE; } } diff --git a/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java b/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java index e070a81..573bc20 100644 --- a/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java +++ b/src/main/java/com/alibaba/testable/translator/TestableFieldTranslator.java @@ -1,5 +1,6 @@ package com.alibaba.testable.translator; +import com.alibaba.testable.util.ConstPool; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.TreeTranslator; @@ -30,7 +31,7 @@ public class TestableFieldTranslator extends TreeTranslator { private JCTree.JCIdent getTestableClassIdent(JCTree.JCExpression clazz) { Name className = ((JCTree.JCIdent)clazz).name; - return treeMaker.Ident(className.table.fromString(className + "Testable")); + return treeMaker.Ident(className.table.fromString(className + ConstPool.TESTABLE)); } } diff --git a/src/main/java/com/alibaba/testable/util/ConstPool.java b/src/main/java/com/alibaba/testable/util/ConstPool.java index c4edb69..bd3f350 100644 --- a/src/main/java/com/alibaba/testable/util/ConstPool.java +++ b/src/main/java/com/alibaba/testable/util/ConstPool.java @@ -8,6 +8,6 @@ import com.sun.tools.javac.tree.JCTree; public final class ConstPool { public static final String CONSTRUCTOR_NAME = ""; - - public static final String CONSTRUCTOR_VOID = "void"; + public static final String TYPE_VOID = "void"; + public static final String TESTABLE = "Testable"; }