blob: 57632ea8775a99e3703684e83ec3477754b688b8 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.android.ide.eclipse.adt.internal.project;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.google.common.collect.Lists;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
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.ui.JavaUI;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import java.util.ArrayList;
import java.util.List;
/**
* Utility methods to manipulate projects.
*/
public final class BaseProjectHelper {
public static final String TEST_CLASS_OK = null;
/**
* Project filter to be used with {@link BaseProjectHelper#getAndroidProjects(IProjectFilter)}.
*/
public static interface IProjectFilter {
boolean accept(IProject project);
}
/**
* returns a list of source classpath for a specified project
* @param javaProject
* @return a list of path relative to the workspace root.
*/
@NonNull
public static List<IPath> getSourceClasspaths(IJavaProject javaProject) {
List<IPath> sourceList = Lists.newArrayList();
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
if (classpaths != null) {
for (IClasspathEntry e : classpaths) {
if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
sourceList.add(e.getPath());
}
}
}
return sourceList;
}
/**
* returns a list of source classpath for a specified project
* @param project
* @return a list of path relative to the workspace root.
*/
public static List<IPath> getSourceClasspaths(IProject project) {
IJavaProject javaProject = JavaCore.create(project);
return getSourceClasspaths(javaProject);
}
/**
* Adds a marker to a file on a specific line. This methods catches thrown
* {@link CoreException}, and returns null instead.
* @param resource the resource to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param lineNumber the line number where to put the mark. If line is < 1, it puts the marker
* on line 1,
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
public final static IMarker markResource(IResource resource, String markerId,
String message, int lineNumber, int severity) {
return markResource(resource, markerId, message, lineNumber, -1, -1, severity);
}
/**
* Adds a marker to a file on a specific line, for a specific range of text. This
* methods catches thrown {@link CoreException}, and returns null instead.
*
* @param resource the resource to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param lineNumber the line number where to put the mark. If line is < 1, it puts
* the marker on line 1,
* @param startOffset the beginning offset of the marker (relative to the beginning of
* the document, not the line), or -1 for no range
* @param endOffset the ending offset of the marker
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
@Nullable
public final static IMarker markResource(IResource resource, String markerId,
String message, int lineNumber, int startOffset, int endOffset, int severity) {
if (!resource.isAccessible()) {
return null;
}
try {
IMarker marker = resource.createMarker(markerId);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
// if marker is text type, enforce a line number so that it shows in the editor
// somewhere (line 1)
if (lineNumber < 1 && marker.isSubtypeOf(IMarker.TEXT)) {
lineNumber = 1;
}
if (lineNumber >= 1) {
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
}
if (startOffset != -1) {
marker.setAttribute(IMarker.CHAR_START, startOffset);
marker.setAttribute(IMarker.CHAR_END, endOffset);
}
// on Windows, when adding a marker to a project, it takes a refresh for the marker
// to show. In order to fix this we're forcing a refresh of elements receiving
// markers (and only the element, not its children), to force the marker display.
resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
return marker;
} catch (CoreException e) {
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", //$NON-NLS-1$
markerId, resource.getFullPath());
}
return null;
}
/**
* Adds a marker to a resource. This methods catches thrown {@link CoreException},
* and returns null instead.
* @param resource the file to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
@Nullable
public final static IMarker markResource(IResource resource, String markerId,
String message, int severity) {
return markResource(resource, markerId, message, -1, severity);
}
/**
* Adds a marker to an {@link IProject}. This method does not catch {@link CoreException}, like
* {@link #markResource(IResource, String, String, int)}.
*
* @param project the project to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param severity the severity of the marker.
* @param priority the priority of the marker
* @return the IMarker that was added.
* @throws CoreException if the marker cannot be added
*/
@Nullable
public final static IMarker markProject(IProject project, String markerId,
String message, int severity, int priority) throws CoreException {
if (!project.isAccessible()) {
return null;
}
IMarker marker = project.createMarker(markerId);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
marker.setAttribute(IMarker.PRIORITY, priority);
// on Windows, when adding a marker to a project, it takes a refresh for the marker
// to show. In order to fix this we're forcing a refresh of elements receiving
// markers (and only the element, not its children), to force the marker display.
project.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
return marker;
}
/**
* Tests that a class name is valid for usage in the manifest.
* <p/>
* This tests the class existence, that it can be instantiated (ie it must not be abstract,
* nor non static if enclosed), and that it extends the proper super class (not necessarily
* directly)
* @param javaProject the {@link IJavaProject} containing the class.
* @param className the fully qualified name of the class to test.
* @param superClassName the fully qualified name of the expected super class.
* @param testVisibility if <code>true</code>, the method will check the visibility of the class
* or of its constructors.
* @return {@link #TEST_CLASS_OK} or an error message.
*/
public final static String testClassForManifest(IJavaProject javaProject, String className,
String superClassName, boolean testVisibility) {
try {
// replace $ by .
String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
// look for the IType object for this class
IType type = javaProject.findType(javaClassName);
if (type != null && type.exists()) {
// test that the class is not abstract
int flags = type.getFlags();
if (Flags.isAbstract(flags)) {
return String.format("%1$s is abstract", className);
}
// test whether the class is public or not.
if (testVisibility && Flags.isPublic(flags) == false) {
// if its not public, it may have a public default constructor,
// which would then be fine.
IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]);
if (basicConstructor != null && basicConstructor.exists()) {
int constructFlags = basicConstructor.getFlags();
if (Flags.isPublic(constructFlags) == false) {
return String.format(
"%1$s or its default constructor must be public for the system to be able to instantiate it",
className);
}
} else {
return String.format(
"%1$s must be public, or the system will not be able to instantiate it.",
className);
}
}
// If it's enclosed, test that it's static. If its declaring class is enclosed
// as well, test that it is also static, and public.
IType declaringType = type;
do {
IType tmpType = declaringType.getDeclaringType();
if (tmpType != null) {
if (tmpType.exists()) {
flags = declaringType.getFlags();
if (Flags.isStatic(flags) == false) {
return String.format("%1$s is enclosed, but not static",
declaringType.getFullyQualifiedName());
}
flags = tmpType.getFlags();
if (testVisibility && Flags.isPublic(flags) == false) {
return String.format("%1$s is not public",
tmpType.getFullyQualifiedName());
}
} else {
// if it doesn't exist, we need to exit so we may as well mark it null.
tmpType = null;
}
}
declaringType = tmpType;
} while (declaringType != null);
// test the class inherit from the specified super class.
// get the type hierarchy
ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
// if the super class is not the reference class, it may inherit from
// it so we get its supertype. At some point it will be null and we
// will stop
IType superType = type;
boolean foundProperSuperClass = false;
while ((superType = hierarchy.getSuperclass(superType)) != null &&
superType.exists()) {
if (superClassName.equals(superType.getFullyQualifiedName())) {
foundProperSuperClass = true;
}
}
// didn't find the proper superclass? return false.
if (foundProperSuperClass == false) {
return String.format("%1$s does not extend %2$s", className, superClassName);
}
return TEST_CLASS_OK;
} else {
return String.format("Class %1$s does not exist", className);
}
} catch (JavaModelException e) {
return String.format("%1$s: %2$s", className, e.getMessage());
}
}
/**
* Returns the {@link IJavaProject} for a {@link IProject} object.
* <p/>
* This checks if the project has the Java Nature first.
* @param project
* @return the IJavaProject or null if the project couldn't be created or if the project
* does not have the Java Nature.
* @throws CoreException if this method fails. Reasons include:
* <ul><li>This project does not exist.</li><li>This project is not open.</li></ul>
*/
public static IJavaProject getJavaProject(IProject project) throws CoreException {
if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
return JavaCore.create(project);
}
return null;
}
/**
* Reveals a specific line in the source file defining a specified class,
* for a specific project.
* @param project
* @param className
* @param line
* @return true if the source was revealed
*/
public static boolean revealSource(IProject project, String className, int line) {
// Inner classes are pointless: All we need is the enclosing type to find the file, and the
// line number.
// Since the anonymous ones will cause IJavaProject#findType to fail, we remove
// all of them.
int pos = className.indexOf('$');
if (pos != -1) {
className = className.substring(0, pos);
}
// get the java project
IJavaProject javaProject = JavaCore.create(project);
try {
// look for the IType matching the class name.
IType result = javaProject.findType(className);
if (result != null && result.exists()) {
// before we show the type in an editor window, we make sure the current
// workbench page has an editor area (typically the ddms perspective doesn't).
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
IWorkbenchPage page = window.getActivePage();
if (page.isEditorAreaVisible() == false) {
// no editor area? we open the java perspective.
new OpenJavaPerspectiveAction().run();
}
IEditorPart editor = JavaUI.openInEditor(result);
if (editor instanceof ITextEditor) {
// get the text editor that was just opened.
ITextEditor textEditor = (ITextEditor)editor;
IEditorInput input = textEditor.getEditorInput();
// get the location of the line to show.
IDocumentProvider documentProvider = textEditor.getDocumentProvider();
IDocument document = documentProvider.getDocument(input);
IRegion lineInfo = document.getLineInformation(line - 1);
// select and reveal the line.
textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
}
return true;
}
} catch (JavaModelException e) {
} catch (PartInitException e) {
} catch (BadLocationException e) {
}
return false;
}
/**
* Returns the list of android-flagged projects. This list contains projects that are opened
* in the workspace and that are flagged as android project (through the android nature)
* @param filter an optional filter to control which android project are returned. Can be null.
* @return an array of IJavaProject, which can be empty if no projects match.
*/
public static @NonNull IJavaProject[] getAndroidProjects(@Nullable IProjectFilter filter) {
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
return getAndroidProjects(javaModel, filter);
}
/**
* Returns the list of android-flagged projects for the specified java Model.
* This list contains projects that are opened in the workspace and that are flagged as android
* project (through the android nature)
* @param javaModel the Java Model object corresponding for the current workspace root.
* @param filter an optional filter to control which android project are returned. Can be null.
* @return an array of IJavaProject, which can be empty if no projects match.
*/
@NonNull
public static IJavaProject[] getAndroidProjects(@NonNull IJavaModel javaModel,
@Nullable IProjectFilter filter) {
// get the java projects
IJavaProject[] javaProjectList = null;
try {
javaProjectList = javaModel.getJavaProjects();
}
catch (JavaModelException jme) {
return new IJavaProject[0];
}
// temp list to build the android project array
ArrayList<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
// loop through the projects and add the android flagged projects to the temp list.
for (IJavaProject javaProject : javaProjectList) {
// get the workspace project object
IProject project = javaProject.getProject();
// check if it's an android project based on its nature
if (isAndroidProject(project)) {
if (filter == null || filter.accept(project)) {
androidProjectList.add(javaProject);
}
}
}
// return the android projects list.
return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]);
}
/**
* Returns true if the given project is an Android project (e.g. is a Java project
* that also has the Android nature)
*
* @param project the project to test
* @return true if the given project is an Android project
*/
public static boolean isAndroidProject(IProject project) {
// check if it's an android project based on its nature
try {
return project.hasNature(AdtConstants.NATURE_DEFAULT);
} catch (CoreException e) {
// this exception, thrown by IProject.hasNature(), means the project either doesn't
// exist or isn't opened. So, in any case we just skip it (the exception will
// bypass the ArrayList.add()
}
return false;
}
/**
* Returns the {@link IFolder} representing the output for the project for Android specific
* files.
* <p>
* The project must be a java project and be opened, or the method will return null.
* @param project the {@link IProject}
* @return an IFolder item or null.
*/
public final static IFolder getJavaOutputFolder(IProject project) {
try {
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
// get a java project from the normal project object
IJavaProject javaProject = JavaCore.create(project);
IPath path = javaProject.getOutputLocation();
path = path.removeFirstSegments(1);
return project.getFolder(path);
}
} catch (JavaModelException e) {
// Let's do nothing and return null
} catch (CoreException e) {
// Let's do nothing and return null
}
return null;
}
/**
* Returns the {@link IFolder} representing the output for the project for compiled Java
* files.
* <p>
* The project must be a java project and be opened, or the method will return null.
* @param project the {@link IProject}
* @return an IFolder item or null.
*/
@Nullable
public final static IFolder getAndroidOutputFolder(IProject project) {
try {
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
return project.getFolder(SdkConstants.FD_OUTPUT);
}
} catch (JavaModelException e) {
// Let's do nothing and return null
} catch (CoreException e) {
// Let's do nothing and return null
}
return null;
}
}