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.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; 22 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 23 import com.android.sdklib.SdkConstants; 24 import com.android.sdklib.xml.ManifestData; 25 import com.android.util.Pair; 26 27 import org.eclipse.core.resources.IFile; 28 import org.eclipse.core.resources.IFolder; 29 import org.eclipse.core.resources.IMarker; 30 import org.eclipse.core.resources.IProject; 31 import org.eclipse.core.resources.IProjectDescription; 32 import org.eclipse.core.resources.IResource; 33 import org.eclipse.core.resources.IWorkspace; 34 import org.eclipse.core.resources.IncrementalProjectBuilder; 35 import org.eclipse.core.resources.ResourcesPlugin; 36 import org.eclipse.core.runtime.CoreException; 37 import org.eclipse.core.runtime.IPath; 38 import org.eclipse.core.runtime.IProgressMonitor; 39 import org.eclipse.core.runtime.NullProgressMonitor; 40 import org.eclipse.core.runtime.Path; 41 import org.eclipse.core.runtime.QualifiedName; 42 import org.eclipse.jdt.core.IClasspathEntry; 43 import org.eclipse.jdt.core.IJavaModel; 44 import org.eclipse.jdt.core.IJavaProject; 45 import org.eclipse.jdt.core.JavaCore; 46 import org.eclipse.jdt.core.JavaModelException; 47 import org.eclipse.jdt.launching.JavaRuntime; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.TreeMap; 53 54 /** 55 * Utility class to manipulate Project parameters/properties. 56 */ 57 public final class ProjectHelper { 58 public final static int COMPILER_COMPLIANCE_OK = 0; 59 public final static int COMPILER_COMPLIANCE_LEVEL = 1; 60 public final static int COMPILER_COMPLIANCE_SOURCE = 2; 61 public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3; 62 63 /** 64 * Adds the corresponding source folder to the class path entries. 65 * This method does not check whether the entry is already defined in the project. 66 * 67 * @param entries The class path entries to read. A copy will be returned. 68 * @param newEntry The new class path entry to add. 69 * @return A new class path entries array. 70 */ 71 public static IClasspathEntry[] addEntryToClasspath( 72 IClasspathEntry[] entries, IClasspathEntry newEntry) { 73 int n = entries.length; 74 IClasspathEntry[] newEntries = new IClasspathEntry[n + 1]; 75 System.arraycopy(entries, 0, newEntries, 0, n); 76 newEntries[n] = newEntry; 77 return newEntries; 78 } 79 80 /** 81 * Adds the corresponding source folder to the project's class path entries. 82 * This method does not check whether the entry is already defined in the project. 83 * 84 * @param javaProject The java project of which path entries to update. 85 * @param newEntry The new class path entry to add. 86 * @throws JavaModelException 87 */ 88 public static void addEntryToClasspath(IJavaProject javaProject, IClasspathEntry newEntry) 89 throws JavaModelException { 90 91 IClasspathEntry[] entries = javaProject.getRawClasspath(); 92 entries = addEntryToClasspath(entries, newEntry); 93 javaProject.setRawClasspath(entries, new NullProgressMonitor()); 94 } 95 96 /** 97 * Checks whether the given class path entry is already defined in the project. 98 * 99 * @param javaProject The java project of which path entries to check. 100 * @param newEntry The parent source folder to remove. 101 * @return True if the class path entry is already defined. 102 * @throws JavaModelException 103 */ 104 public static boolean isEntryInClasspath(IJavaProject javaProject, IClasspathEntry newEntry) 105 throws JavaModelException { 106 107 IClasspathEntry[] entries = javaProject.getRawClasspath(); 108 for (IClasspathEntry entry : entries) { 109 if (entry.equals(newEntry)) { 110 return true; 111 } 112 } 113 return false; 114 } 115 116 /** 117 * Remove a classpath entry from the array. 118 * @param entries The class path entries to read. A copy will be returned 119 * @param index The index to remove. 120 * @return A new class path entries array. 121 */ 122 public static IClasspathEntry[] removeEntryFromClasspath( 123 IClasspathEntry[] entries, int index) { 124 int n = entries.length; 125 IClasspathEntry[] newEntries = new IClasspathEntry[n-1]; 126 127 // copy the entries before index 128 System.arraycopy(entries, 0, newEntries, 0, index); 129 130 // copy the entries after index 131 System.arraycopy(entries, index + 1, newEntries, index, 132 entries.length - index - 1); 133 134 return newEntries; 135 } 136 137 /** 138 * Converts a OS specific path into a path valid for the java doc location 139 * attributes of a project. 140 * @param javaDocOSLocation The OS specific path. 141 * @return a valid path for the java doc location. 142 */ 143 public static String getJavaDocPath(String javaDocOSLocation) { 144 // first thing we do is convert the \ into / 145 String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$ 146 AdtConstants.WS_SEP); 147 148 // then we add file: at the beginning for unix path, and file:/ for non 149 // unix path 150 if (javaDoc.startsWith(AdtConstants.WS_SEP)) { 151 return "file:" + javaDoc; //$NON-NLS-1$ 152 } 153 154 return "file:/" + javaDoc; //$NON-NLS-1$ 155 } 156 157 /** 158 * Look for a specific classpath entry by full path and return its index. 159 * @param entries The entry array to search in. 160 * @param entryPath The OS specific path of the entry. 161 * @param entryKind The kind of the entry. Accepted values are 0 162 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, 163 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, 164 * and IClasspathEntry.CPE_CONTAINER 165 * @return the index of the found classpath entry or -1. 166 */ 167 public static int findClasspathEntryByPath(IClasspathEntry[] entries, 168 String entryPath, int entryKind) { 169 for (int i = 0 ; i < entries.length ; i++) { 170 IClasspathEntry entry = entries[i]; 171 172 int kind = entry.getEntryKind(); 173 174 if (kind == entryKind || entryKind == 0) { 175 // get the path 176 IPath path = entry.getPath(); 177 178 String osPathString = path.toOSString(); 179 if (osPathString.equals(entryPath)) { 180 return i; 181 } 182 } 183 } 184 185 // not found, return bad index. 186 return -1; 187 } 188 189 /** 190 * Look for a specific classpath entry for file name only and return its 191 * index. 192 * @param entries The entry array to search in. 193 * @param entryName The filename of the entry. 194 * @param entryKind The kind of the entry. Accepted values are 0 195 * (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT, 196 * IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE, 197 * and IClasspathEntry.CPE_CONTAINER 198 * @param startIndex Index where to start the search 199 * @return the index of the found classpath entry or -1. 200 */ 201 public static int findClasspathEntryByName(IClasspathEntry[] entries, 202 String entryName, int entryKind, int startIndex) { 203 if (startIndex < 0) { 204 startIndex = 0; 205 } 206 for (int i = startIndex ; i < entries.length ; i++) { 207 IClasspathEntry entry = entries[i]; 208 209 int kind = entry.getEntryKind(); 210 211 if (kind == entryKind || entryKind == 0) { 212 // get the path 213 IPath path = entry.getPath(); 214 String name = path.segment(path.segmentCount()-1); 215 216 if (name.equals(entryName)) { 217 return i; 218 } 219 } 220 } 221 222 // not found, return bad index. 223 return -1; 224 } 225 226 /** 227 * Fix the project. This checks the SDK location. 228 * @param project The project to fix. 229 * @throws JavaModelException 230 */ 231 public static void fixProject(IProject project) throws JavaModelException { 232 if (AdtPlugin.getOsSdkFolder().length() == 0) { 233 AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed."); 234 return; 235 } 236 237 // get a java project 238 IJavaProject javaProject = JavaCore.create(project); 239 fixProjectClasspathEntries(javaProject); 240 } 241 242 /** 243 * Fix the project classpath entries. The method ensures that: 244 * <ul> 245 * <li>The project does not reference any old android.zip/android.jar archive.</li> 246 * <li>The project does not use its output folder as a sourc folder.</li> 247 * <li>The project does not reference a desktop JRE</li> 248 * <li>The project references the AndroidClasspathContainer. 249 * </ul> 250 * @param javaProject The project to fix. 251 * @throws JavaModelException 252 */ 253 public static void fixProjectClasspathEntries(IJavaProject javaProject) 254 throws JavaModelException { 255 256 // get the project classpath 257 IClasspathEntry[] entries = javaProject.getRawClasspath(); 258 IClasspathEntry[] oldEntries = entries; 259 260 // check if the JRE is set as library 261 int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER, 262 IClasspathEntry.CPE_CONTAINER); 263 if (jreIndex != -1) { 264 // the project has a JRE included, we remove it 265 entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex); 266 } 267 268 // get the output folder 269 IPath outputFolder = javaProject.getOutputLocation(); 270 271 boolean foundFrameworkContainer = false; 272 boolean foundLibrariesContainer = false; 273 274 for (int i = 0 ; i < entries.length ;) { 275 // get the entry and kind 276 IClasspathEntry entry = entries[i]; 277 int kind = entry.getEntryKind(); 278 279 if (kind == IClasspathEntry.CPE_SOURCE) { 280 IPath path = entry.getPath(); 281 282 if (path.equals(outputFolder)) { 283 entries = ProjectHelper.removeEntryFromClasspath(entries, i); 284 285 // continue, to skip the i++; 286 continue; 287 } 288 } else if (kind == IClasspathEntry.CPE_CONTAINER) { 289 String path = entry.getPath().toString(); 290 if (AdtConstants.CONTAINER_FRAMEWORK.equals(path)) { 291 foundFrameworkContainer = true; 292 } 293 if (AdtConstants.CONTAINER_LIBRARIES.equals(path)) { 294 foundLibrariesContainer = true; 295 } 296 } 297 298 i++; 299 } 300 301 // if the framework container is not there, we add it 302 if (foundFrameworkContainer == false) { 303 // add the android container to the array 304 entries = ProjectHelper.addEntryToClasspath(entries, 305 JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_FRAMEWORK))); 306 } 307 308 // same thing for the library container 309 if (foundLibrariesContainer == false) { 310 // add the android container to the array 311 entries = ProjectHelper.addEntryToClasspath(entries, 312 JavaCore.newContainerEntry(new Path(AdtConstants.CONTAINER_LIBRARIES))); 313 } 314 315 // set the new list of entries to the project 316 if (entries != oldEntries) { 317 javaProject.setRawClasspath(entries, new NullProgressMonitor()); 318 } 319 320 // If needed, check and fix compiler compliance and source compatibility 321 ProjectHelper.checkAndFixCompilerCompliance(javaProject); 322 } 323 324 325 /** 326 * Checks the project compiler compliance level is supported. 327 * @param javaProject The project to check 328 * @return A pair with the first integer being an error code, and the second value 329 * being the invalid value found or null. The error code can be: <ul> 330 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> 331 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> 332 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> 333 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> 334 * </ul> 335 */ 336 public static final Pair<Integer, String> checkCompilerCompliance(IJavaProject javaProject) { 337 // get the project compliance level option 338 String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); 339 340 // check it against a list of valid compliance level strings. 341 if (checkCompliance(compliance) == false) { 342 // if we didn't find the proper compliance level, we return an error 343 return Pair.of(COMPILER_COMPLIANCE_LEVEL, compliance); 344 } 345 346 // otherwise we check source compatibility 347 String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true); 348 349 // check it against a list of valid compliance level strings. 350 if (checkCompliance(source) == false) { 351 // if we didn't find the proper compliance level, we return an error 352 return Pair.of(COMPILER_COMPLIANCE_SOURCE, source); 353 } 354 355 // otherwise check codegen level 356 String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true); 357 358 // check it against a list of valid compliance level strings. 359 if (checkCompliance(codeGen) == false) { 360 // if we didn't find the proper compliance level, we return an error 361 return Pair.of(COMPILER_COMPLIANCE_CODEGEN_TARGET, codeGen); 362 } 363 364 return Pair.of(COMPILER_COMPLIANCE_OK, null); 365 } 366 367 /** 368 * Checks the project compiler compliance level is supported. 369 * @param project The project to check 370 * @return A pair with the first integer being an error code, and the second value 371 * being the invalid value found or null. The error code can be: <ul> 372 * <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li> 373 * <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li> 374 * <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li> 375 * <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li> 376 * </ul> 377 */ 378 public static final Pair<Integer, String> checkCompilerCompliance(IProject project) { 379 // get the java project from the IProject resource object 380 IJavaProject javaProject = JavaCore.create(project); 381 382 // check and return the result. 383 return checkCompilerCompliance(javaProject); 384 } 385 386 387 /** 388 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility 389 * level 390 * @param project The project to check and fix. 391 */ 392 public static final void checkAndFixCompilerCompliance(IProject project) { 393 // FIXME This method is never used. Shall we just removed it? 394 // {@link #checkAndFixCompilerCompliance(IJavaProject)} is used instead. 395 396 // get the java project from the IProject resource object 397 IJavaProject javaProject = JavaCore.create(project); 398 399 // Now we check the compiler compliance level and make sure it is valid 400 checkAndFixCompilerCompliance(javaProject); 401 } 402 403 /** 404 * Checks, and fixes if needed, the compiler compliance level, and the source compatibility 405 * level 406 * @param javaProject The Java project to check and fix. 407 */ 408 public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) { 409 Pair<Integer, String> result = checkCompilerCompliance(javaProject); 410 if (result.getFirst().intValue() != COMPILER_COMPLIANCE_OK) { 411 // setup the preferred compiler compliance level. 412 javaProject.setOption(JavaCore.COMPILER_COMPLIANCE, 413 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 414 javaProject.setOption(JavaCore.COMPILER_SOURCE, 415 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 416 javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, 417 AdtConstants.COMPILER_COMPLIANCE_PREFERRED); 418 419 // clean the project to make sure we recompile 420 try { 421 javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD, 422 new NullProgressMonitor()); 423 } catch (CoreException e) { 424 AdtPlugin.printErrorToConsole(javaProject.getProject(), 425 "Project compiler settings changed. Clean your project."); 426 } 427 } 428 } 429 430 /** 431 * Returns a {@link IProject} by its running application name, as it returned by the AVD. 432 * <p/> 433 * <var>applicationName</var> will in most case be the package declared in the manifest, but 434 * can, in some cases, be a custom process name declared in the manifest, in the 435 * <code>application</code>, <code>activity</code>, <code>receiver</code>, or 436 * <code>service</code> nodes. 437 * @param applicationName The application name. 438 * @return a project or <code>null</code> if no matching project were found. 439 */ 440 public static IProject findAndroidProjectByAppName(String applicationName) { 441 // Get the list of project for the current workspace 442 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 443 IProject[] projects = workspace.getRoot().getProjects(); 444 445 // look for a project that matches the packageName of the app 446 // we're trying to debug 447 for (IProject p : projects) { 448 if (p.isOpen()) { 449 try { 450 if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 451 // ignore non android projects 452 continue; 453 } 454 } catch (CoreException e) { 455 // failed to get the nature? skip project. 456 continue; 457 } 458 459 // check that there is indeed a manifest file. 460 IFile manifestFile = getManifest(p); 461 if (manifestFile == null) { 462 // no file? skip this project. 463 continue; 464 } 465 466 ManifestData data = AndroidManifestHelper.parseForData(manifestFile); 467 if (data == null) { 468 // skip this project. 469 continue; 470 } 471 472 String manifestPackage = data.getPackage(); 473 474 if (manifestPackage != null && manifestPackage.equals(applicationName)) { 475 // this is the project we were looking for! 476 return p; 477 } else { 478 // if the package and application name don't match, 479 // we look for other possible process names declared in the manifest. 480 String[] processes = data.getProcesses(); 481 for (String process : processes) { 482 if (process.equals(applicationName)) { 483 return p; 484 } 485 } 486 } 487 } 488 } 489 490 return null; 491 492 } 493 494 public static void fixProjectNatureOrder(IProject project) throws CoreException { 495 IProjectDescription description = project.getDescription(); 496 String[] natures = description.getNatureIds(); 497 498 // if the android nature is not the first one, we reorder them 499 if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) { 500 // look for the index 501 for (int i = 0 ; i < natures.length ; i++) { 502 if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) { 503 // if we try to just reorder the array in one pass, this doesn't do 504 // anything. I guess JDT check that we are actually adding/removing nature. 505 // So, first we'll remove the android nature, and then add it back. 506 507 // remove the android nature 508 removeNature(project, AdtConstants.NATURE_DEFAULT); 509 510 // now add it back at the first index. 511 description = project.getDescription(); 512 natures = description.getNatureIds(); 513 514 String[] newNatures = new String[natures.length + 1]; 515 516 // first one is android 517 newNatures[0] = AdtConstants.NATURE_DEFAULT; 518 519 // next the rest that was before the android nature 520 System.arraycopy(natures, 0, newNatures, 1, natures.length); 521 522 // set the new natures 523 description.setNatureIds(newNatures); 524 project.setDescription(description, null); 525 526 // and stop 527 break; 528 } 529 } 530 } 531 } 532 533 534 /** 535 * Removes a specific nature from a project. 536 * @param project The project to remove the nature from. 537 * @param nature The nature id to remove. 538 * @throws CoreException 539 */ 540 public static void removeNature(IProject project, String nature) throws CoreException { 541 IProjectDescription description = project.getDescription(); 542 String[] natures = description.getNatureIds(); 543 544 // check if the project already has the android nature. 545 for (int i = 0; i < natures.length; ++i) { 546 if (nature.equals(natures[i])) { 547 String[] newNatures = new String[natures.length - 1]; 548 if (i > 0) { 549 System.arraycopy(natures, 0, newNatures, 0, i); 550 } 551 System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1); 552 description.setNatureIds(newNatures); 553 project.setDescription(description, null); 554 555 return; 556 } 557 } 558 559 } 560 561 /** 562 * Returns if the project has error level markers. 563 * @param includeReferencedProjects flag to also test the referenced projects. 564 * @throws CoreException 565 */ 566 public static boolean hasError(IProject project, boolean includeReferencedProjects) 567 throws CoreException { 568 IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); 569 if (markers != null && markers.length > 0) { 570 // the project has marker(s). even though they are "problem" we 571 // don't know their severity. so we loop on them and figure if they 572 // are warnings or errors 573 for (IMarker m : markers) { 574 int s = m.getAttribute(IMarker.SEVERITY, -1); 575 if (s == IMarker.SEVERITY_ERROR) { 576 return true; 577 } 578 } 579 } 580 581 // test the referenced projects if needed. 582 if (includeReferencedProjects) { 583 List<IProject> projects = getReferencedProjects(project); 584 585 for (IProject p : projects) { 586 if (hasError(p, false)) { 587 return true; 588 } 589 } 590 } 591 592 return false; 593 } 594 595 /** 596 * Saves a String property into the persistent storage of a resource. 597 * @param resource The resource into which the string value is saved. 598 * @param propertyName the name of the property. The id of the plug-in is added to this string. 599 * @param value the value to save 600 * @return true if the save succeeded. 601 */ 602 public static boolean saveStringProperty(IResource resource, String propertyName, 603 String value) { 604 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); 605 606 try { 607 resource.setPersistentProperty(qname, value); 608 } catch (CoreException e) { 609 return false; 610 } 611 612 return true; 613 } 614 615 /** 616 * Loads a String property from the persistent storage of a resource. 617 * @param resource The resource from which the string value is loaded. 618 * @param propertyName the name of the property. The id of the plug-in is added to this string. 619 * @return the property value or null if it was not found. 620 */ 621 public static String loadStringProperty(IResource resource, String propertyName) { 622 QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName); 623 624 try { 625 String value = resource.getPersistentProperty(qname); 626 return value; 627 } catch (CoreException e) { 628 return null; 629 } 630 } 631 632 /** 633 * Saves a property into the persistent storage of a resource. 634 * @param resource The resource into which the boolean value is saved. 635 * @param propertyName the name of the property. The id of the plug-in is added to this string. 636 * @param value the value to save 637 * @return true if the save succeeded. 638 */ 639 public static boolean saveBooleanProperty(IResource resource, String propertyName, 640 boolean value) { 641 return saveStringProperty(resource, propertyName, Boolean.toString(value)); 642 } 643 644 /** 645 * Loads a boolean property from the persistent storage of a resource. 646 * @param resource The resource from which the boolean value is loaded. 647 * @param propertyName the name of the property. The id of the plug-in is added to this string. 648 * @param defaultValue The default value to return if the property was not found. 649 * @return the property value or the default value if the property was not found. 650 */ 651 public static boolean loadBooleanProperty(IResource resource, String propertyName, 652 boolean defaultValue) { 653 String value = loadStringProperty(resource, propertyName); 654 if (value != null) { 655 return Boolean.parseBoolean(value); 656 } 657 658 return defaultValue; 659 } 660 661 /** 662 * Saves the path of a resource into the persistent storage of a resource. 663 * @param resource The resource into which the resource path is saved. 664 * @param propertyName the name of the property. The id of the plug-in is added to this string. 665 * @param value The resource to save. It's its path that is actually stored. If null, an 666 * empty string is stored. 667 * @return true if the save succeeded 668 */ 669 public static boolean saveResourceProperty(IResource resource, String propertyName, 670 IResource value) { 671 if (value != null) { 672 IPath iPath = value.getFullPath(); 673 return saveStringProperty(resource, propertyName, iPath.toString()); 674 } 675 676 return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$ 677 } 678 679 /** 680 * Loads the path of a resource from the persistent storage of a resource, and returns the 681 * corresponding IResource object. 682 * @param resource The resource from which the resource path is loaded. 683 * @param propertyName the name of the property. The id of the plug-in is added to this string. 684 * @return The corresponding IResource object (or children interface) or null 685 */ 686 public static IResource loadResourceProperty(IResource resource, String propertyName) { 687 String value = loadStringProperty(resource, propertyName); 688 689 if (value != null && value.length() > 0) { 690 return ResourcesPlugin.getWorkspace().getRoot().findMember(new Path(value)); 691 } 692 693 return null; 694 } 695 696 /** 697 * Returns the list of referenced project that are opened and Java projects. 698 * @param project 699 * @return a new list object containing the opened referenced java project. 700 * @throws CoreException 701 */ 702 public static List<IProject> getReferencedProjects(IProject project) throws CoreException { 703 IProject[] projects = project.getReferencedProjects(); 704 705 ArrayList<IProject> list = new ArrayList<IProject>(); 706 707 for (IProject p : projects) { 708 if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) { 709 list.add(p); 710 } 711 } 712 713 return list; 714 } 715 716 717 /** 718 * Checks a Java project compiler level option against a list of supported versions. 719 * @param optionValue the Compiler level option. 720 * @return true if the option value is supproted. 721 */ 722 private static boolean checkCompliance(String optionValue) { 723 for (String s : AdtConstants.COMPILER_COMPLIANCE) { 724 if (s != null && s.equals(optionValue)) { 725 return true; 726 } 727 } 728 729 return false; 730 } 731 732 /** 733 * Returns the apk filename for the given project 734 * @param project The project. 735 * @param config An optional config name. Can be null. 736 */ 737 public static String getApkFilename(IProject project, String config) { 738 if (config != null) { 739 return project.getName() + "-" + config + AdtConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$ 740 } 741 742 return project.getName() + AdtConstants.DOT_ANDROID_PACKAGE; 743 } 744 745 /** 746 * Find the list of projects on which this JavaProject is dependent on at the compilation level. 747 * 748 * @param javaProject Java project that we are looking for the dependencies. 749 * @return A list of Java projects for which javaProject depend on. 750 * @throws JavaModelException 751 */ 752 public static List<IJavaProject> getAndroidProjectDependencies(IJavaProject javaProject) 753 throws JavaModelException { 754 String[] requiredProjectNames = javaProject.getRequiredProjectNames(); 755 756 // Go from java project name to JavaProject name 757 IJavaModel javaModel = javaProject.getJavaModel(); 758 759 // loop through all dependent projects and keep only those that are Android projects 760 List<IJavaProject> projectList = new ArrayList<IJavaProject>(requiredProjectNames.length); 761 for (String javaProjectName : requiredProjectNames) { 762 IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName); 763 764 //Verify that the project has also the Android Nature 765 try { 766 if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { 767 continue; 768 } 769 } catch (CoreException e) { 770 continue; 771 } 772 773 projectList.add(androidJavaProject); 774 } 775 776 return projectList; 777 } 778 779 /** 780 * Returns the android package file as an IFile object for the specified 781 * project. 782 * @param project The project 783 * @return The android package as an IFile object or null if not found. 784 */ 785 public static IFile getApplicationPackage(IProject project) { 786 // get the output folder 787 IFolder outputLocation = BaseProjectHelper.getAndroidOutputFolder(project); 788 789 if (outputLocation == null) { 790 AdtPlugin.printErrorToConsole(project, 791 "Failed to get the output location of the project. Check build path properties" 792 ); 793 return null; 794 } 795 796 797 // get the package path 798 String packageName = project.getName() + AdtConstants.DOT_ANDROID_PACKAGE; 799 IResource r = outputLocation.findMember(packageName); 800 801 // check the package is present 802 if (r instanceof IFile && r.exists()) { 803 return (IFile)r; 804 } 805 806 String msg = String.format("Could not find %1$s!", packageName); 807 AdtPlugin.printErrorToConsole(project, msg); 808 809 return null; 810 } 811 812 /** 813 * Returns an {@link IFile} object representing the manifest for the given project. 814 * 815 * @param project The project containing the manifest file. 816 * @return An IFile object pointing to the manifest or null if the manifest 817 * is missing. 818 */ 819 public static IFile getManifest(IProject project) { 820 IResource r = project.findMember(AdtConstants.WS_SEP 821 + SdkConstants.FN_ANDROID_MANIFEST_XML); 822 823 if (r == null || r.exists() == false || (r instanceof IFile) == false) { 824 return null; 825 } 826 return (IFile) r; 827 } 828 829 /** 830 * Build project incrementally. If fullBuild is not set, then the packaging steps in 831 * the post compiler are skipped. (Though resource deltas are still processed). 832 * 833 * @param project The project to be built. 834 * @param monitor A eclipse runtime progress monitor to be updated by the builders. 835 * @param fullBuild Set whether to 836 * run the packaging (dexing and building apk) steps of the 837 * post compiler. 838 * @param buildDeps Set whether to run builders on the dependencies of the project 839 * @throws CoreException 840 */ 841 public static void build(IProject project, IProgressMonitor monitor, 842 boolean fullBuild, boolean buildDeps) 843 throws CoreException { 844 // Get list of projects that we depend on 845 List<IJavaProject> androidProjectList = new ArrayList<IJavaProject>(); 846 if (buildDeps) { 847 try { 848 androidProjectList = getAndroidProjectDependencies( 849 BaseProjectHelper.getJavaProject(project)); 850 } catch (JavaModelException e) { 851 AdtPlugin.printErrorToConsole(project, e); 852 } 853 // Recursively build dependencies 854 for (IJavaProject dependency : androidProjectList) { 855 build(dependency.getProject(), monitor, fullBuild, true); 856 } 857 } 858 859 // Do an incremental build to pick up all the deltas 860 project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); 861 862 // If the preferences indicate not to use post compiler optimization 863 // then the incremental build will have done everything necessary 864 if (fullBuild && AdtPrefs.getPrefs().getBuildSkipPostCompileOnFileSave()) { 865 // Create the map to pass to the PostC builder 866 Map<String, String> args = new TreeMap<String, String>(); 867 args.put(PostCompilerBuilder.POST_C_REQUESTED, ""); //$NON-NLS-1$ 868 // Get Post Compiler for this project 869 project.build(IncrementalProjectBuilder.FULL_BUILD, 870 PostCompilerBuilder.ID, args, monitor); 871 } 872 } 873 874 /** 875 * Build the project incrementally. Post compilation step will not occur. 876 * Projects that this project depends on will not be built. 877 * This is equivalent to calling 878 * <code>build(project, monitor, false, false)</code> 879 * 880 * @param project The project to be built. 881 * @param monitor A eclipse runtime progress monitor to be updated by the builders. 882 * @throws CoreException 883 * @see #build(IProject, IProgressMonitor, boolean) 884 */ 885 public static void build(IProject project, IProgressMonitor monitor) 886 throws CoreException { 887 // Disable full building by default 888 build(project, monitor, false, false); 889 } 890 } 891