/* * 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.generatemenucode.model.codegenerators; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jdt.core.IJavaModelStatus; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; import org.eclipse.jdt.core.dom.PrimitiveType; 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.SuperMethodInvocation; 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.generatemenucode.model.MenuItemNode; import com.motorola.studio.android.generateviewbylayout.JavaViewBasedOnLayoutModifierConstants; /** * Responsible to create menu handlers (Android code) for activities / fragments */ public class MenuHandlerCodeGenerator extends AbstractMenuCodeGenerator { /* * Constants (method bindings to avoid repetitive code) */ private static final String ON_CREATE_OPTIONS_MENU_MENU_METHODBINDING = "public boolean onCreateOptionsMenu(android.view.Menu)"; //$NON-NLS-1$ private static final String ON_CREATE_OPTIONS_MENU_MENU_METHODBINDING_FRAG = "public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater)"; //$NON-NLS-1$ private static final String ON_OPTIONS_ITEM_SELECTED_MENU_ITEM_METHODBINDING = "public boolean onOptionsItemSelected(android.view.MenuItem)"; //$NON-NLS-1$ private static final String ON_CREATE_METHODBINDING = "public void onCreate(android.os.Bundle)"; //$NON-NLS-1$ /** * @param codeGeneratorData * @param typeDeclaration */ public MenuHandlerCodeGenerator(CodeGeneratorDataBasedOnMenu codeGeneratorData, TypeDeclaration typeDeclaration) { super(codeGeneratorData, typeDeclaration); } @Override public void generateCode(IProgressMonitor monitor) throws JavaModelException { if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnMenu.TYPE.FRAGMENT)) { //for fragments, it is required to change onCreate to add setHasOptionMenu invocation createOnCreateAndSetHasOptionMenu(monitor); } insertMethodToInflateMenu(monitor); addMethodToHandleMenu(monitor); } /** * Calls method that enables menu contribution for fragments * <br> * GENERATED_CODE_FORMAT: * <br> * <br> * <code> * public void onCreate (Bundle savedInstanceState) { * <br> * setHasOptionMenu(true); * <br> * super.onCreate(savedInstanceState); * <br> * } * </code> */ @SuppressWarnings("unchecked") private void createOnCreateAndSetHasOptionMenu(IProgressMonitor monitor) { MethodDeclaration onCreateMethodDeclaration = createOnCreateMethod(monitor); MethodDeclaration foundMethod = isMethodAlreadyDeclared(onCreateMethodDeclaration, ON_CREATE_METHODBINDING); if (foundMethod != null) { //method onCreateOptionsMenu is already created => use the found method instead of the new created one onCreateMethodDeclaration = foundMethod; } MethodInvocation setHasOptionMenuInvoke = createMethodInvocation(null, CodeGeneratorBasedOnMenuConstants.SET_HAS_OPTIONS_MENU); BooleanLiteral defaultValue = typeDeclaration.getAST().newBooleanLiteral(true); setHasOptionMenuInvoke.arguments().add(defaultValue); ExpressionStatement statement = typeDeclaration.getAST().newExpressionStatement(setHasOptionMenuInvoke); addStatementIfNotFound(onCreateMethodDeclaration, statement, false); List<String> arguments = new ArrayList<String>(); arguments.add(CodeGeneratorBasedOnMenuConstants.SAVED_INSTANCE_STATE); //$NON-NLS-1$ //super.onCreate(savedInstanceState); insertSuperInvocation(onCreateMethodDeclaration, CodeGeneratorBasedOnMenuConstants.ON_CREATE, arguments); if (foundMethod == null) { //method onCreateOptionsMenu was not yet declared typeDeclaration.bodyDeclarations().add(onCreateMethodDeclaration); } } private MethodDeclaration createOnCreateMethod(IProgressMonitor monitor) { SubMonitor subMonitor = SubMonitor.convert(monitor); subMonitor.beginTask( CodeUtilsNLS.MenuHandlerCodeGenerator_AddingOnCreateAndSetHasOptionMenu, 1); List<SingleVariableDeclaration> parameters = new ArrayList<SingleVariableDeclaration>(); SingleVariableDeclaration param1 = createVariableDeclarationFromStrings(CodeGeneratorBasedOnMenuConstants.BUNDLE, CodeGeneratorBasedOnMenuConstants.SAVED_INSTANCE_STATE); parameters.add(param1); //public void onCreate (Bundle savedInstanceState) MethodDeclaration methodDeclaration = addMethodDeclaration(ModifierKeyword.PUBLIC_KEYWORD, CodeGeneratorBasedOnMenuConstants.ON_CREATE, PrimitiveType.VOID, parameters); Block block = typeDeclaration.getAST().newBlock(); methodDeclaration.setBody(block); subMonitor.worked(1); return methodDeclaration; } /** * Adds method to inflate menu * <br> * GENERATED_CODE_FORMAT (for activity): * <br> * <br> * <code> * public boolean onCreateOptionsMenu(Menu menu) { * <br> * MenuInflater inflater = getMenuInflater(); * <br> * inflater.inflate(R.menu.<menu_id>, menu); * <br> * return true; * <br> * } * * <br> * GENERATED_CODE_FORMAT (for fragment): * <br> * <br> * <code> * public boolean onCreateOptionsMenu(Menu menu, MenuInflater inflater) { * <br> * inflater.inflate(R.menu.<menu_id>, menu); * <br> * return true; * <br> * } * </code> * * @param monitor to indicate progress when adding method declaration */ @SuppressWarnings("unchecked") private void insertMethodToInflateMenu(IProgressMonitor monitor) { SubMonitor subMonitor = SubMonitor.convert(monitor); //need to look at each GUI item and them create 1 method subMonitor.beginTask(CodeUtilsNLS.MenuHandlerCodeGenerator_AddingOnCreateOptionsMenu, 1); MethodDeclaration methodDeclaration = null; MethodDeclaration foundMethod = null; if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnMenu.TYPE.ACTIVITY)) { //declare method methodDeclaration = declareOnCreateOptionsMenuMethod(); foundMethod = isMethodAlreadyDeclared(methodDeclaration, ON_CREATE_OPTIONS_MENU_MENU_METHODBINDING); if (foundMethod != null) { //method onCreateOptionsMenu is already created => use the found method instead of the new created one methodDeclaration = foundMethod; } //declare inflater variable declareInflaterVariable(methodDeclaration); //call inflate method callsInflateMethod(methodDeclaration); //add return statement createReturnStatement(methodDeclaration); } else if (getCodeGeneratorData().getAssociatedType().equals( CodeGeneratorDataBasedOnMenu.TYPE.FRAGMENT)) { //declare method methodDeclaration = declareOnCreateOptionsMenuMethodFragment(); foundMethod = isMethodAlreadyDeclared(methodDeclaration, ON_CREATE_OPTIONS_MENU_MENU_METHODBINDING_FRAG); if (foundMethod != null) { //method onCreateOptionsMenu is already created => use the found method instead of the new created one methodDeclaration = foundMethod; } //call inflate method callsInflateMethod(methodDeclaration); //add return statement createReturnStatementFragment(methodDeclaration); } if (foundMethod == null) { //method onCreateOptionsMenu was not yet declared typeDeclaration.bodyDeclarations().add(methodDeclaration); } subMonitor.worked(1); } /** * @param methodDeclaration */ protected void createReturnStatementFragment(MethodDeclaration methodDeclaration) { List<String> arguments = new ArrayList<String>(); arguments.add(CodeGeneratorBasedOnMenuConstants.MENU_VARIABLE); //$NON-NLS-1$ arguments.add(CodeGeneratorBasedOnMenuConstants.INFLATER_VARIABLE); //$NON-NLS-1$ insertSuperInvocation(methodDeclaration, CodeGeneratorBasedOnMenuConstants.ON_CREATE_OPTIONS_MENU, arguments); } /** * Generates the following code: * <code>public boolean onCreateOptionsMenu(Menu menu){}</code> * @return */ protected MethodDeclaration declareOnCreateOptionsMenuMethod() { List<SingleVariableDeclaration> parameters = new ArrayList<SingleVariableDeclaration>(); SingleVariableDeclaration param1 = createVariableDeclarationFromStrings(CodeGeneratorBasedOnMenuConstants.MENU_TYPE, CodeGeneratorBasedOnMenuConstants.MENU_VARIABLE); parameters.add(param1); MethodDeclaration methodDeclaration = addMethodDeclaration(ModifierKeyword.PUBLIC_KEYWORD, CodeGeneratorBasedOnMenuConstants.ON_CREATE_OPTIONS_MENU, PrimitiveType.BOOLEAN, parameters); Block block = typeDeclaration.getAST().newBlock(); methodDeclaration.setBody(block); return methodDeclaration; } /** * Generates the following code: * <code>public void onCreateOptionsMenu(Menu menu, Inflater inflate){}</code> * @return */ protected MethodDeclaration declareOnCreateOptionsMenuMethodFragment() { List<SingleVariableDeclaration> parameters = new ArrayList<SingleVariableDeclaration>(); SingleVariableDeclaration param1 = createVariableDeclarationFromStrings(CodeGeneratorBasedOnMenuConstants.MENU_TYPE, CodeGeneratorBasedOnMenuConstants.MENU_VARIABLE); parameters.add(param1); SingleVariableDeclaration param2 = createVariableDeclarationFromStrings( CodeGeneratorBasedOnMenuConstants.MENU_INFLATER_VARIABLE, CodeGeneratorBasedOnMenuConstants.INFLATER_VARIABLE); parameters.add(param2); //public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { MethodDeclaration methodDeclaration = addMethodDeclaration(ModifierKeyword.PUBLIC_KEYWORD, CodeGeneratorBasedOnMenuConstants.ON_CREATE_OPTIONS_MENU, PrimitiveType.VOID, parameters); Block block = typeDeclaration.getAST().newBlock(); methodDeclaration.setBody(block); return methodDeclaration; } /** * Generates the following code: * <code>inflater.inflate(R.menu.<menu_id>, menu);</code> * @param methodDeclaration */ @SuppressWarnings("unchecked") protected void callsInflateMethod(MethodDeclaration methodDeclaration) { MethodInvocation inflateInvoke = createMethodInvocation(CodeGeneratorBasedOnMenuConstants.INFLATER_VARIABLE, CodeGeneratorBasedOnMenuConstants.INFLATE_METHOD); SimpleName r = typeDeclaration.getAST().newSimpleName(CodeGeneratorBasedOnMenuConstants.R); SimpleName menu = typeDeclaration.getAST().newSimpleName( CodeGeneratorBasedOnMenuConstants.MENU_VARIABLE); SimpleName menuIdName = typeDeclaration.getAST().newSimpleName( getCodeGeneratorData().getMenuFile().getNameWithoutExtension()); QualifiedName rMenu = typeDeclaration.getAST().newQualifiedName(r, menu); QualifiedName menuId = typeDeclaration.getAST().newQualifiedName(rMenu, menuIdName); inflateInvoke.arguments().add(menuId); SimpleName menuArg = typeDeclaration.getAST().newSimpleName( CodeGeneratorBasedOnMenuConstants.MENU_VARIABLE); inflateInvoke.arguments().add(menuArg); ExpressionStatement inflateExprStatement = typeDeclaration.getAST().newExpressionStatement(inflateInvoke); addStatementIfNotFound(methodDeclaration, inflateExprStatement, false); } /** * Generates the following code: * <code>MenuInflater inflater = getMenuInflater();</code> * @param methodDeclaration */ protected void declareInflaterVariable(MethodDeclaration methodDeclaration) { MethodInvocation getMenuInflaterInvoke = createMethodInvocation(null, CodeGeneratorBasedOnMenuConstants.GET_MENU_INFLATER); VariableDeclarationFragment declarationFragment = typeDeclaration.getAST().newVariableDeclarationFragment(); SimpleName inflater = typeDeclaration.getAST().newSimpleName( CodeGeneratorBasedOnMenuConstants.INFLATER_VARIABLE); declarationFragment.setName(inflater); declarationFragment.setInitializer(getMenuInflaterInvoke); VariableDeclarationStatement declarationStatement = typeDeclaration.getAST().newVariableDeclarationStatement(declarationFragment); SimpleName menuInflaterName = typeDeclaration.getAST().newSimpleName( CodeGeneratorBasedOnMenuConstants.MENU_INFLATER_VARIABLE); SimpleType menuInflaterType = typeDeclaration.getAST().newSimpleType(menuInflaterName); declarationStatement.setType(menuInflaterType); addStatementIfNotFound(methodDeclaration, declarationStatement, false); } /** * Adds method to declare menu events handler * @param monitor * @throws JavaModelException * * <br> * GENERATED_CODE_FORMAT: * <br> * <br> * <code> * public boolean onOptionsItemSelected(MenuItem item) { * <br> * if (item.getItemId() == $MENUITEM_ID1) { * <br> * } else if (item.getItemId() == $MENUITEM_ID2) { * <br> * } else { * <br> * return super.onOptionsItemSelected(item); * <br> * } * <br> * return true; * <br> * } * <br> * </code> */ private void addMethodToHandleMenu(IProgressMonitor monitor) throws JavaModelException { SubMonitor subMonitor = SubMonitor.convert(monitor); //need to look at each GUI item and them create 1 method subMonitor.beginTask(CodeUtilsNLS.MenuHandlerCodeGenerator_AddingOnOptionsItemSelected, codeGeneratorData.getMenuItemsNodes().size() + 1); IfStatement ifStatement = null; for (MenuItemNode node : codeGeneratorData.getMenuItemsNodes()) { if ((node.getOnClickMethod() == null) || node.getOnClickMethod().equals("")) //$NON-NLS-1$ { //does not have on click declared if (ifStatement == null) { //create method in the first time, after that, start to add new items in the same method ifStatement = createOnOptionsItemSelectedForMenuItems(node); } else { //already have onOptionsItemSelected method declared => append new item into the "else if" chain addElseIfForEachMenuItemId(ifStatement, node); } } else { //has on click declared createMenuItemHandlerForOnClick(node); } subMonitor.worked(1); } } /** * Creates method to handle menu item if android:onClick is defined on menu.xml * <code>public void $myMethodName(MenuItem item)</code> * @param node */ @SuppressWarnings("unchecked") private void createMenuItemHandlerForOnClick(MenuItemNode node) throws JavaModelException { if (node.getOnClickMethod() != null) { int i = 0; //check if the onClick is valid String invalidChar = null; boolean validMethodName = true; for (char ch : node.getOnClickMethod().toCharArray()) { if ((i <= 0) && !Character.isJavaIdentifierStart(ch)) { invalidChar = "" + ch; //$NON-NLS-1$ validMethodName = false; break; } else if ((i > 0) && !Character.isJavaIdentifierPart(ch)) { //i>0 invalidChar = "" + ch; //$NON-NLS-1$ validMethodName = false; break; } i++; } if (!validMethodName) { Object[] bindings = new Object[] { node.getOnClickMethod(), getCodeGeneratorData().getMenuFile().getFile().getName(), invalidChar }; String msg = CodeUtilsNLS .bind(CodeUtilsNLS.MenuHandlerCodeGenerator_InvalidJavaCharacterInAndroidOnClickAttribute, bindings); throw new JavaModelException(new IllegalArgumentException(msg), IJavaModelStatus.ERROR); } MethodDeclaration methodDeclaration = addMethodDeclaration(ModifierKeyword.PUBLIC_KEYWORD, node.getOnClickMethod(), PrimitiveType.VOID, CodeGeneratorBasedOnMenuConstants.MENU_ITEM, CodeGeneratorBasedOnMenuConstants.ITEM); String methodBinding = "public void " + node.getOnClickMethod() + "(android.view.MenuItem)"; //$NON-NLS-1$ //$NON-NLS-2$ MethodDeclaration foundMethod = isMethodAlreadyDeclared(methodDeclaration, methodBinding); if (foundMethod == null) { //method public void $myMethodName(MenuItem item) was not yet declared Block block = typeDeclaration.getAST().newBlock(); methodDeclaration.setBody(block); typeDeclaration.bodyDeclarations().add(methodDeclaration); } } } /** * Creates method with handle for menu items (if android:onClick) is not defined on menu.xml * <code>public boolean onOptionsItemSelected(MenuItem item)</code> */ @SuppressWarnings("unchecked") private IfStatement createOnOptionsItemSelectedForMenuItems(MenuItemNode node) { IfStatement ifSt; //declare method MethodDeclaration methodDeclaration = addMethodDeclaration(ModifierKeyword.PUBLIC_KEYWORD, CodeGeneratorBasedOnMenuConstants.ON_OPTIONS_ITEM_SELECTED, PrimitiveType.BOOLEAN, CodeGeneratorBasedOnMenuConstants.MENU_ITEM, CodeGeneratorBasedOnMenuConstants.ITEM); MethodDeclaration foundMethod = isMethodAlreadyDeclared(methodDeclaration, ON_OPTIONS_ITEM_SELECTED_MENU_ITEM_METHODBINDING); Block block = null; if (foundMethod != null) { //method onOptionsItemSelected is already created => use the found method instead of the new created one methodDeclaration = foundMethod; block = methodDeclaration.getBody(); } else { //method onOptionsItemSelected not found => create block to insert statements block = typeDeclaration.getAST().newBlock(); methodDeclaration.setBody(block); } //create if and else-if's ifSt = createElseIfForEachMenuItemId(node); IfStatement foundIfStatement = (IfStatement) findIfStatementAlreadyDeclared(ifSt, true, block.statements()); if (foundIfStatement != null) { ifSt = foundIfStatement; } else { //if not existent yet then: //1-add else addingElseExpression(ifSt, methodDeclaration); //2-add if statement only if there is not another one addStatementIfNotFound(block, ifSt, true); } createReturnStatement(methodDeclaration); if (foundMethod == null) { //method onOptionsItemSelected was not yet declared typeDeclaration.bodyDeclarations().add(methodDeclaration); } return ifSt; } /** * @param ifSt * @param methodDeclaration */ private void addingElseExpression(IfStatement ifSt, MethodDeclaration methodDeclaration) { Block block = typeDeclaration.getAST().newBlock(); ReturnStatement returnStatement = typeDeclaration.getAST().newReturnStatement(); List<String> arguments = new ArrayList<String>(); arguments.add(CodeGeneratorBasedOnMenuConstants.ITEM); //$NON-NLS-1$ SuperMethodInvocation superMethodInvocation = createSuperMethodInvocation( CodeGeneratorBasedOnMenuConstants.ON_OPTIONS_ITEM_SELECTED, arguments); //$NON-NLS-1$ returnStatement.setExpression(superMethodInvocation); addStatementIfNotFound(block, returnStatement, false); ifSt.setElseStatement(block); } /** * @return "else if" chain for each menu item id */ private IfStatement createElseIfForEachMenuItemId(MenuItemNode node) { IfStatement ifStatement = typeDeclaration.getAST().newIfStatement(); addElseIfForEachMenuItemId(ifStatement, node); return ifStatement; } /** * Creates else if and else statements for each menu item node * @param ifSt If statement where the next "else if" will be appended * @param node Menu node */ private void addElseIfForEachMenuItemId(IfStatement ifSt, MenuItemNode node) { MethodInvocation invocation = typeDeclaration.getAST().newMethodInvocation(); invocation.setExpression(getVariableName(CodeGeneratorBasedOnMenuConstants.ITEM)); SimpleName getIdName = typeDeclaration.getAST().newSimpleName( CodeGeneratorBasedOnMenuConstants.GET_ITEM_ID); invocation.setName(getIdName); SimpleName r = typeDeclaration.getAST().newSimpleName(JavaViewBasedOnLayoutModifierConstants.R); SimpleName id = typeDeclaration.getAST().newSimpleName(JavaViewBasedOnLayoutModifierConstants.ID); QualifiedName rid = typeDeclaration.getAST().newQualifiedName(r, id); SimpleName guiId = typeDeclaration.getAST().newSimpleName(node.getId()); QualifiedName guiQN = typeDeclaration.getAST().newQualifiedName(rid, guiId); createElseIfAndElseStatements(ifSt, invocation, guiQN); } }