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