summaryrefslogtreecommitdiff
path: root/src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java')
-rw-r--r--src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java618
1 files changed, 618 insertions, 0 deletions
diff --git a/src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java b/src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java
new file mode 100644
index 0000000..b1b4cf5
--- /dev/null
+++ b/src/plugins/android.codeutils/src/com/motorola/studio/android/generatecode/JDTUtils.java
@@ -0,0 +1,618 @@
+/*
+ * 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;
+ }
+
+}