com.motorola.studio.android.generatecode.JDTUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.motorola.studio.android.generatecode.JDTUtils.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.generatecode;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.osgi.util.NLS;

import com.motorola.studio.android.codeutils.i18n.CodeUtilsNLS;
import com.motorola.studio.android.common.IAndroidConstants;
import com.motorola.studio.android.common.exception.AndroidException;
import com.motorola.studio.android.common.log.StudioLogger;
import com.motorola.studio.android.generatemenucode.model.codegenerators.CodeGeneratorBasedOnMenuVisitor;
import com.motorola.studio.android.generatemenucode.model.codegenerators.CodeGeneratorDataBasedOnMenu;
import com.motorola.studio.android.generateviewbylayout.GenerateCodeBasedOnLayoutVisitor;
import com.motorola.studio.android.generateviewbylayout.model.CodeGeneratorDataBasedOnLayout;
import com.motorola.studio.android.generateviewbylayout.model.JavaLayoutData;

/**
 * Class that implements convenient methods to abstract and handle JDT related operations.
 * This class is not meant to be instantiated.
 * */
public class JDTUtils {

    private JDTUtils() {
        //does nothing.
        //prevents other objects to instantiate this class. 
    }

    /**
     * Retrieves the name of the inflated menu inside Activity or Fragment {@code type}.
     * @param project The android project that contains the activity or fragment. 
     * @param compUnit The compilation unit of the activity or fragment.
     * @return The name of the inflated menu inside {@code compUnit}.
     * */
    public static String getInflatedMenuFileName(IProject project, ICompilationUnit compUnit) {
        //check if type is either activity or fragment
        //check if type inflates a menu on OnCreateOptionsMenu
        //return the name of the inflated menu with ".xml" appended

        CodeGeneratorBasedOnMenuVisitor visitor = new CodeGeneratorBasedOnMenuVisitor();
        CompilationUnit cpAstNode = parse(compUnit);
        StudioLogger.info("Trying to visit code for class: " + compUnit.getResource().getName()); //$NON-NLS-1$
        try {
            cpAstNode.accept(visitor);
        } catch (IllegalArgumentException illegalArgumentException) {
            StudioLogger.error(
                    "Error while trying to visit code to get an inflated menu:" + compUnit.getResource().getName());
        }
        return visitor.getInflatedMenuName();
    }

    /**
     * Retrieves a list with the available android activities inside {@code project}.
     * @param project The android project to retrieve the activities.
     * @param monitor A progress monitor to be used to show operation status. 
     * */
    public static List<IType> getAvailableActivities(IProject project, IProgressMonitor monitor)
            throws JavaModelException {
        return getAvailableSubclasses(project, "android.app.Activity", monitor); //$NON-NLS-1$
    }

    /**
     * Retrieves the list of subclasses of a given {@code superclass} inside an {@code androidProject}.
     * @throws JavaModelException If there are problems parsing java files.
     * */
    public static List<IType> getAvailableSubclasses(IProject androidProject, String superclass,
            IProgressMonitor monitor) throws JavaModelException {
        List<IType> availableActivities = new ArrayList<IType>();

        SubMonitor subMonitor = SubMonitor.convert(monitor);
        subMonitor.beginTask("Resolving available types", 1000); //$NON-NLS-1$

        IJavaProject javaProject = JavaCore.create(androidProject);
        IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(androidProject.findMember("src")); //$NON-NLS-1$
        ArrayList<IPackageFragment> fragments = new ArrayList<IPackageFragment>();
        for (IJavaElement element : root.getChildren()) {
            fragments.add((IPackageFragment) element);
        }

        subMonitor.worked(100);

        if (fragments.size() == 0) {
            subMonitor.worked(900);
        }

        for (IPackageFragment fragment : fragments) {
            ICompilationUnit[] units = fragment.getCompilationUnits();
            if (units.length == 0) {
                subMonitor.worked(900 / fragments.size());
            }
            for (int j = 0; j < units.length; j++) {
                ICompilationUnit unit = units[j];
                IType[] availableTypes = unit.getTypes();
                if (availableTypes.length == 0) {
                    subMonitor.worked(900 / fragments.size() / units.length);
                }

                for (int k = 0; k < availableTypes.length; k++) {
                    ITypeHierarchy hierarchy = availableTypes[k].newSupertypeHierarchy(
                            subMonitor.newChild(900 / fragments.size() / units.length / availableTypes.length));

                    if (isSubclass(hierarchy, availableTypes[k], superclass)) {
                        availableActivities.add(availableTypes[k]);
                    }
                }

            }
        }

        return availableActivities;
    }

