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