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 { 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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