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