Home | History | Annotate | Download | only in project
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.project;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.NonNull;
     21 import com.android.annotations.Nullable;
     22 import com.android.ide.eclipse.adt.AdtConstants;
     23 import com.android.ide.eclipse.adt.AdtPlugin;
     24 import com.google.common.collect.Lists;
     25 
     26 import org.eclipse.core.resources.IFolder;
     27 import org.eclipse.core.resources.IMarker;
     28 import org.eclipse.core.resources.IProject;
     29 import org.eclipse.core.resources.IResource;
     30 import org.eclipse.core.resources.IWorkspaceRoot;
     31 import org.eclipse.core.resources.ResourcesPlugin;
     32 import org.eclipse.core.runtime.CoreException;
     33 import org.eclipse.core.runtime.IPath;
     34 import org.eclipse.core.runtime.NullProgressMonitor;
     35 import org.eclipse.jdt.core.Flags;
     36 import org.eclipse.jdt.core.IClasspathEntry;
     37 import org.eclipse.jdt.core.IJavaModel;
     38 import org.eclipse.jdt.core.IJavaProject;
     39 import org.eclipse.jdt.core.IMethod;
     40 import org.eclipse.jdt.core.IType;
     41 import org.eclipse.jdt.core.ITypeHierarchy;
     42 import org.eclipse.jdt.core.JavaCore;
     43 import org.eclipse.jdt.core.JavaModelException;
     44 import org.eclipse.jdt.ui.JavaUI;
     45 import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
     46 import org.eclipse.jface.text.BadLocationException;
     47 import org.eclipse.jface.text.IDocument;
     48 import org.eclipse.jface.text.IRegion;
     49 import org.eclipse.ui.IEditorInput;
     50 import org.eclipse.ui.IEditorPart;
     51 import org.eclipse.ui.IWorkbench;
     52 import org.eclipse.ui.IWorkbenchPage;
     53 import org.eclipse.ui.IWorkbenchWindow;
     54 import org.eclipse.ui.PartInitException;
     55 import org.eclipse.ui.PlatformUI;
     56 import org.eclipse.ui.texteditor.IDocumentProvider;
     57 import org.eclipse.ui.texteditor.ITextEditor;
     58 
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 
     62 /**
     63  * Utility methods to manipulate projects.
     64  */
     65 public final class BaseProjectHelper {
     66 
     67     public static final String TEST_CLASS_OK = null;
     68 
     69     /**
     70      * Project filter to be used with {@link BaseProjectHelper#getAndroidProjects(IProjectFilter)}.
     71      */
     72     public static interface IProjectFilter {
     73         boolean accept(IProject project);
     74     }
     75 
     76     /**
     77      * returns a list of source classpath for a specified project
     78      * @param javaProject
     79      * @return a list of path relative to the workspace root.
     80      */
     81     @NonNull
     82     public static List<IPath> getSourceClasspaths(IJavaProject javaProject) {
     83         List<IPath> sourceList = Lists.newArrayList();
     84         IClasspathEntry[] classpaths = javaProject.readRawClasspath();
     85         if (classpaths != null) {
     86             for (IClasspathEntry e : classpaths) {
     87                 if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
     88                     sourceList.add(e.getPath());
     89                 }
     90             }
     91         }
     92 
     93         return sourceList;
     94     }
     95 
     96     /**
     97      * returns a list of source classpath for a specified project
     98      * @param project
     99      * @return a list of path relative to the workspace root.
    100      */
    101     public static List<IPath> getSourceClasspaths(IProject project) {
    102         IJavaProject javaProject = JavaCore.create(project);
    103         return getSourceClasspaths(javaProject);
    104     }
    105 
    106     /**
    107      * Adds a marker to a file on a specific line. This methods catches thrown
    108      * {@link CoreException}, and returns null instead.
    109      * @param resource the resource to be marked
    110      * @param markerId The id of the marker to add.
    111      * @param message the message associated with the mark
    112      * @param lineNumber the line number where to put the mark. If line is < 1, it puts the marker
    113      * on line 1,
    114      * @param severity the severity of the marker.
    115      * @return the IMarker that was added or null if it failed to add one.
    116      */
    117     public final static IMarker markResource(IResource resource, String markerId,
    118             String message, int lineNumber, int severity) {
    119         return markResource(resource, markerId, message, lineNumber, -1, -1, severity);
    120     }
    121 
    122     /**
    123      * Adds a marker to a file on a specific line, for a specific range of text. This
    124      * methods catches thrown {@link CoreException}, and returns null instead.
    125      *
    126      * @param resource the resource to be marked
    127      * @param markerId The id of the marker to add.
    128      * @param message the message associated with the mark
    129      * @param lineNumber the line number where to put the mark. If line is < 1, it puts
    130      *            the marker on line 1,
    131      * @param startOffset the beginning offset of the marker (relative to the beginning of
    132      *            the document, not the line), or -1 for no range
    133      * @param endOffset the ending offset of the marker
    134      * @param severity the severity of the marker.
    135      * @return the IMarker that was added or null if it failed to add one.
    136      */
    137     @Nullable
    138     public final static IMarker markResource(IResource resource, String markerId,
    139                 String message, int lineNumber, int startOffset, int endOffset, int severity) {
    140         if (!resource.isAccessible()) {
    141             return null;
    142         }
    143 
    144         try {
    145             IMarker marker = resource.createMarker(markerId);
    146             marker.setAttribute(IMarker.MESSAGE, message);
    147             marker.setAttribute(IMarker.SEVERITY, severity);
    148 
    149             // if marker is text type, enforce a line number so that it shows in the editor
    150             // somewhere (line 1)
    151             if (lineNumber < 1 && marker.isSubtypeOf(IMarker.TEXT)) {
    152                 lineNumber = 1;
    153             }
    154 
    155             if (lineNumber >= 1) {
    156                 marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
    157             }
    158 
    159             if (startOffset != -1) {
    160                 marker.setAttribute(IMarker.CHAR_START, startOffset);
    161                 marker.setAttribute(IMarker.CHAR_END, endOffset);
    162             }
    163 
    164             // on Windows, when adding a marker to a project, it takes a refresh for the marker
    165             // to show. In order to fix this we're forcing a refresh of elements receiving
    166             // markers (and only the element, not its children), to force the marker display.
    167             resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
    168 
    169             return marker;
    170         } catch (CoreException e) {
    171             AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", //$NON-NLS-1$
    172                     markerId, resource.getFullPath());
    173         }
    174 
    175         return null;
    176     }
    177 
    178     /**
    179      * Adds a marker to a resource. This methods catches thrown {@link CoreException},
    180      * and returns null instead.
    181      * @param resource the file to be marked
    182      * @param markerId The id of the marker to add.
    183      * @param message the message associated with the mark
    184      * @param severity the severity of the marker.
    185      * @return the IMarker that was added or null if it failed to add one.
    186      */
    187     @Nullable
    188     public final static IMarker markResource(IResource resource, String markerId,
    189             String message, int severity) {
    190         return markResource(resource, markerId, message, -1, severity);
    191     }
    192 
    193     /**
    194      * Adds a marker to an {@link IProject}. This method does not catch {@link CoreException}, like
    195      * {@link #markResource(IResource, String, String, int)}.
    196      *
    197      * @param project the project to be marked
    198      * @param markerId The id of the marker to add.
    199      * @param message the message associated with the mark
    200      * @param severity the severity of the marker.
    201      * @param priority the priority of the marker
    202      * @return the IMarker that was added.
    203      * @throws CoreException if the marker cannot be added
    204      */
    205     @Nullable
    206     public final static IMarker markProject(IProject project, String markerId,
    207             String message, int severity, int priority) throws CoreException {
    208         if (!project.isAccessible()) {
    209             return null;
    210         }
    211 
    212         IMarker marker = project.createMarker(markerId);
    213         marker.setAttribute(IMarker.MESSAGE, message);
    214         marker.setAttribute(IMarker.SEVERITY, severity);
    215         marker.setAttribute(IMarker.PRIORITY, priority);
    216 
    217         // on Windows, when adding a marker to a project, it takes a refresh for the marker
    218         // to show. In order to fix this we're forcing a refresh of elements receiving
    219         // markers (and only the element, not its children), to force the marker display.
    220         project.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
    221 
    222         return marker;
    223     }
    224 
    225     /**
    226      * Tests that a class name is valid for usage in the manifest.
    227      * <p/>
    228      * This tests the class existence, that it can be instantiated (ie it must not be abstract,
    229      * nor non static if enclosed), and that it extends the proper super class (not necessarily
    230      * directly)
    231      * @param javaProject the {@link IJavaProject} containing the class.
    232      * @param className the fully qualified name of the class to test.
    233      * @param superClassName the fully qualified name of the expected super class.
    234      * @param testVisibility if <code>true</code>, the method will check the visibility of the class
    235      * or of its constructors.
    236      * @return {@link #TEST_CLASS_OK} or an error message.
    237      */
    238     public final static String testClassForManifest(IJavaProject javaProject, String className,
    239             String superClassName, boolean testVisibility) {
    240         try {
    241             // replace $ by .
    242             String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
    243 
    244             // look for the IType object for this class
    245             IType type = javaProject.findType(javaClassName);
    246             if (type != null && type.exists()) {
    247                 // test that the class is not abstract
    248                 int flags = type.getFlags();
    249                 if (Flags.isAbstract(flags)) {
    250                     return String.format("%1$s is abstract", className);
    251                 }
    252 
    253                 // test whether the class is public or not.
    254                 if (testVisibility && Flags.isPublic(flags) == false) {
    255                     // if its not public, it may have a public default constructor,
    256                     // which would then be fine.
    257                     IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]);
    258                     if (basicConstructor != null && basicConstructor.exists()) {
    259                         int constructFlags = basicConstructor.getFlags();
    260                         if (Flags.isPublic(constructFlags) == false) {
    261                             return String.format(
    262                                     "%1$s or its default constructor must be public for the system to be able to instantiate it",
    263                                     className);
    264                         }
    265                     } else {
    266                         return String.format(
    267                                 "%1$s must be public, or the system will not be able to instantiate it.",
    268                                 className);
    269                     }
    270                 }
    271 
    272                 // If it's enclosed, test that it's static. If its declaring class is enclosed
    273                 // as well, test that it is also static, and public.
    274                 IType declaringType = type;
    275                 do {
    276                     IType tmpType = declaringType.getDeclaringType();
    277                     if (tmpType != null) {
    278                         if (tmpType.exists()) {
    279                             flags = declaringType.getFlags();
    280                             if (Flags.isStatic(flags) == false) {
    281                                 return String.format("%1$s is enclosed, but not static",
    282                                         declaringType.getFullyQualifiedName());
    283                             }
    284 
    285                             flags = tmpType.getFlags();
    286                             if (testVisibility && Flags.isPublic(flags) == false) {
    287                                 return String.format("%1$s is not public",
    288                                         tmpType.getFullyQualifiedName());
    289                             }
    290                         } else {
    291                             // if it doesn't exist, we need to exit so we may as well mark it null.
    292                             tmpType = null;
    293                         }
    294                     }
    295                     declaringType = tmpType;
    296                 } while (declaringType != null);
    297 
    298                 // test the class inherit from the specified super class.
    299                 // get the type hierarchy
    300                 ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
    301 
    302                 // if the super class is not the reference class, it may inherit from
    303                 // it so we get its supertype. At some point it will be null and we
    304                 // will stop
    305                 IType superType = type;
    306                 boolean foundProperSuperClass = false;
    307                 while ((superType = hierarchy.getSuperclass(superType)) != null &&
    308                         superType.exists()) {
    309                     if (superClassName.equals(superType.getFullyQualifiedName())) {
    310                         foundProperSuperClass = true;
    311                     }
    312                 }
    313 
    314                 // didn't find the proper superclass? return false.
    315                 if (foundProperSuperClass == false) {
    316                     return String.format("%1$s does not extend %2$s", className, superClassName);
    317                 }
    318 
    319                 return TEST_CLASS_OK;
    320             } else {
    321                 return String.format("Class %1$s does not exist", className);
    322             }
    323         } catch (JavaModelException e) {
    324             return String.format("%1$s: %2$s", className, e.getMessage());
    325         }
    326     }
    327 
    328     /**
    329      * Returns the {@link IJavaProject} for a {@link IProject} object.
    330      * <p/>
    331      * This checks if the project has the Java Nature first.
    332      * @param project
    333      * @return the IJavaProject or null if the project couldn't be created or if the project
    334      * does not have the Java Nature.
    335      * @throws CoreException if this method fails. Reasons include:
    336      * <ul><li>This project does not exist.</li><li>This project is not open.</li></ul>
    337      */
    338     public static IJavaProject getJavaProject(IProject project) throws CoreException {
    339         if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
    340             return JavaCore.create(project);
    341         }
    342         return null;
    343     }
    344 
    345     /**
    346      * Reveals a specific line in the source file defining a specified class,
    347      * for a specific project.
    348      * @param project
    349      * @param className
    350      * @param line
    351      * @return true if the source was revealed
    352      */
    353     public static boolean revealSource(IProject project, String className, int line) {
    354         // Inner classes are pointless: All we need is the enclosing type to find the file, and the
    355         // line number.
    356         // Since the anonymous ones will cause IJavaProject#findType to fail, we remove
    357         // all of them.
    358         int pos = className.indexOf('$');
    359         if (pos != -1) {
    360             className = className.substring(0, pos);
    361         }
    362 
    363         // get the java project
    364         IJavaProject javaProject = JavaCore.create(project);
    365 
    366         try {
    367             // look for the IType matching the class name.
    368             IType result = javaProject.findType(className);
    369             if (result != null && result.exists()) {
    370                 // before we show the type in an editor window, we make sure the current
    371                 // workbench page has an editor area (typically the ddms perspective doesn't).
    372                 IWorkbench workbench = PlatformUI.getWorkbench();
    373                 IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
    374                 IWorkbenchPage page = window.getActivePage();
    375                 if (page.isEditorAreaVisible() == false) {
    376                     // no editor area? we open the java perspective.
    377                     new OpenJavaPerspectiveAction().run();
    378                 }
    379 
    380                 IEditorPart editor = JavaUI.openInEditor(result);
    381                 if (editor instanceof ITextEditor) {
    382                     // get the text editor that was just opened.
    383                     ITextEditor textEditor = (ITextEditor)editor;
    384 
    385                     IEditorInput input = textEditor.getEditorInput();
    386 
    387                     // get the location of the line to show.
    388                     IDocumentProvider documentProvider = textEditor.getDocumentProvider();
    389                     IDocument document = documentProvider.getDocument(input);
    390                     IRegion lineInfo = document.getLineInformation(line - 1);
    391 
    392                     // select and reveal the line.
    393                     textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
    394                 }
    395 
    396                 return true;
    397             }
    398         } catch (JavaModelException e) {
    399         } catch (PartInitException e) {
    400         } catch (BadLocationException e) {
    401         }
    402 
    403         return false;
    404     }
    405 
    406     /**
    407      * Returns the list of android-flagged projects. This list contains projects that are opened
    408      * in the workspace and that are flagged as android project (through the android nature)
    409      * @param filter an optional filter to control which android project are returned. Can be null.
    410      * @return an array of IJavaProject, which can be empty if no projects match.
    411      */
    412     public static @NonNull IJavaProject[] getAndroidProjects(@Nullable IProjectFilter filter) {
    413         IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
    414         IJavaModel javaModel = JavaCore.create(workspaceRoot);
    415 
    416         return getAndroidProjects(javaModel, filter);
    417     }
    418 
    419     /**
    420      * Returns the list of android-flagged projects for the specified java Model.
    421      * This list contains projects that are opened in the workspace and that are flagged as android
    422      * project (through the android nature)
    423      * @param javaModel the Java Model object corresponding for the current workspace root.
    424      * @param filter an optional filter to control which android project are returned. Can be null.
    425      * @return an array of IJavaProject, which can be empty if no projects match.
    426      */
    427     @NonNull
    428     public static IJavaProject[] getAndroidProjects(@NonNull IJavaModel javaModel,
    429             @Nullable IProjectFilter filter) {
    430         // get the java projects
    431         IJavaProject[] javaProjectList = null;
    432         try {
    433             javaProjectList  = javaModel.getJavaProjects();
    434         }
    435         catch (JavaModelException jme) {
    436             return new IJavaProject[0];
    437         }
    438 
    439         // temp list to build the android project array
    440         ArrayList<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
    441 
    442         // loop through the projects and add the android flagged projects to the temp list.
    443         for (IJavaProject javaProject : javaProjectList) {
    444             // get the workspace project object
    445             IProject project = javaProject.getProject();
    446 
    447             // check if it's an android project based on its nature
    448             if (isAndroidProject(project)) {
    449                 if (filter == null || filter.accept(project)) {
    450                     androidProjectList.add(javaProject);
    451                 }
    452             }
    453         }
    454 
    455         // return the android projects list.
    456         return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]);
    457     }
    458 
    459     /**
    460      * Returns true if the given project is an Android project (e.g. is a Java project
    461      * that also has the Android nature)
    462      *
    463      * @param project the project to test
    464      * @return true if the given project is an Android project
    465      */
    466     public static boolean isAndroidProject(IProject project) {
    467         // check if it's an android project based on its nature
    468         try {
    469             return project.hasNature(AdtConstants.NATURE_DEFAULT);
    470         } catch (CoreException e) {
    471             // this exception, thrown by IProject.hasNature(), means the project either doesn't
    472             // exist or isn't opened. So, in any case we just skip it (the exception will
    473             // bypass the ArrayList.add()
    474         }
    475 
    476         return false;
    477     }
    478 
    479     /**
    480      * Returns the {@link IFolder} representing the output for the project for Android specific
    481      * files.
    482      * <p>
    483      * The project must be a java project and be opened, or the method will return null.
    484      * @param project the {@link IProject}
    485      * @return an IFolder item or null.
    486      */
    487     public final static IFolder getJavaOutputFolder(IProject project) {
    488         try {
    489             if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
    490                 // get a java project from the normal project object
    491                 IJavaProject javaProject = JavaCore.create(project);
    492 
    493                 IPath path = javaProject.getOutputLocation();
    494                 path = path.removeFirstSegments(1);
    495                 return project.getFolder(path);
    496             }
    497         } catch (JavaModelException e) {
    498             // Let's do nothing and return null
    499         } catch (CoreException e) {
    500             // Let's do nothing and return null
    501         }
    502         return null;
    503     }
    504 
    505     /**
    506      * Returns the {@link IFolder} representing the output for the project for compiled Java
    507      * files.
    508      * <p>
    509      * The project must be a java project and be opened, or the method will return null.
    510      * @param project the {@link IProject}
    511      * @return an IFolder item or null.
    512      */
    513     @Nullable
    514     public final static IFolder getAndroidOutputFolder(IProject project) {
    515         try {
    516             if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
    517                 return project.getFolder(SdkConstants.FD_OUTPUT);
    518             }
    519         } catch (JavaModelException e) {
    520             // Let's do nothing and return null
    521         } catch (CoreException e) {
    522             // Let's do nothing and return null
    523         }
    524         return null;
    525     }
    526 
    527 }
    528