/* * Copyright (C) 2012 The Android Open Source Project * * Licensed 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 com.motorola.studio.android.generatecode; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IfStatement; 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.Modifier; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.PrimitiveType.Code; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.WildcardType; import com.motorola.studio.android.generateviewbylayout.JavaViewBasedOnLayoutModifierConstants; import com.motorola.studio.android.generateviewbylayout.model.LayoutNode; /** * Abstract code generator class that has common methods to generate code (for Menu, GUI handlers, findViewById, attributes). */ public abstract class AbstractCodeGenerator { protected static final String THIZ = "this."; protected TypeDeclaration typeDeclaration; /** * Default constructor * @param typeDeclaration AST for the type to modify */ public AbstractCodeGenerator(TypeDeclaration typeDeclaration) { this.typeDeclaration = typeDeclaration; } /** * Generates code by changing AST (Abstract Syntax tree) for a given Android class * @param monitor concrete implementation should use <code>SubMonitor.convert(monitor)</code> to display status messages during code generation * @throws JavaModelException if any error occurs to create new/modified AST to be written */ public abstract void generateCode(IProgressMonitor monitor) throws JavaModelException; /** * Constructs a new method declaration on type declaration (for a single parameter). * Warning: Calling methods need to fill the body and add the item into typeDeclaration * @param node */ protected MethodDeclaration addMethodDeclaration(ModifierKeyword modifierKeyword, String methodNameStr, Code returnType, String parameterClazzType, String parameterVariableName) { SingleVariableDeclaration singleVarDecl = createVariableDeclarationFromStrings(parameterClazzType, parameterVariableName); List<SingleVariableDeclaration> parameters = new ArrayList<SingleVariableDeclaration>(); parameters.add(singleVarDecl); MethodDeclaration methodDeclaration = addMethodDeclaration(modifierKeyword, methodNameStr, returnType, parameters); return methodDeclaration; } /** * Constructs a new method declaration on type declaration (for multiple parameters). * Warning: Calling methods need to fill the body and add the item into typeDeclaration * @param node */ @SuppressWarnings("unchecked") protected MethodDeclaration addMethodDeclaration(ModifierKeyword modifierKeyword, String methodNameStr, Code returnType, List<SingleVariableDeclaration> parameters) { MethodDeclaration methodDeclaration = typeDeclaration.getAST().newMethodDeclaration(); Modifier mod = typeDeclaration.getAST().newModifier(modifierKeyword); methodDeclaration.modifiers().add(mod); SimpleName methodName = typeDeclaration.getAST().newSimpleName(methodNameStr); methodDeclaration.setName(methodName); PrimitiveType voidType = typeDeclaration.getAST().newPrimitiveType(returnType); methodDeclaration.setReturnType2(voidType); if (parameters != null) { for (SingleVariableDeclaration param : parameters) { methodDeclaration.parameters().add(param); } } return methodDeclaration; } /** * * @param parameterClazzType * @param parameterVariableName * @return */ protected SingleVariableDeclaration createVariableDeclarationFromStrings( String parameterClazzType, String parameterVariableName) { SingleVariableDeclaration singleVarDecl = typeDeclaration.getAST().newSingleVariableDeclaration(); singleVarDecl.setType(typeDeclaration.getAST().newSimpleType( getViewName(parameterClazzType))); SimpleName variableName = getVariableName(parameterVariableName); singleVarDecl.setName(variableName); return singleVarDecl; } /** * Creates a variable declaration when it required a List with a parameterized type * @param parameterClazzType * @param typeArgument used to create variables such as List<T> * @param parameterVariableName * @return */ @SuppressWarnings("unchecked") protected SingleVariableDeclaration createWildcardTypeVariableDeclarationFromStrings( String parameterClazzType, String parameterVariableName) { SingleVariableDeclaration singleVarDecl = typeDeclaration.getAST().newSingleVariableDeclaration(); SimpleType type = typeDeclaration.getAST().newSimpleType(getViewName(parameterClazzType)); ParameterizedType paramType = typeDeclaration.getAST().newParameterizedType(type); WildcardType wildcardType = typeDeclaration.getAST().newWildcardType(); paramType.typeArguments().add(wildcardType); singleVarDecl.setType(paramType); SimpleName variableName = getVariableName(parameterVariableName); singleVarDecl.setName(variableName); return singleVarDecl; } /** * @param parameterClazzType * @param parameterVariableName * @return AST */ protected SingleVariableDeclaration createVariableDeclarationPrimitiveCode(Code code, String parameterVariableName) { SingleVariableDeclaration singleVarDecl = typeDeclaration.getAST().newSingleVariableDeclaration(); singleVarDecl.setType(typeDeclaration.getAST().newPrimitiveType(code)); SimpleName variableName = getVariableName(parameterVariableName); singleVarDecl.setName(variableName); return singleVarDecl; } /** * @return AST representation for the name */ protected SimpleName getVariableName(String name) { SimpleName variableName = typeDeclaration.getAST().newSimpleName(name); return variableName; } /** * @return AST for simple type */ protected SimpleType getListenerSimpleType(String clazzType, String methodListenerMethodName) { SimpleName listenerName = typeDeclaration.getAST().newSimpleName(methodListenerMethodName); SimpleName viewName = getViewName(clazzType); QualifiedName listenerQualifiedName = typeDeclaration.getAST().newQualifiedName(viewName, listenerName); SimpleType listenerType = typeDeclaration.getAST().newSimpleType(listenerQualifiedName); return listenerType; } /** * @return AST Simple Name for the clazz type */ protected SimpleName getViewName(String clazzType) { SimpleName viewName = typeDeclaration.getAST().newSimpleName(clazzType); return viewName; } /** * Returns the index of the inflate invocation * @param index * @param expression */ protected int findInflateIndexAtStatement(int index, Expression expression) { int foundIndex = -1; if (expression instanceof MethodInvocation) { MethodInvocation inflateInvocation = (MethodInvocation) expression; if ((inflateInvocation.getName() != null) && inflateInvocation.getName().getIdentifier().equals("inflate")) { foundIndex = index; } } return foundIndex; } /** * Checks if onClick is already declared in Java Activity (based on layout XML declaration) * @param node * @return true if declared, false otherwise */ protected boolean onClickFromXmlAlreadyDeclared(LayoutNode node) { boolean containMethodDeclared = false; if (typeDeclaration.bodyDeclarations() != null) { //check if method already declared for (Object bd : typeDeclaration.bodyDeclarations()) { if (bd instanceof MethodDeclaration) { MethodDeclaration md = (MethodDeclaration) bd; if ((md.getName() != null) && md.getName().toString().equals(node.getOnClick())) { containMethodDeclared = true; break; } } } } return containMethodDeclared; } /** * Checks if a method is already declared * @param methodToCheck method to verify if already existent in the code * @param bindingString * @return null if there method not declared yet, or the the method found (if already declared) */ protected MethodDeclaration isMethodAlreadyDeclared(MethodDeclaration methodToCheck, String bindingString) { MethodDeclaration result = null; if (typeDeclaration.bodyDeclarations() != null) { //check if method already declared for (Object bd : typeDeclaration.bodyDeclarations()) { if (bd instanceof MethodDeclaration) { MethodDeclaration md = (MethodDeclaration) bd; IMethodBinding binding = md.resolveBinding(); if ((binding != null) && (bindingString != null) && binding.toString().trim().equals(bindingString.trim())) { result = md; break; } } } } return result; } /** * Adds a statement into methodDeclaration if the statement is not already declared. * If the last statement is a {@link ReturnStatement}, it inserts before it, otherwise it inserts as the last statement in the block * @param methodDeclaration * @param declarationStatement * @param sameClass true, it will compare if there is the same statement class in the body of the methodDeclaration (but the content may be different), false it will ignore the class and it will compare if the content is the same (given by toString.equals()) */ protected void addStatementIfNotFound(MethodDeclaration methodDeclaration, Statement declarationStatement, boolean sameClass) { addStatementIfNotFound(methodDeclaration.getBody(), declarationStatement, sameClass); } /** * Adds a statement into block if the statement is not already declared. * If the last statement is a {@link ReturnStatement}, it inserts before it, otherwise it inserts as the last statement in the block * @param block * @param declarationStatement * @param sameClass true, it will compare if there is the same statement class in the body of the methodDeclaration (but the content may be different), false it will ignore the class and it will compare if the content is the same (given by toString.equals()) */ @SuppressWarnings("unchecked") protected void addStatementIfNotFound(Block block, Statement declarationStatement, boolean sameClass) { boolean alreadyDeclared = false; List<Statement> statements = block.statements(); alreadyDeclared = isStatementAlreadyDeclared(declarationStatement, sameClass, statements); if (!alreadyDeclared) { Statement statement = block.statements().size() > 0 ? (Statement) block.statements().get( block.statements().size() - 1) : null; if ((statement instanceof ReturnStatement) && !(declarationStatement instanceof ReturnStatement)) { //need to insert before return block.statements().add(block.statements().size() - 1, declarationStatement); } else { block.statements().add(declarationStatement); } } } /** * Checks if the given declarationg statement is already available in the list of statements * @param declarationStatement * @param sameClass true, it will compare if there is the same statement class in the body of the methodDeclaration (but the content may be different), false it will ignore the class and it will compare if the content is the same (given by toString.equals()) * @param alreadyDeclared * @param statements * @return true if statement found, false otherwise */ protected boolean isStatementAlreadyDeclared(Statement declarationStatement, boolean sameClass, List<Statement> statements) { boolean alreadyDeclared = false; if (statements != null) { for (Statement statement : statements) { if ((!sameClass && declarationStatement.toString().equals(statement.toString())) || (sameClass && statement.getClass().equals( declarationStatement.getClass()))) { alreadyDeclared = true; break; } } } return alreadyDeclared; } /** * Finds a statement if already declared * @param declarationStatement * @param sameClass true, it will compare if there is the same statement class in the body of the methodDeclaration (but the content may be different), false it will ignore the class and it will compare if the content is the same (given by toString.equals()) * @param alreadyDeclared * @param statements * @return null if not found, the reference to the statement if it is found */ protected Statement findIfStatementAlreadyDeclared(Statement declarationStatement, boolean sameClass, List<Statement> statements) { Statement foundStatement = null; if (statements != null) { for (Statement statement : statements) { if ((!sameClass && declarationStatement.toString().equals(statement.toString())) || (sameClass && statement.getClass().equals( declarationStatement.getClass()))) { foundStatement = statement; break; } } } return foundStatement; } /** * Inserts method in the format super.$superMethodName($list_params); and inserts it into the methodDeclaration (if not already available) * @param methodDeclaration * @param superMethodName * @param arguments null if not necessary or a list of arguments to pass for method */ @SuppressWarnings("unchecked") public void insertSuperInvocation(MethodDeclaration methodDeclaration, String superMethodName, List<String> arguments) { boolean alreadyHaveMethod = false; if (methodDeclaration.getBody() != null) { //check if method already declared for (Object bd : methodDeclaration.getBody().statements()) { if (bd instanceof ExpressionStatement) { ExpressionStatement es = (ExpressionStatement) bd; Expression ex = es.getExpression(); if (ex instanceof SuperMethodInvocation) { SuperMethodInvocation smi = (SuperMethodInvocation) ex; if (smi.getName().toString().equals(superMethodName)) { alreadyHaveMethod = true; break; } } } } } if (!alreadyHaveMethod) { SuperMethodInvocation superInvoke = createSuperMethodInvocation(superMethodName, arguments); ExpressionStatement exprSt = methodDeclaration.getAST().newExpressionStatement(superInvoke); methodDeclaration.getBody().statements().add(exprSt); } } /** * Creates a method in the format super.$superMethodName($list_params); * @param superMethodName * @param arguments null if not necessary or a list of arguments to pass for method * @return */ @SuppressWarnings("unchecked") protected SuperMethodInvocation createSuperMethodInvocation(String superMethodName, List<String> arguments) { SuperMethodInvocation superInvoke = typeDeclaration.getAST().newSuperMethodInvocation(); SimpleName onSaveStateName = typeDeclaration.getAST().newSimpleName(superMethodName); superInvoke.setName(onSaveStateName); if (arguments != null) { for (String a : arguments) { SimpleName arg = typeDeclaration.getAST().newSimpleName(a); superInvoke.arguments().add(arg); } } return superInvoke; } @SuppressWarnings("unchecked") /** * Generates AST to invoke a method with the given structure <code>prefix.methodName(){}</code>. * This code avoids method invocation be duplicated in the {@link MethodDeclaration}. * @param method declared method to insert the invocation * @param prefix * @param methodName */ protected void invokeMethod(MethodDeclaration method, String prefix, String methodName) { boolean alreadyHaveMethod = false; if (method.getBody() != null) { //check if method already declared for (Object bd : method.getBody().statements()) { if (bd instanceof ExpressionStatement) { ExpressionStatement es = (ExpressionStatement) bd; Expression ex = es.getExpression(); if (ex instanceof MethodInvocation) { MethodInvocation mi = (MethodInvocation) ex; if (mi.getName().toString().equals(methodName) && mi.getExpression().toString().equals(prefix)) { alreadyHaveMethod = true; break; } } } } } if (!alreadyHaveMethod) { MethodInvocation invoke = createMethodInvocation(prefix, methodName); ExpressionStatement commitExpr = method.getAST().newExpressionStatement(invoke); method.getBody().statements().add(commitExpr); } } /** * Create a method invocation in the format * <code>prefix.methodName()</code> * @param prefix null if does not have * @param methodName * @return {@link MethodInvocation} */ protected MethodInvocation createMethodInvocation(String prefix, String methodName) { MethodInvocation invoke = typeDeclaration.getAST().newMethodInvocation(); SimpleName methodInvokeName = typeDeclaration.getAST().newSimpleName(methodName); invoke.setName(methodInvokeName); if (prefix != null) { SimpleName prefixName = typeDeclaration.getAST().newSimpleName(prefix); invoke.setExpression(prefixName); } return invoke; } /** * Recursive private method to verify if an radio button id is in a "else if" chain * * @param ifSt * @param expression * @return */ protected boolean ifChainContainsExpression(IfStatement ifSt, Expression expression) { boolean containsExpression = false; Statement elseStatement = ifSt.getElseStatement(); //verifies if the first if's expression already verifies the current radio button. //the characters "(" and ")" are added to avoid that an substring is considered true if (ifSt.getExpression().toString().equals(expression.toString())) { containsExpression = true; } else if ((elseStatement != null) && (elseStatement instanceof IfStatement)) { containsExpression = ifChainContainsExpression((IfStatement) elseStatement, expression); } return containsExpression; } /** * Recursive private method to retrieve the last if in a "else if" chain * * @param ifSt * @return */ protected IfStatement getLastIfStatementInChain(IfStatement ifSt) { IfStatement lastStatement = null; Statement elseStatement = ifSt.getElseStatement(); // looks for the if statement which is in a else statement. Will stop when find and if withoud else statement. if ((elseStatement != null) && (elseStatement instanceof IfStatement)) { lastStatement = getLastIfStatementInChain((IfStatement) elseStatement); } //lastStatement will receive ifSt because it does not have the else or have an else but it is not and else { lastStatement = ifSt; } return lastStatement; } /** * Creates a chain og else if and else statement for the given if statement * @param ifSt * @param invocation * @param guiQN */ protected void createElseIfAndElseStatements(IfStatement ifSt, MethodInvocation invocation, QualifiedName guiQN) { InfixExpression infixExp = typeDeclaration.getAST().newInfixExpression(); infixExp.setOperator(InfixExpression.Operator.EQUALS); infixExp.setLeftOperand(invocation); infixExp.setRightOperand(guiQN); //first verifies if the expression of the if statement is missing, it means we created it, just need to add the expression. //Otherwise, the "else if" chain must be verified before add the new if statement if (ifSt.getExpression().toString() .equals(JavaViewBasedOnLayoutModifierConstants.EXPRESSION_MISSING)) { ifSt.setExpression(infixExp); } else { boolean expressionAlreadyExists = false; //verifies if the first if's expression already verifies the current menu item or radio button if (ifChainContainsExpression(ifSt, infixExp)) { expressionAlreadyExists = true; } if (!expressionAlreadyExists) { IfStatement lastIfStatement = getLastIfStatementInChain(ifSt); if (lastIfStatement != null) { IfStatement elseSt = typeDeclaration.getAST().newIfStatement(); elseSt.setExpression(infixExp); if (lastIfStatement.getElseStatement() != null) { Statement oldElseStatement = lastIfStatement.getElseStatement(); elseSt.setElseStatement((Statement) ASTNode.copySubtree(elseSt.getAST(), oldElseStatement)); lastIfStatement.setElseStatement(elseSt); } else { lastIfStatement.setElseStatement(elseSt); } } } } } /** * Creates a return statemtn into the method declaration (it only adds the return if it does not exist yet) * @param methodDeclaration to add the return statement */ protected void createReturnStatement(MethodDeclaration methodDeclaration) { ReturnStatement returnStatement = typeDeclaration.getAST().newReturnStatement(); returnStatement.setExpression(typeDeclaration.getAST().newBooleanLiteral(true)); //try to find a ReturnStatement (may be a different return, but the content may differ) addStatementIfNotFound(methodDeclaration, returnStatement, true); } }