    /**
     * Retrieves the list of fragments of a given {@code androidProject}.
     * @throws JavaModelException If there are problems parsing java files.
     * */
    public static List<IType> getAvailableFragmentsSubclasses(IProject androidProject, IProgressMonitor monitor)
            throws JavaModelException {
        List<IType> availableFragments = new ArrayList<IType>();

        SubMonitor subMonitor = SubMonitor.convert(monitor);
        subMonitor.beginTask("Resolving available types", 1000); //$NON-NLS-1$

        IJavaProject javaProject = JavaCore.create(androidProject);
        IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(androidProject.findMember("src")); //$NON-NLS-1$
        ArrayList<IPackageFragment> fragments = new ArrayList<IPackageFragment>();
        for (IJavaElement element : root.getChildren()) {
            fragments.add((IPackageFragment) element);
        }

        subMonitor.worked(100);

        if (fragments.size() == 0) {
            subMonitor.worked(900);
        }

        for (IPackageFragment fragment : fragments) {
            ICompilationUnit[] units = fragment.getCompilationUnits();
            if (units.length == 0) {
                subMonitor.worked(900 / fragments.size());
            }
            for (int j = 0; j < units.length; j++) {
                ICompilationUnit unit = units[j];
                IType[] availableTypes = unit.getTypes();
                if (availableTypes.length == 0) {
                    subMonitor.worked(900 / fragments.size() / units.length);
                }

                for (int k = 0; k < availableTypes.length; k++) {
                    ITypeHierarchy hierarchy = availableTypes[k].newSupertypeHierarchy(
                            subMonitor.newChild(900 / fragments.size() / units.length / availableTypes.length));

                    if (isFragmentSubclass(hierarchy, availableTypes[k])) {
                        availableFragments.add(availableTypes[k]);
                    }
                }

            }
        }

        return availableFragments;
    }

    /*
     *  Returns true if the {@code type} belongs to {@code superclass} hierarchy. Otherwise, returns false.
     * */
    private static boolean isSubclass(ITypeHierarchy hierarchy, IType type, String superclass) {
        boolean contains = false;
        IType superclasstype = hierarchy.getSuperclass(type);
        if (superclasstype != null) {
            if (hierarchy.getType().getFullyQualifiedName().equals(superclass)
                    || superclasstype.getFullyQualifiedName().equals(superclass)) {
                contains = true;
            } else {
                contains = isSubclass(hierarchy, superclasstype, superclass);
            }
        }

        return contains;
    }

    /**
     * @return
     *  True if the {@code type} belongs to {@code superclass} hierarchy. Otherwise, returns false.
     * */
    public static boolean isSubclass(IType type, String superclass) throws JavaModelException {
        ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
        return isSubclass(typeHierarchy, type, superclass);
    }

    /*
     * Verifies if a given class extends a Fragment class (from sdk after 3.0 or from compatibility pack) 
     * 
     * @param hierarchy the hierarchy abstraction to the {@code type}
     * @param type the type to be checked
     * @return
     *  True if {@code type} has a Android Fragment in its type hierarchy.
     */
    private static boolean isFragmentSubclass(ITypeHierarchy hierarchy, IType type) {
        boolean contains = false;
        IType superclasstype = hierarchy.getSuperclass(type);
        if (superclasstype != null) {
            if (isAndroidFragment(superclasstype.getFullyQualifiedName())) {
                contains = true;
            } else {
                contains = isFragmentSubclass(hierarchy, superclasstype);
            }
        }

        return contains;
    }

    /*
     * Verifies if a given class extends a Fragment class (Fragment from compatibility pack) 
     * 
     * @param hierarchy
     * @param type
     * @return
     */
    private static boolean isCompatibilityPackFragmentsSubclass(ITypeHierarchy hierarchy, IType type) {
        boolean contains = false;
        IType superclasstype = hierarchy.getSuperclass(type);
        if (superclasstype != null) {
            if (isAndroidCompatibilityPackFragment(superclasstype.getFullyQualifiedName())) {
                contains = true;
            } else {
                contains = isCompatibilityPackFragmentsSubclass(hierarchy, superclasstype);
            }
        }

        return contains;
    }

    /*
     * Verifies if a given class extends a Fragment class (FragmentActivity from compatibility pack) 
     * 
     * @param hierarchy
     * @param type
     * @return
     */
    private static boolean isCompatibilityPackFragmentActivitySubclass(ITypeHierarchy hierarchy, IType type) {
        boolean contains = false;
        IType superclasstype = hierarchy.getSuperclass(type);
        if (superclasstype != null) {
            if (isAndroidCompatibilityPackFragmentActivity(superclasstype.getFullyQualifiedName())) {
                contains = true;
            } else {
                contains = isCompatibilityPackFragmentActivitySubclass(hierarchy, superclasstype);
            }
        }

        return contains;
    }

