com.motorola.studio.android.generatemenucode.model.codegenerators.MenuHandlerCodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.motorola.studio.android.generatemenucode.model.codegenerators.MenuHandlerCodeGenerator.java

Source

/*
 * 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);
    }
}