Java tutorial
/* * 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 org.eclipse.andmore.android.generatecode; import java.util.ArrayList; import java.util.List; import org.eclipse.andmore.android.generateviewbylayout.JavaViewBasedOnLayoutModifierConstants; import org.eclipse.andmore.android.generateviewbylayout.model.LayoutNode; 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; /** * 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); } }