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