• 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.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 {
accept(IProject project)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      */
getSourceClasspaths(IJavaProject javaProject)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      */
getSourceClasspaths(IProject project)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      */
markResource(IResource resource, String markerId, String message, int lineNumber, int severity)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
markResource(IResource resource, String markerId, String message, int lineNumber, int startOffset, int endOffset, int severity)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
markResource(IResource resource, String markerId, String message, int severity)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
markProject(IProject project, String markerId, String message, int severity, int priority)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      */
testClassForManifest(IJavaProject javaProject, String className, String superClassName, boolean testVisibility)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      */
getJavaProject(IProject project)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      */
revealSource(IProject project, String className, int line)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      */
getAndroidProjects(@ullable IProjectFilter filter)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
getAndroidProjects(@onNull IJavaModel javaModel, @Nullable IProjectFilter filter)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      */
isAndroidProject(IProject project)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      */
getJavaOutputFolder(IProject project)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      */
getAndroidOutputFolder(IProject project)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