blob: 03273d999f2609357c5840170a4508cc5827cea2 [file] [log] [blame]
/*
* 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;
}
}