    private static boolean isAndroidFragment(String className) {
        boolean result = false;

        if ((className != null) && (className.startsWith("android.")) && (className.endsWith(".Fragment"))) {
            result = true;
        }

        return result;
    }

    private static boolean isAndroidCompatibilityPackFragment(String className) {
        boolean result = false;

        if ((className != null) && (className.startsWith("android.support."))
                && (className.endsWith(".Fragment"))) {
            result = true;
        }

        return result;
    }

    private static boolean isAndroidCompatibilityPackFragmentActivity(String className) {
        boolean result = false;

        if ((className != null) && (className.startsWith("android.support."))
                && (className.endsWith(".FragmentActivity"))) {
            result = true;
        }

        return result;
    }

    /**
     * Parses source code.
     *
     * @param lwUnit
     *            the Java Model handle for the compilation unit
     * @return the root AST node of the parsed source
     */
    public static CompilationUnit parse(ICompilationUnit lwUnit) {
        ASTParser parser = ASTParser.newParser(AST.JLS3);
        parser.setKind(ASTParser.K_COMPILATION_UNIT);
        parser.setSource(lwUnit); // set source
        parser.setResolveBindings(true); // we need bindings later on
        return (CompilationUnit) parser.createAST(null /* IProgressMonitor */); // parse
    }

    /**
     * Creates the representation of a layout file based on the compilation unit
     * @param visitor
     * @param layout
     * @return layout file
     * @throws AndroidException if layout xml is malformed
     */
    public static CodeGeneratorDataBasedOnLayout createLayoutFile(IProject project, ICompilationUnit compUnit)
            throws AndroidException {
        GenerateCodeBasedOnLayoutVisitor visitor = new GenerateCodeBasedOnLayoutVisitor();
        CompilationUnit cpAstNode = parse(compUnit);
        StudioLogger.info("Trying to visit code for class: " + compUnit.getResource().getName()); //$NON-NLS-1$
        try {
            cpAstNode.accept(visitor);
        } catch (IllegalArgumentException illegalArgumentException) {
            String msg = CodeUtilsNLS.JDTUtils_FragmentOnCreateViewWithProblemsOrWithWrongFormat;
            throw new AndroidException(msg, illegalArgumentException);
        }
        IFile layout = project.getFile(File.separator + IAndroidConstants.FD_RES + File.separator
                + IAndroidConstants.FD_LAYOUT + File.separator + visitor.getLayoutName() + ".xml"); //$NON-NLS-1$
        CodeGeneratorDataBasedOnLayout codeGeneratorData = null;
        if (visitor.getLayoutName() == null) {
            //layout set or inflate not declared 
            throw new AndroidException(CodeUtilsNLS.UI_ChooseLayoutItemsDialog_Error_onCreate_Not_Declared);
        } else {
            StudioLogger.info("Trying to read layout: " + layout); //$NON-NLS-1$            
            try {
                codeGeneratorData = new CodeGeneratorDataBasedOnLayout();
                codeGeneratorData.init(visitor.getLayoutName(), layout.getLocation().toFile());
                codeGeneratorData.setAssociatedType(visitor.getTypeAssociatedToLayout());

                JavaLayoutData javaLayoutData = new JavaLayoutData();
                javaLayoutData.setInflatedViewName(visitor.getInflatedViewName());
                javaLayoutData.setDeclaredViewIdsOnCode(visitor.getDeclaredViewIds());
                javaLayoutData.setSavedViewIds(visitor.getSavedViewIds());
                javaLayoutData.setRestoredViewIds(visitor.getRestoredViewIds());
                javaLayoutData.setVisitor(visitor);
                javaLayoutData.setCompUnit(compUnit);
                javaLayoutData.setCompUnitAstNode(cpAstNode);

                codeGeneratorData.setJavaLayoutData(javaLayoutData);
            } catch (AndroidException ae) {
                String errorsMsg = visitor.getLayoutName() != null
                        ? NLS.bind(CodeUtilsNLS.JDTUtils_MalformedXMLWhenFilenameAvailable_Error,
                                layout.getFullPath().toFile())
                        : CodeUtilsNLS.JDTUtils_MalformedXMLWhenFilenameNotAvailable_Error;
                throw new AndroidException(errorsMsg, ae);
            }
        }
        return codeGeneratorData;
    }

