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