/* * 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.common.project; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; import org.eclipse.core.resources.IFile; 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; /** * Utility methods to manipulate projects. */ public final class BaseProjectHelper { public static final String TEST_CLASS_OK = null; /** * returns a list of source classpath for a specified project * @param javaProject * @return a list of path relative to the workspace root. */ public static ArrayList<IPath> getSourceClasspaths(IJavaProject javaProject) { ArrayList<IPath> sourceList = new ArrayList<IPath>(); IClasspathEntry[] classpaths = javaProject.readRawClasspath(); if (classpaths != null) { for (IClasspathEntry e : classpaths) { if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) { sourceList.add(e.getPath()); } } } return sourceList; } /** * Adds a marker to a file on a specific line. This methods catches thrown * {@link CoreException}, and returns null instead. * @param file the file 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 addMarker(IResource file, String markerId, String message, int lineNumber, int severity) { try { IMarker marker = file.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); if (lineNumber < 1) { lineNumber = 1; } marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); // 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. file.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, file.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. */ public final static IMarker addMarker(IResource resource, String markerId, String message, int severity) { try { IMarker marker = resource.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); // 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 method does not catch {@link CoreException} and instead * throw them. * @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 lineNumber the line number where to put the mark if != -1. * @param severity the severity of the marker. * @param priority the priority of the marker * @return the IMarker that was added. * @throws CoreException */ public final static IMarker addMarker(IResource resource, String markerId, String message, int lineNumber, int severity, int priority) throws CoreException { IMarker marker = resource.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); if (lineNumber != -1) { marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); } 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. resource.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()); } } /** * Parses the manifest file for errors. * <p/> * This starts by removing the current XML marker, and then parses the xml for errors, both * of XML type and of Android type (checking validity of class files). * @param manifestFile * @param errorListener * @throws CoreException */ public static AndroidManifestParser parseManifestForError(IFile manifestFile, XmlErrorListener errorListener) throws CoreException { // remove previous markers if (manifestFile.exists()) { manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, IResource.DEPTH_ZERO); manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); } // and parse return AndroidManifestParser.parseForError( BaseProjectHelper.getJavaProject(manifestFile.getProject()), manifestFile, errorListener); } /** * 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 */ 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 */ public static void revealSource(IProject project, String className, int line) { // in case the type is enclosed, we need to replace the $ with . className = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$ // 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()); } } } catch (JavaModelException e) { } catch (PartInitException e) { } catch (BadLocationException e) { } } /** * 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) * @return an array of IJavaProject, which can be empty if no projects match. */ public static IJavaProject[] getAndroidProjects() { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IJavaModel javaModel = JavaCore.create(workspaceRoot); return getAndroidProjects(javaModel); } /** * 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. * @return an array of IJavaProject, which can be empty if no projects match. */ public static IJavaProject[] getAndroidProjects(IJavaModel javaModel) { // 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 try { if (project.hasNature(AndroidConstants.NATURE)) { androidProjectList.add(javaProject); } } 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 the android projects list. return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]); } /** * Returns the {@link IFolder} representing the output for the project. * <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 getOutputFolder(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(); IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); IResource outputResource = wsRoot.findMember(path); if (outputResource != null && outputResource.getType() == IResource.FOLDER) { return (IFolder)outputResource; } } } catch (JavaModelException e) { // Let's do nothing and return null } catch (CoreException e) { // Let's do nothing and return null } return null; } }