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