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