package org.sahagin.runlib.srctreegen; import java.io.File; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.jar.Manifest; import java.util.logging.Logger; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AssertStatement; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.FileASTRequestor; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.sahagin.runlib.additionaltestdoc.AdditionalClassTestDoc; import org.sahagin.runlib.additionaltestdoc.AdditionalMethodTestDoc; import org.sahagin.runlib.additionaltestdoc.AdditionalPage; import org.sahagin.runlib.additionaltestdoc.AdditionalTestDocs; import org.sahagin.runlib.external.CaptureStyle; import org.sahagin.runlib.external.TestStepLabelMethod; import org.sahagin.runlib.external.adapter.JavaAdapterContainer; import org.sahagin.share.AcceptableLocales; import org.sahagin.share.CommonUtils; import org.sahagin.share.IllegalTestScriptException; import org.sahagin.share.Logging; import org.sahagin.share.srctree.PageClass; import org.sahagin.share.srctree.SrcTree; import org.sahagin.share.srctree.TestClass; import org.sahagin.share.srctree.TestClassTable; import org.sahagin.share.srctree.TestField; import org.sahagin.share.srctree.TestFieldTable; import org.sahagin.share.srctree.TestMethod; import org.sahagin.share.srctree.TestMethodTable; import org.sahagin.share.srctree.code.Code; import org.sahagin.share.srctree.code.CodeLine; import org.sahagin.share.srctree.code.Field; import org.sahagin.share.srctree.code.LocalVar; import org.sahagin.share.srctree.code.MethodArgument; import org.sahagin.share.srctree.code.StringCode; import org.sahagin.share.srctree.code.SubMethodInvoke; import org.sahagin.share.srctree.code.TestStepLabel; import org.sahagin.share.srctree.code.UnknownCode; import org.sahagin.share.srctree.code.VarAssign; import static org.sahagin.runlib.external.adapter.javasystem.JavaSystemAdditionalTestDocsAdapter.*; public class SrcTreeGenerator { private static Logger logger = Logging.getLogger(SrcTreeGenerator.class.getName()); private AdditionalTestDocs additionalTestDocs; private AcceptableLocales locales; // additionalTestDocs can be null public SrcTreeGenerator(AdditionalTestDocs additionalTestDocs, AcceptableLocales locales) { if (additionalTestDocs == null) { // use empty additionalTestDocs this.additionalTestDocs = new AdditionalTestDocs(); } else { this.additionalTestDocs = additionalTestDocs; } this.locales = locales; } // result first value .. TestDoc value. return null if no TestDoc found // result second value.. isPage private Pair<String, Boolean> getTestDoc(ITypeBinding type) { // Page testDoc is prior to TestDoc value String pageDoc = ASTUtils.getPageDoc(type, locales); if (pageDoc != null) { return Pair.of(pageDoc, true); } String testDoc = ASTUtils.getTestDoc(type, locales); if (testDoc != null) { return Pair.of(testDoc, false); } AdditionalClassTestDoc additional = additionalTestDocs.getClassTestDoc(type.getBinaryName()); if (additional != null) { return Pair.of(additional.getTestDoc(), additional instanceof AdditionalPage); } return Pair.of(null, false); } // null and default CaptureStyle pair if not found private Pair<String, CaptureStyle> getTestDoc(IMethodBinding method) { // TODO additional TestDoc should be prior to annotation TestDoc !? Pair<String, CaptureStyle> pair = ASTUtils.getTestDoc(method, locales); if (pair.getLeft() != null) { return pair; } List<String> argClassQualifiedNames = getArgClassQualifiedNames(method); AdditionalMethodTestDoc additional = additionalTestDocs.getMethodTestDoc( method.getDeclaringClass().getBinaryName(), method.getName(), argClassQualifiedNames); if (additional != null) { return Pair.of(additional.getTestDoc(), additional.getCaptureStyle()); } return Pair.of(null, CaptureStyle.getDefault()); } // returns null and default CaptureStyle pair if the method is not sub method private Pair<String, CaptureStyle> testDocIfSubMethod(IMethodBinding methodBinding) { // rootMethod also can have its TestDoc value if (JavaAdapterContainer.globalInstance().isRootMethod(methodBinding)) { return Pair.of(null, CaptureStyle.getDefault()); } return getTestDoc(methodBinding); } private IVariableBinding getVariableBinding(FieldDeclaration declaration) { for (Object fragment : declaration.fragments()) { if (fragment instanceof VariableDeclarationFragment) { VariableDeclarationFragment varDecl = (VariableDeclarationFragment) fragment; IVariableBinding binding = varDecl.resolveBinding(); if (binding != null) { return binding; } } } return null; } private List<String> getArgClassQualifiedNames(IMethodBinding method) { ITypeBinding[] paramTypes; if (method.isParameterizedMethod()) { // Use generic type to get argument class types // instead of the actual type resolved by JDT. IMethodBinding originalMethod = method.getMethodDeclaration(); paramTypes = originalMethod.getParameterTypes(); } else { paramTypes = method.getParameterTypes(); } List<String> result = new ArrayList<>(paramTypes.length); for (ITypeBinding param : paramTypes) { // AdditionalTestDoc's argClassQualifiedNames are defined by type erasure. // TODO is this generic handling logic always work well?? ITypeBinding erasure = param.getErasure(); if (erasure.isPrimitive() || erasure.isArray()) { // "int", "boolean", etc result.add(erasure.getQualifiedName()); } else { // getBinaryName and getQualifiedName are not the same. // For example, getBinaryName returns parent$child for inner class, // but getQualifiedName returns parent.child result.add(erasure.getBinaryName()); } } return result; } private String generateMethodKey(IMethodBinding method, boolean noArgClassesStr) { String classQualifiedName = method.getDeclaringClass().getBinaryName(); String methodSimpleName = method.getName(); List<String> argClassQualifiedNames = getArgClassQualifiedNames(method); if (noArgClassesStr) { return TestMethod.generateMethodKey(classQualifiedName, methodSimpleName); } else { return TestMethod.generateMethodKey( classQualifiedName, methodSimpleName, argClassQualifiedNames); } } // srcFiles..parse target files // classPathEntries.. all class paths (class file containing directory or jar file) srcFiles depend private static void parseAST(String[] srcFiles, Charset srcCharset, String[] classPathEntries, FileASTRequestor requestor) { ASTParser parser = ASTParser.newParser(AST.JLS8); Map<String, String> options = JavaCore.getOptions(); JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); parser.setCompilerOptions(options); parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setResolveBindings(true); parser.setBindingsRecovery(true); parser.setEnvironment(classPathEntries, null, null, true); String[] srcEncodings = new String[srcFiles.length]; for (int i = 0; i < srcEncodings.length; i++) { srcEncodings[i] = srcCharset.name(); } parser.createASTs( srcFiles, srcEncodings, new String[]{}, requestor, null); } // Get all root methods and its class as TestRootClass. // Each TestRootClass owns only TestRootMethod, and TestSubMethod information is not set yet. // methods does not have code information yet. private class CollectRootVisitor extends ASTVisitor { private TestClassTable rootClassTable; private TestMethodTable rootMethodTable; // old value in rootClassTable is not replaced. // old value in rootMethodTable is replaced. public CollectRootVisitor( TestClassTable rootClassTable, TestMethodTable rootMethodTable) { this.rootClassTable = rootClassTable; this.rootMethodTable = rootMethodTable; } @Override public boolean visit(MethodDeclaration node) { IMethodBinding methodBinding = node.resolveBinding(); if (!JavaAdapterContainer.globalInstance().isRootMethod(methodBinding)) { return super.visit(node); } ITypeBinding classBinding = methodBinding.getDeclaringClass(); if (!classBinding.isClass() && !classBinding.isInterface()) { // enum method, etc return super.visit(node); } TestClass rootClass = rootClassTable.getByKey(classBinding.getBinaryName()); if (rootClass == null) { Pair<String, Boolean> pair = getTestDoc(classBinding); if (pair.getRight()) { rootClass = new PageClass(); // though root class cannot be page class } else { rootClass = new TestClass(); } rootClass.setKey(classBinding.getBinaryName()); rootClass.setQualifiedName(classBinding.getBinaryName()); rootClass.setTestDoc(pair.getLeft()); rootClassTable.addTestClass(rootClass); } TestMethod testMethod = new TestMethod(); testMethod.setKey(generateMethodKey(methodBinding, false)); testMethod.setSimpleName(methodBinding.getName()); Pair<String, CaptureStyle> pair = getTestDoc(methodBinding); if (pair.getLeft() != null) { // pair is null if the root method does not have TestDoc annotation testMethod.setTestDoc(pair.getLeft()); testMethod.setCaptureStyle(pair.getRight()); } for (Object element : node.parameters()) { if (!(element instanceof SingleVariableDeclaration)) { throw new RuntimeException("not supported yet: " + element); } SingleVariableDeclaration varDecl = (SingleVariableDeclaration)element; testMethod.addArgVariable(varDecl.getName().getIdentifier()); if (varDecl.isVarargs()) { testMethod.setVariableLengthArgIndex(testMethod.getArgVariables().size() - 1); } } testMethod.setTestClassKey(rootClass.getKey()); testMethod.setTestClass(rootClass); rootMethodTable.addTestMethod(testMethod); rootClass.addTestMethodKey(testMethod.getKey()); rootClass.addTestMethod(testMethod); return super.visit(node); } } private class CollectRootRequestor extends FileASTRequestor { private TestClassTable rootClassTable; private TestMethodTable rootMethodTable; public CollectRootRequestor() { rootClassTable = new TestClassTable(); rootMethodTable = new TestMethodTable(); } public TestClassTable getRootClassTable() { return rootClassTable; } public TestMethodTable getRootMethodTable() { return rootMethodTable; } @Override public void acceptAST(String sourceFilePath, CompilationUnit ast) { ast.accept(new CollectRootVisitor(rootClassTable, rootMethodTable)); } } private class CollectSubVisitor extends ASTVisitor { private TestClassTable subClassTable; private TestMethodTable subMethodTable; private TestClassTable rootClassTable; private TestFieldTable fieldTable; // rootClassTable if only for read, not write any data. // old value in subClassTable is not replaced. // old value in subMethodTable and fieldTable is replaced. public CollectSubVisitor(TestClassTable rootClassTable, TestClassTable subClassTable, TestMethodTable subMethodTable, TestFieldTable fieldTable) { this.rootClassTable = rootClassTable; this.subClassTable = subClassTable; this.subMethodTable = subMethodTable; this.fieldTable = fieldTable; } // Returns existing TestClass in rootClassTable or subClassTable if found in these table. // If not found, create new TestClass and add it to subClassTable and returns it private TestClass classBindingTestClass(ITypeBinding classBinding) { TestClass testClass = rootClassTable.getByKey(classBinding.getBinaryName()); if (testClass == null) { testClass = subClassTable.getByKey(classBinding.getBinaryName()); if (testClass == null) { Pair<String, Boolean> pair = getTestDoc(classBinding); if (pair.getRight()) { testClass = new PageClass(); } else { testClass = new TestClass(); } testClass.setKey(classBinding.getBinaryName()); testClass.setQualifiedName(classBinding.getBinaryName()); testClass.setTestDoc(pair.getLeft()); subClassTable.addTestClass(testClass); } } return testClass; } @Override public boolean visit(FieldDeclaration node) { IVariableBinding variable = getVariableBinding(node); if (variable == null) { return super.visit(node); } ITypeBinding classBinding = variable.getDeclaringClass(); if (!classBinding.isClass() && !classBinding.isInterface()) { // enum method, etc return super.visit(node); } // TODO support additional testDoc for Field String testDoc = ASTUtils.getTestDoc(variable, locales); if (testDoc == null) { return super.visit(node); } TestClass testClass = classBindingTestClass(classBinding); TestField testField = new TestField(); testField.setTestClassKey(testClass.getKey()); testField.setTestClass(testClass); testField.setKey(testClass.getKey() + "." + variable.getName()); testField.setSimpleName(variable.getName()); testField.setTestDoc(testDoc); testField.setValue(null); // TODO currently not supported fieldTable.addTestField(testField); testClass.addTestFieldKey(testField.getKey()); testClass.addTestField(testField); return super.visit(node); } @Override public boolean visit(MethodDeclaration node) { IMethodBinding methodBinding = node.resolveBinding(); Pair<String, CaptureStyle> testDocPair = testDocIfSubMethod(methodBinding); if (testDocPair.getLeft() == null) { return super.visit(node); } ITypeBinding classBinding = methodBinding.getDeclaringClass(); if (!classBinding.isClass() && !classBinding.isInterface()) { // enum method, etc return super.visit(node); } TestClass testClass = classBindingTestClass(classBinding); TestMethod testMethod = new TestMethod(); testMethod.setKey(generateMethodKey(methodBinding, false)); testMethod.setSimpleName(methodBinding.getName()); testMethod.setTestDoc(testDocPair.getLeft()); testMethod.setCaptureStyle(testDocPair.getRight()); for (Object element : node.parameters()) { if (!(element instanceof SingleVariableDeclaration)) { throw new RuntimeException("not supported yet: " + element); } SingleVariableDeclaration varDecl = (SingleVariableDeclaration)element; testMethod.addArgVariable(varDecl.getName().getIdentifier()); if (varDecl.isVarargs()) { testMethod.setVariableLengthArgIndex(testMethod.getArgVariables().size() - 1); } } testMethod.setTestClassKey(testClass.getKey()); testMethod.setTestClass(testClass); subMethodTable.addTestMethod(testMethod); testClass.addTestMethodKey(testMethod.getKey()); testClass.addTestMethod(testMethod); return super.visit(node); } } private class CollectSubRequestor extends FileASTRequestor { private TestClassTable subClassTable; private TestMethodTable subMethodTable; private TestClassTable rootClassTable; private TestFieldTable fieldTable; public CollectSubRequestor(TestClassTable rootClassTable) { this.rootClassTable = rootClassTable; subClassTable = new TestClassTable(); subMethodTable = new TestMethodTable(); fieldTable = new TestFieldTable(); } public TestClassTable getSubClassTable() { return subClassTable; } public TestMethodTable getSubMethodTable() { return subMethodTable; } public TestFieldTable getFieldTable() { return fieldTable; } @Override public void acceptAST(String sourceFilePath, CompilationUnit ast) { ast.accept(new CollectSubVisitor( rootClassTable, subClassTable, subMethodTable, fieldTable)); } } private class CollectCodeVisitor extends ASTVisitor { private TestClassTable subClassTable; private TestMethodTable rootMethodTable; private TestMethodTable subMethodTable; private TestFieldTable fieldTable; private CompilationUnit compilationUnit; // set code information to method table public CollectCodeVisitor(TestClassTable subClassTable, TestMethodTable rootMethodTable, TestMethodTable subMethodTable, TestFieldTable fieldTable, CompilationUnit compilationUnit) { this.subClassTable = subClassTable; this.rootMethodTable = rootMethodTable; this.subMethodTable = subMethodTable; this.fieldTable = fieldTable; this.compilationUnit = compilationUnit; } private TestMethod getTestMethod(IMethodBinding method) { TestMethod testMethod = subMethodTable.getByKey(generateMethodKey(method, false)); if (testMethod != null) { return testMethod; } testMethod = subMethodTable.getByKey(generateMethodKey(method, true)); if (testMethod != null) { return testMethod; } return null; } // search super method of the specified method // from the specified type and its super class and implementing interface recursively. // type: class or interface // superOnly: if true, does not check the specified type itself private TestMethod getSuperMethodSub( ITypeBinding type, IMethodBinding method, boolean superOnly) { if (type == null) { return null; } if (!superOnly) { for (IMethodBinding declaredMethod : type.getDeclaredMethods()) { if (method.overrides(declaredMethod)) { TestMethod testMethod = getTestMethod(declaredMethod); if (testMethod != null) { return testMethod; } } } } TestMethod testMethod = getSuperMethodSub(type.getSuperclass(), method, false); if (testMethod != null) { return testMethod; } for (ITypeBinding implInterface : type.getInterfaces()) { testMethod = getSuperMethodSub(implInterface, method, false); if (testMethod != null) { return testMethod; } } return null; } // First found super method of the specified method. // returns null if not found private TestMethod getSuperMethod(IMethodBinding method) { return getSuperMethodSub(method.getDeclaringClass(), method, true); } private Code generateMethodInvokeCode(IMethodBinding binding, Expression thisInstance, List<?> arguments, String original, TestMethod parentMethod) { if (binding == null) { return generateUnknownCode(original); } boolean childInvoke = false; TestMethod invocationMethod = getTestMethod(binding); if (invocationMethod == null) { invocationMethod = getSuperMethod(binding); if (invocationMethod != null) { childInvoke = true; } } if (invocationMethod == null) { return generateUnknownCode(original); } SubMethodInvoke subMethodInvoke = new SubMethodInvoke(); subMethodInvoke.setSubMethodKey(invocationMethod.getKey()); subMethodInvoke.setSubMethod(invocationMethod); if (thisInstance == null) { subMethodInvoke.setThisInstance(null); } else { subMethodInvoke.setThisInstance(expressionCode(thisInstance, parentMethod)); } for (Object arg : arguments) { Expression exp = (Expression) arg; subMethodInvoke.addArg(expressionCode(exp, parentMethod)); } subMethodInvoke.setChildInvoke(childInvoke); subMethodInvoke.setOriginal(original); return subMethodInvoke; } private Code generateMethodArgCode(SimpleName simpleName, IVariableBinding paramVarBinding, TestMethod parentMethod) { int argIndex; if (parentMethod == null) { argIndex = -1; } else { String varName = paramVarBinding.getName(); argIndex = parentMethod.getArgVariables().indexOf(varName); } if (argIndex == -1) { // when fails to resolve parameter variable return generateUnknownCode(simpleName); } MethodArgument methodArg = new MethodArgument(); methodArg.setOriginal(simpleName.toString().trim()); methodArg.setArgIndex(argIndex); return methodArg; } private Code generateFieldCode(SimpleName simpleName, IVariableBinding localVarBinding) { String key = localVarBinding.getDeclaringClass().getBinaryName() + "." + localVarBinding.getName(); TestField testField = fieldTable.getByKey(key); if (testField == null) { return generateUnknownCode(simpleName.toString().trim()); } Field field = new Field(); field.setFieldKey(testField.getKey()); field.setField(testField); field.setOriginal(simpleName.toString().trim()); field.setThisInstance(null); // TODO really null?? return field; } private LocalVar generateLocalVarCode(SimpleName simpleName, IVariableBinding localVarBinding) { LocalVar localVar = new LocalVar(); localVar.setOriginal(simpleName.toString().trim()); localVar.setName(simpleName.getIdentifier()); return localVar; } // expression is used to get original code, and can be null private Code generateLocalVarAssignCode(Expression expression, Expression left, Expression right, TestMethod parentMethod) { Code rightCode = expressionCode(right, parentMethod); if (rightCode instanceof UnknownCode) { // ignore left for UnknownCode assignment return rightCode; } if (!(left instanceof SimpleName)) { return rightCode; // ignore left } SimpleName simpleName = (SimpleName) left; IBinding leftBinding = simpleName.resolveBinding(); if (!(leftBinding instanceof IVariableBinding)) { return rightCode; // ignore left } IVariableBinding varBinding = (IVariableBinding) leftBinding; if (varBinding.isField() || varBinding.isParameter()) { // ignore left for field assignment and method argument assignment // TODO should handle field assignment as VarAssign for Field ?? return rightCode; } String classKey = varBinding.getType().getBinaryName(); TestClass subClass = subClassTable.getByKey(classKey); if (subClass != null && subClass instanceof PageClass) { // ignore left for page type variable assignment // since usually page type variable is not used in other TestDoc return rightCode; } LocalVar localVar = generateLocalVarCode(simpleName, varBinding); VarAssign assign = new VarAssign(); assign.setOriginal(expression.toString().trim()); assign.setVariable(localVar); assign.setValue(rightCode); return assign; } // return null if method does not represent TestStepLabel private TestStepLabel generateTestStepLabelCode( MethodInvocation invocation, TestMethod parentMethod) { IMethodBinding method = invocation.resolveMethodBinding(); if (method == null) { return null; } String methodName = method.getName(); if (methodName == null || !methodName.equals("TestDoc")) { return null; } ITypeBinding defClass = method.getDeclaringClass(); if (defClass == null || defClass.getQualifiedName() == null || !defClass.getQualifiedName().equals(TestStepLabelMethod.class.getCanonicalName())) { return null; } assert invocation.arguments().size() == 1; Expression arg = (Expression) invocation.arguments().get(0); Code argCode = expressionCode(arg, parentMethod); if (!(argCode instanceof StringCode)) { throw new RuntimeException( "testDoc method argument must be string literal argument at this moment"); } TestStepLabel stepLabel = new TestStepLabel(); stepLabel.setLabel(null); stepLabel.setText(((StringCode) argCode).getValue()); return stepLabel; } private SubMethodInvoke generateAssertMethodInvokeCode( Expression expression, String original, TestMethod parentMethod) { String assertMethodKey = TestMethod.generateMethodKey(CLASS_QUALIFIED_NAME, METHOD_ASSERT); TestMethod assertMethod = subMethodTable.getByKey(assertMethodKey); assert assertMethod != null; SubMethodInvoke assertMethodInvoke = new SubMethodInvoke(); assertMethodInvoke.setSubMethodKey(assertMethodKey); assertMethodInvoke.setSubMethod(assertMethod); assertMethodInvoke.addArg(expressionCode(expression, parentMethod)); assertMethodInvoke.setOriginal(original); return assertMethodInvoke; } private Code generateInfixMethodInvokeCode(InfixExpression infix, TestMethod parentMethod) { String infixMethodKey; String operator = infix.getOperator().toString(); if (operator.equals(InfixExpression.Operator.EQUALS.toString())) { infixMethodKey = TestMethod.generateMethodKey(CLASS_QUALIFIED_NAME, METHOD_EQUALS); } else if (operator.equals(InfixExpression.Operator.NOT_EQUALS.toString())) { infixMethodKey = TestMethod.generateMethodKey(CLASS_QUALIFIED_NAME, METHOD_NOT_EQUALS); } else { return generateUnknownCode(infix); } TestMethod infixMethod = subMethodTable.getByKey(infixMethodKey); assert infixMethod != null; SubMethodInvoke infixMethodInvoke = new SubMethodInvoke(); infixMethodInvoke.setSubMethodKey(infixMethodKey); infixMethodInvoke.setSubMethod(infixMethod); Code leftCode = expressionCode(infix.getLeftOperand(), parentMethod); Code rightcode = expressionCode(infix.getRightOperand(), parentMethod); infixMethodInvoke.addArg(leftCode); infixMethodInvoke.addArg(rightcode); infixMethodInvoke.setOriginal(infix.toString().trim()); return infixMethodInvoke; } private UnknownCode generateUnknownCode(String original) { UnknownCode unknownCode = new UnknownCode(); unknownCode.setOriginal(original); return unknownCode; } private UnknownCode generateUnknownCode(Expression expression) { return generateUnknownCode(expression.toString().trim()); } private Code expressionCode(Expression expression, TestMethod parentMethod) { if (expression == null) { StringCode strCode = new StringCode(); strCode.setValue(null); strCode.setOriginal("null"); return strCode; } else if (expression instanceof StringLiteral) { StringCode strCode = new StringCode(); strCode.setValue(((StringLiteral) expression).getLiteralValue()); strCode.setOriginal(expression.toString().trim()); return strCode; } else if (expression instanceof Assignment) { Assignment assignment = (Assignment) expression; return generateLocalVarAssignCode(expression, assignment.getLeftHandSide(), assignment.getRightHandSide(), parentMethod); } else if (expression instanceof MethodInvocation) { MethodInvocation invocation = (MethodInvocation) expression; TestStepLabel stepLabel = generateTestStepLabelCode(invocation, parentMethod); if (stepLabel != null) { return stepLabel; } IMethodBinding binding = invocation.resolveMethodBinding(); return generateMethodInvokeCode(binding, invocation.getExpression(), invocation.arguments(), expression.toString().trim(), parentMethod); } else if (expression instanceof ClassInstanceCreation) { ClassInstanceCreation creation = (ClassInstanceCreation) expression; IMethodBinding binding = creation.resolveConstructorBinding(); return generateMethodInvokeCode(binding, null, creation.arguments(), expression.toString().trim(), parentMethod); } else if (expression instanceof SimpleName) { SimpleName simpleName = (SimpleName) expression; IBinding binding = simpleName.resolveBinding(); if (binding instanceof IVariableBinding) { IVariableBinding varBinding = (IVariableBinding) binding; if (varBinding.isParameter()) { // method argument return generateMethodArgCode(simpleName, varBinding, parentMethod); } else if (varBinding.isField()) { return generateFieldCode(simpleName, varBinding); } else { // local variable reference return generateLocalVarCode(simpleName, varBinding); } } else { return generateUnknownCode(expression); } } else if (expression instanceof InfixExpression) { InfixExpression infix = (InfixExpression) expression; return generateInfixMethodInvokeCode(infix, parentMethod); } else{ return generateUnknownCode(expression); } } private CodeLine statementCodeLine(Statement statement, TestMethod parentMethod) { Code code; if (statement instanceof ExpressionStatement) { Expression expression = ((ExpressionStatement) statement).getExpression(); code = expressionCode(expression, parentMethod); } else if (statement instanceof VariableDeclarationStatement) { // TODO assume single VariableDeclarationFragment VariableDeclarationStatement varDeclStatement = (VariableDeclarationStatement) statement; VariableDeclarationFragment varFrag = (VariableDeclarationFragment) varDeclStatement.fragments().get(0); Expression expression = varFrag.getInitializer(); if (expression == null) { code = new UnknownCode(); } else { code = generateLocalVarAssignCode(expression, varFrag.getName(), expression, parentMethod); } } else if (statement instanceof AssertStatement) { Expression expression = ((AssertStatement) statement).getExpression(); code = generateAssertMethodInvokeCode(expression, statement.toString().trim(), parentMethod); } else { code = new UnknownCode(); } CodeLine codeLine = new CodeLine(); codeLine.setStartLine(compilationUnit.getLineNumber(statement.getStartPosition())); codeLine.setEndLine(compilationUnit.getLineNumber( statement.getStartPosition() + statement.getLength())); codeLine.setCode(code); // sometimes original value set by expressionCode method does not equal to the one of statementNode code.setOriginal(statement.toString().trim()); return codeLine; } @Override public boolean visit(MethodDeclaration node) { TestMethod testMethod; IMethodBinding methodBinding = node.resolveBinding(); if (JavaAdapterContainer.globalInstance().isRootMethod(methodBinding)) { // TODO searching twice from table is not elegant logic.. testMethod = rootMethodTable.getByKey(generateMethodKey(methodBinding, false)); if (testMethod == null) { testMethod = rootMethodTable.getByKey(generateMethodKey(methodBinding, true)); } } else if (testDocIfSubMethod(methodBinding).getLeft() != null) { // subMethod // TODO searching twice from table is not elegant logic.. testMethod = subMethodTable.getByKey(generateMethodKey(methodBinding, false)); if (testMethod == null) { testMethod = subMethodTable.getByKey(generateMethodKey(methodBinding, true)); } } else { return super.visit(node); } Block body = node.getBody(); if (body == null) { // no body. Maybe abstract method or interface method return super.visit(node); } List<?> list = body.statements(); for (Object obj : list) { assert obj instanceof Statement; CodeLine codeLine = statementCodeLine((Statement) obj, testMethod); testMethod.addCodeBody(codeLine); } return super.visit(node); } } private class CollectCodeRequestor extends FileASTRequestor { private TestClassTable subClassTable; private TestMethodTable rootMethodTable; private TestMethodTable subMethodTable; private TestFieldTable fieldTable; public CollectCodeRequestor(TestClassTable subClassTable, TestMethodTable rootMethodTable, TestMethodTable subMethodTable, TestFieldTable fieldTable) { this.subClassTable = subClassTable; this.rootMethodTable = rootMethodTable; this.subMethodTable = subMethodTable; this.fieldTable = fieldTable; } @Override public void acceptAST(String sourceFilePath, CompilationUnit ast) { ast.accept(new CollectCodeVisitor( subClassTable, rootMethodTable, subMethodTable, fieldTable, ast)); } } // srcFiles..parse target files // srcCharset.. charset of srcFiles. // classPathEntries.. all class paths (class file containing directory or jar file) srcFiles depend. // this path value is similar to --classpath command line argument, but you must give // all class containing sub directories even if the class is in a named package public SrcTree generate(String[] srcFiles, Charset srcCharset, String[] classPathEntries) { // collect root class and method table without code body CollectRootRequestor rootRequestor = new CollectRootRequestor(); parseAST(srcFiles, srcCharset, classPathEntries, rootRequestor); // collect sub class and method table without code body CollectSubRequestor subRequestor = new CollectSubRequestor(rootRequestor.getRootClassTable()); parseAST(srcFiles, srcCharset, classPathEntries, subRequestor); // add additional TestDoc to the table AdditionalTestDocsSetter setter = new AdditionalTestDocsSetter( rootRequestor.getRootClassTable(), subRequestor.getSubClassTable(), rootRequestor.getRootMethodTable(), subRequestor.getSubMethodTable()); setter.set(additionalTestDocs); // collect code CollectCodeRequestor codeRequestor = new CollectCodeRequestor( subRequestor.getSubClassTable(), rootRequestor.getRootMethodTable(), subRequestor.getSubMethodTable(), subRequestor.getFieldTable()); parseAST(srcFiles, srcCharset, classPathEntries, codeRequestor); SrcTree result = new SrcTree(); result.setRootClassTable(rootRequestor.getRootClassTable()); result.setSubClassTable(subRequestor.getSubClassTable()); result.setRootMethodTable(rootRequestor.getRootMethodTable()); result.setSubMethodTable(subRequestor.getSubMethodTable()); result.setFieldTable(subRequestor.getFieldTable()); return result; } private void addToClassPathListFromJarManifest(List<String> classPathList, File jarFile) { if (!jarFile.exists()) { return; // do nothing } Manifest manifest = CommonUtils.readManifestFromExternalJar(jarFile); if (manifest == null) { return; // just ignore no manifest jar file } // jar class path is sometimes not set at java.class.path property // (this case happens for Maven surefire plug-in. // see http://maven.apache.org/surefire/maven-surefire-plugin/examples/class-loading.html) String jarClassPathStr = manifest.getMainAttributes().getValue("Class-Path"); if (jarClassPathStr != null) { String[] jarClassPathArray = jarClassPathStr.split(" "); // separator is space character addToClassPathList(classPathList, jarClassPathArray); } } private void addToClassPathList(List<String> classPathList, String[] classPathArray) { for (String classPath : classPathArray) { if (classPath == null || classPath.trim().equals("")) { continue; } String classPathWithoutPrefix; if (classPath.startsWith("file:")) { // class path in the jar MANIFEST sometimes has this form of class path classPathWithoutPrefix = classPath.substring(5); } else { classPathWithoutPrefix = classPath; } String absClassPath = new File(classPathWithoutPrefix).getAbsolutePath(); if (absClassPath.endsWith(".jar")) { if (!classPathList.contains(absClassPath)) { classPathList.add(absClassPath); addToClassPathListFromJarManifest(classPathList, new File(absClassPath)); } } else if (absClassPath.endsWith(".zip")) { if (!classPathList.contains(absClassPath)) { classPathList.add(absClassPath); } } else { File classPathFile = new File(absClassPath); if (classPathFile.isDirectory()) { // needs to add all sub directories // since SrcTreeGenerator does not search classPathEntry sub directories // TODO should add jar file in the sub directories ?? Collection<File> subDirCollection = FileUtils.listFilesAndDirs( classPathFile, FileFilterUtils.directoryFileFilter(), FileFilterUtils.trueFileFilter()); for (File subDir : subDirCollection) { if (!classPathList.contains(subDir.getAbsolutePath())) { classPathList.add(subDir.getAbsolutePath()); } } } } } } public SrcTree generateWithRuntimeClassPath(File srcRootDir, Charset srcCharset) throws IllegalTestScriptException { // set up srcFilePaths if (!srcRootDir.exists()) { throw new IllegalArgumentException("directory does not exist: " + srcRootDir.getAbsolutePath()); } Collection<File> srcFileCollection = FileUtils.listFiles(srcRootDir, new String[]{"java"}, true); List<File> srcFileList = new ArrayList<>(srcFileCollection); String[] srcFilePaths = new String[srcFileList.size()]; for (int i = 0; i < srcFileList.size(); i++) { srcFilePaths[i] = srcFileList.get(i).getAbsolutePath(); } // set up classPathEntries // TODO handle wild card classpath entry List<String> classPathList = new ArrayList<>(64); String classPathStr = System.getProperty("java.class.path"); String[] classPathArray = classPathStr.split(Pattern.quote(File.pathSeparator)); addToClassPathList(classPathList, classPathArray); for (String classPath : classPathList) { logger.info("classPath: " + classPath); } return generate(srcFilePaths, srcCharset, classPathList.toArray(new String[0])); } }