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