    /**
     * Creates the representation of a menu file based on the compilation unit
     * @param project
     * @param compUnit
     * @param menuFileName
     * @param typeAssociated
     * @return
     * @throws AndroidException if menu xml is malformed
     */
    public static CodeGeneratorDataBasedOnMenu createMenuFile(IProject project, ICompilationUnit compUnit,
            String menuFileName, CodeGeneratorDataBasedOnMenu.TYPE typeAssociated) throws AndroidException {
        CodeGeneratorBasedOnMenuVisitor visitor = new CodeGeneratorBasedOnMenuVisitor();
        CompilationUnit cpAstNode = parse(compUnit);
        StudioLogger.info("Trying to visit code for class: " + compUnit.getResource().getName()); //$NON-NLS-1$
        try {
            cpAstNode.accept(visitor);
        } catch (IllegalArgumentException illegalArgumentException) {
            String msg = CodeUtilsNLS.JDTUtils_GenerateCodeForMenuVisitingCode_Error;
            throw new AndroidException(msg, illegalArgumentException);
        }
        IFile menu = project.getFile(File.separator + IAndroidConstants.FD_RES + File.separator
                + IAndroidConstants.FD_MENU + File.separator + menuFileName); //$NON-NLS-1$
        CodeGeneratorDataBasedOnMenu codeGeneratorData = null;
        StudioLogger.info("Trying to read menu: " + menu); //$NON-NLS-1$            
        try {
            codeGeneratorData = new CodeGeneratorDataBasedOnMenu();
            codeGeneratorData.init(menuFileName, menu.getLocation().toFile());
            codeGeneratorData.setAssociatedType(typeAssociated);
            codeGeneratorData.setAbstractCodeVisitor(visitor);
            codeGeneratorData.setICompilationUnit(compUnit);
            codeGeneratorData.setCompilationUnit(cpAstNode);
        } catch (AndroidException ae) {
            String errorsMsg = NLS.bind(CodeUtilsNLS.JDTUtils_MalformedMenuXMLWhenFilenameAvailable_Error,
                    menu.getLocation().toFile().toString());
            throw new AndroidException(errorsMsg, ae);
        }

        return codeGeneratorData;
    }

    /**
     * Given a Java {@link IFile}, this method returns <code>true</code> in case
     * the qualified name entered as parameter represents its superclass.
     * 
     * @param javaFile Java {@link IFile}.
     * @param superClassFullyQualifiedName Super class fully qualified naem.
     * 
     * @return Returns <code>true</code> in case the Java {@link IFile} class
     * inherits from the super class represented by its full qualified name.
     * 
     * @throws JavaModelException Exception thrown in case there are problems retrieving
     * classes hierarchy. 
     */
    public static boolean isSubclass(IFile javaFile, String superClassFullyQualifiedName)
            throws JavaModelException {
        ICompilationUnit compUnit = JavaCore.createCompilationUnitFrom(javaFile);

        IType type = compUnit.getType(javaFile.getName().split("." + javaFile.getFileExtension())[0]); //$NON-NLS-1$

        ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());

        return isSubclass(typeHierarchy, type, superClassFullyQualifiedName);

    }

    /**
     * Verifies if a java class extends a Fragment class from a compatibility pack
     * 
     * @param javaFile
     * @return
     * @throws JavaModelException
     */
    public static boolean isFragmentSubclass(IFile javaFile) throws JavaModelException {
        ICompilationUnit compUnit = JavaCore.createCompilationUnitFrom(javaFile);

        IType type = compUnit.getType(javaFile.getName().split("." + javaFile.getFileExtension())[0]); //$NON-NLS-1$

        ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());

        return isFragmentSubclass(typeHierarchy, type);
    }

    /**
     * Verifies if a java class extends a Fragment class from a compatibility pack
     * 
     * @param javaFile
     * @return
     * @throws JavaModelException
     */
    public static boolean isCompatibilityFragmentSubclass(IFile javaFile) throws JavaModelException {
        ICompilationUnit compUnit = JavaCore.createCompilationUnitFrom(javaFile);

        IType type = compUnit.getType(javaFile.getName().split("." + javaFile.getFileExtension())[0]); //$NON-NLS-1$

        ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());

        return isCompatibilityPackFragmentsSubclass(typeHierarchy, type);
    }

    /**
     * Verifies if a java class extends a FragmentActivity class from a compatibility pack
     * 
     * @param javaFile
     * @return
     * @throws JavaModelException
     */
    public static boolean isCompatibilityFragmentActivitySubclass(IFile javaFile) throws JavaModelException {
        ICompilationUnit compUnit = JavaCore.createCompilationUnitFrom(javaFile);

        IType type = compUnit.getType(javaFile.getName().split("." + javaFile.getFileExtension())[0]); //$NON-NLS-1$

        ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());

        return isCompatibilityPackFragmentActivitySubclass(typeHierarchy, type);
    }

    /**
     * @return true if AST have at least one error (warnings are not considered), false otherwise.
     */
    public static boolean hasErrorInCompilationUnitAstUtils(CompilationUnit cpUnit) {
        boolean hasError = false;
        if (cpUnit != null) {
            IProblem[] problems = cpUnit.getProblems();
            for (IProblem probl : problems) {
                if (probl.isError()) {
                    hasError = true;
                    break;
                }
            }
        }
        return hasError;
    }

}