/* * 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.generateviewbylayout.codegenerators; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.JavaConventions; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS; import com.motorola.studio.android.generatecode.AbstractCodeGenerator; import com.motorola.studio.android.generateviewbylayout.JavaViewBasedOnLayoutModifierConstants; import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout; import com.motorola.studio.android.generateviewbylayout.model.LayoutNode; /** * Class that have common methods to generate code based on layout (for GUI handlers, findViewById, attributes) */ public abstract class AbstractLayoutCodeGenerator extends AbstractCodeGenerator { protected CodeGeneratorDataBasedOnLayout codeGeneratorData; protected MethodDeclaration onCreateDeclaration; /** * @param codeGeneratorData input where to get the information (generally a xml, for example, it could be menu or layout) * @param onCreateDeclaration method onCreate (for Activity) or onCreateView (for Fragment) * @param typeDeclaration AST type representing Activity or Fragment Android class */ public AbstractLayoutCodeGenerator(CodeGeneratorDataBasedOnLayout codeGeneratorData, MethodDeclaration onCreateDeclaration, TypeDeclaration typeDeclaration) { super(typeDeclaration); this.codeGeneratorData = codeGeneratorData; this.onCreateDeclaration = onCreateDeclaration; } /** * @return the codeGeneratorData */ protected CodeGeneratorDataBasedOnLayout getCodeGeneratorData() { return codeGeneratorData; } /** * Adds method invocation that instantiates an anonymous class to deal with the event */ protected void addMethodInvocationToListenerHandler(String callerId, String invocationMethod, String classType, String listenerClazzName, MethodDeclaration methodDeclaration) throws JavaModelException { List<MethodDeclaration> declarations = new ArrayList<MethodDeclaration>(); declarations.add(methodDeclaration); addMethodInvocationToListenerHandler(callerId, invocationMethod, classType, listenerClazzName, declarations); } /** * Adds method invocation that instantiates an anonymous class to deal with the event */ @SuppressWarnings("unchecked") protected void addMethodInvocationToListenerHandler(String callerId, String invocationMethod, String classType, String listenerClazzName, List<MethodDeclaration> methodDeclarations) throws JavaModelException { MethodInvocation methodInvocation = onCreateDeclaration.getAST().newMethodInvocation(); SimpleName listenerInvocationName = onCreateDeclaration.getAST().newSimpleName(invocationMethod); SimpleName listenerOptionalName = onCreateDeclaration.getAST().newSimpleName(callerId); FieldAccess fieldAccess = onCreateDeclaration.getAST().newFieldAccess(); fieldAccess.setExpression(onCreateDeclaration.getAST().newThisExpression()); fieldAccess.setName(listenerOptionalName); methodInvocation.setName(listenerInvocationName); methodInvocation.setExpression(fieldAccess); ClassInstanceCreation classInstanceCreation = onCreateDeclaration.getAST().newClassInstanceCreation(); SimpleType listenerType = getListenerSimpleType(classType, listenerClazzName); classInstanceCreation.setType(listenerType); AnonymousClassDeclaration classDeclaration = onCreateDeclaration.getAST().newAnonymousClassDeclaration(); for (MethodDeclaration methodDeclaration : methodDeclarations) { classDeclaration.bodyDeclarations().add(methodDeclaration); } classInstanceCreation.setAnonymousClassDeclaration(classDeclaration); methodInvocation.arguments().add(classInstanceCreation); ExpressionStatement expressionStatement = onCreateDeclaration.getAST().newExpressionStatement(methodInvocation); insertStatementsAtOnCreateDeclaration(expressionStatement, false); } /** * Insert statements on create declaration depending if it is activity / fragment * @param expr * @param insertInTheBeginningOfMethod true, if must insert after setContentView (activity) or after inflate (fragment); * false if must insert in the of the method (activity), last statement before return (fragment) * @throws JavaModelException if fragment can not be modified because it is not in the format appropriate */ @SuppressWarnings("unchecked") protected void insertStatementsAtOnCreateDeclaration(Statement expr, boolean insertInTheBeginningOfMethod) throws JavaModelException { int size = onCreateDeclaration.getBody().statements().size(); if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnLayout.TYPE.ACTIVITY)) { if (insertInTheBeginningOfMethod) { int foundIndex = findSetContentViewIndexInsideStatement(); //if activity => add after second statement (after setContentView) if (foundIndex >= 0) { //it should have super.onCreate and setContentView => add after them onCreateDeclaration.getBody().statements().add(foundIndex + 1, expr); } } else { //last statement onCreateDeclaration.getBody().statements().add(size, expr); } } else if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT)) { if (size <= 1) { throw new JavaModelException(new IllegalArgumentException( CodeUtilsNLS.MethodVisitor_InvalidFormatForFragmentOnCreateView), IStatus.ERROR); } if (insertInTheBeginningOfMethod) { if (size >= 2) { int foundIndex = findInflateIndexAtStatement(); if (foundIndex >= 0) { //if fragment => add after first statement (after inflater.inflate) //it should have inflater.inflate and return statement => add between them onCreateDeclaration.getBody().statements().add(foundIndex + 1, expr); } } } else { //last statement before return onCreateDeclaration.getBody().statements().add(size - 1, expr); } } } /** * Returns the last statement of inflate inside onCreate from Fragment * @return -1 if inflate not found, value >=0 if statement found */ private int findInflateIndexAtStatement() { int foundIndex = -1; int index = 0; while (index < onCreateDeclaration.getBody().statements().size()) { Object st = onCreateDeclaration.getBody().statements().get(index); Expression expression = null; if (st instanceof VariableDeclarationStatement) { VariableDeclarationStatement variableDeclarationStatement = (VariableDeclarationStatement) st; for (Object f : variableDeclarationStatement.fragments()) { VariableDeclarationFragment frag = (VariableDeclarationFragment) f; expression = frag.getInitializer(); } } else if (st instanceof ExpressionStatement) { ExpressionStatement expressionStatement = (ExpressionStatement) st; if (expressionStatement.getExpression() instanceof Assignment) { Assignment assignment = (Assignment) expressionStatement.getExpression(); expression = assignment.getRightHandSide(); } } if (expression != null) { int aux = findInflateIndexAtStatement(index, expression); if (aux >= 0) { foundIndex = aux; } } index++; } return foundIndex; } /** * Returns the last statement of setContentView inside onCreate from Activity * @return -1 if setContentView not found, value >=0 if statement found */ private int findSetContentViewIndexInsideStatement() { int foundIndex = -1; int index = 0; while (index < onCreateDeclaration.getBody().statements().size()) { Object st = onCreateDeclaration.getBody().statements().get(index); if (st instanceof ExpressionStatement) { ExpressionStatement expressionStatement = (ExpressionStatement) st; if (expressionStatement.getExpression() instanceof MethodInvocation) { MethodInvocation setContentInvocation = (MethodInvocation) expressionStatement.getExpression(); if ((setContentInvocation.getName() != null) && setContentInvocation.getName().getIdentifier() .equals("setContentView")) { foundIndex = index; } } } index++; } return foundIndex; } /** * Checks if the method is already invoked in the body of onCreate method * @param node * @return */ public boolean checkIfInvokeMethodIsDeclared(LayoutNode node, String method) { boolean containMethodDeclared = false; if (onCreateDeclaration.getBody() != null) { //check if method invocation already declared for (Object bodystat : onCreateDeclaration.getBody().statements()) { if (bodystat instanceof ExpressionStatement) { ExpressionStatement exprStatement = (ExpressionStatement) bodystat; if (exprStatement.getExpression() instanceof MethodInvocation) { MethodInvocation invoke = (MethodInvocation) exprStatement.getExpression(); if ((invoke.getName() != null) && invoke.getName().toString().equals(method)) { if ((invoke.getExpression() != null) && invoke.getExpression().toString() .equals(THIZ + node.getNodeId())) { containMethodDeclared = true; break; } } } } } } return containMethodDeclared; } /** * @param node * @return true if there is one findViewById with the give node.getNodeId(), * false otherwise */ public boolean checkIfFindViewByIdAlreadyDeclared(LayoutNode node) { return codeGeneratorData.getJavaLayoutData().getDeclaredViewIdsOnCode() .contains(node.getNodeId()); } /** * Creates an assigment statement. The format follows the example: * * <br><br> * <code>Button b = (Button) v.findViewById($nodeId);</code> * * * @param node * @param optionalExpression if invocation was nested (e.g.: getFragmentManager()) * @param methodToBeCalled * @throws JavaModelException */ @SuppressWarnings("unchecked") public void addAssignmentStatement(LayoutNode node, Expression optionalExpression, String methodToBeCalled) throws JavaModelException { SimpleName guiName; try { guiName = getNodeVariableTypeBasedOnLayoutNode(node); } catch (CoreException e) { throw new JavaModelException(e); } SimpleType guiType = onCreateDeclaration.getAST().newSimpleType(guiName); SimpleName method = onCreateDeclaration.getAST().newSimpleName(methodToBeCalled); SimpleName rId1 = onCreateDeclaration.getAST() .newSimpleName(JavaViewBasedOnLayoutModifierConstants.R); SimpleName rId2 = onCreateDeclaration.getAST().newSimpleName( JavaViewBasedOnLayoutModifierConstants.ID); QualifiedName rQualified1 = onCreateDeclaration.getAST().newQualifiedName(rId1, rId2); SimpleName guiId = onCreateDeclaration.getAST().newSimpleName(node.getNodeId()); QualifiedName rQualified2 = onCreateDeclaration.getAST().newQualifiedName(rQualified1, guiId); MethodInvocation invocation = onCreateDeclaration.getAST().newMethodInvocation(); invocation.setName(method); if (optionalExpression != null) { invocation.setExpression(optionalExpression); } if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnLayout.TYPE.FRAGMENT)) { if (!node.isFragmentPlaceholder()) { invocation.setExpression(onCreateDeclaration.getAST().newSimpleName( getCodeGeneratorData().getJavaLayoutData().getInflatedViewName())); } } invocation.arguments().add(rQualified2); CastExpression castExpr = onCreateDeclaration.getAST().newCastExpression(); castExpr.setExpression(invocation); castExpr.setType(guiType); Assignment assign = onCreateDeclaration.getAST().newAssignment(); SimpleName variableId = onCreateDeclaration.getAST().newSimpleName(node.getNodeId()); FieldAccess fieldAccess = onCreateDeclaration.getAST().newFieldAccess(); fieldAccess.setExpression(onCreateDeclaration.getAST().newThisExpression()); fieldAccess.setName(variableId); assign.setLeftHandSide(fieldAccess); assign.setOperator(Assignment.Operator.ASSIGN); assign.setRightHandSide(castExpr); ExpressionStatement expr = onCreateDeclaration.getAST().newExpressionStatement(assign); insertStatementsAtOnCreateDeclaration(expr, true); } /** * Gets the name of the variable based on the type declared in the layout xml * @param node * @return * @throws CoreException */ public SimpleName getNodeVariableTypeBasedOnLayoutNode(LayoutNode node) throws CoreException { SimpleName guiName = null; String clazzName = node.getClazzName(); if (node.isFragmentPlaceholder() && (clazzName != null)) { //use type defined in the xml IStatus nameStatus = JavaConventions.validateIdentifier(clazzName, "5", "5"); if (nameStatus.isOK()) { guiName = onCreateDeclaration.getAST().newSimpleName(clazzName); } else { throw new CoreException(nameStatus); } } else { guiName = onCreateDeclaration.getAST().newSimpleName(node.getNodeType()); } return guiName; } }