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