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