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.common.sdk.LoadStatus; 20 import com.android.ide.eclipse.adt.AdtConstants; 21 import com.android.ide.eclipse.adt.AdtPlugin; 22 import com.android.ide.eclipse.adt.internal.sdk.ProjectState; 23 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 24 import com.android.sdklib.AndroidVersion; 25 import com.android.sdklib.IAndroidTarget; 26 import com.android.sdklib.IAndroidTarget.IOptionalLibrary; 27 import com.android.sdklib.SdkConstants; 28 29 import org.eclipse.core.resources.IProject; 30 import org.eclipse.core.resources.IWorkspaceRoot; 31 import org.eclipse.core.resources.ResourcesPlugin; 32 import org.eclipse.core.runtime.CoreException; 33 import org.eclipse.core.runtime.FileLocator; 34 import org.eclipse.core.runtime.IPath; 35 import org.eclipse.core.runtime.NullProgressMonitor; 36 import org.eclipse.core.runtime.Path; 37 import org.eclipse.core.runtime.Platform; 38 import org.eclipse.jdt.core.IAccessRule; 39 import org.eclipse.jdt.core.IClasspathAttribute; 40 import org.eclipse.jdt.core.IClasspathContainer; 41 import org.eclipse.jdt.core.IClasspathEntry; 42 import org.eclipse.jdt.core.IJavaModel; 43 import org.eclipse.jdt.core.IJavaProject; 44 import org.eclipse.jdt.core.JavaCore; 45 import org.eclipse.jdt.core.JavaModelException; 46 import org.osgi.framework.Bundle; 47 48 import java.io.File; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.net.URI; 52 import java.net.URISyntaxException; 53 import java.net.URL; 54 import java.util.ArrayList; 55 import java.util.HashSet; 56 import java.util.regex.Pattern; 57 58 /** 59 * Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to 60 * {@link IProject}s. This removes the hard-coded path to the android.jar. 61 */ 62 public class AndroidClasspathContainerInitializer extends BaseClasspathContainerInitializer { 63 64 public static final String NULL_API_URL = "<null>"; //$NON-NLS-1$ 65 66 public static final String SOURCES_ZIP = "/sources.zip"; //$NON-NLS-1$ 67 68 public static final String COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE = 69 "com.android.ide.eclipse.source"; //$NON-NLS-1$ 70 71 private static final String ANDROID_API_REFERENCE = 72 "http://developer.android.com/reference/"; //$NON-NLS-1$ 73 74 private final static String PROPERTY_ANDROID_API = "androidApi"; //$NON-NLS-1$ 75 76 private final static String PROPERTY_ANDROID_SOURCE = "androidSource"; //$NON-NLS-1$ 77 78 /** path separator to store multiple paths in a single property. This is guaranteed to not 79 * be in a path. 80 */ 81 private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$ 82 83 private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$ 84 private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$ 85 private final static String CACHE_VERSION = "01"; //$NON-NLS-1$ 86 private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR; 87 88 private final static int CACHE_INDEX_JAR = 0; 89 private final static int CACHE_INDEX_SRC = 1; 90 private final static int CACHE_INDEX_DOCS_URI = 2; 91 private final static int CACHE_INDEX_OPT_DOCS_URI = 3; 92 private final static int CACHE_INDEX_ADD_ON_START = CACHE_INDEX_OPT_DOCS_URI; 93 94 public AndroidClasspathContainerInitializer() { 95 // pass 96 } 97 98 /** 99 * Binds a classpath container to a {@link IClasspathContainer} for a given project, 100 * or silently fails if unable to do so. 101 * @param containerPath the container path that is the container id. 102 * @param project the project to bind 103 */ 104 @Override 105 public void initialize(IPath containerPath, IJavaProject project) throws CoreException { 106 if (AdtConstants.CONTAINER_FRAMEWORK.equals(containerPath.toString())) { 107 IClasspathContainer container = allocateAndroidContainer(project); 108 if (container != null) { 109 JavaCore.setClasspathContainer(new Path(AdtConstants.CONTAINER_FRAMEWORK), 110 new IJavaProject[] { project }, 111 new IClasspathContainer[] { container }, 112 new NullProgressMonitor()); 113 } 114 } 115 } 116 117 /** 118 * Updates the {@link IJavaProject} objects with new android framework container. This forces 119 * JDT to recompile them. 120 * @param androidProjects the projects to update. 121 * @return <code>true</code> if success, <code>false</code> otherwise. 122 */ 123 static boolean updateProjects(IJavaProject[] androidProjects) { 124 try { 125 // Allocate a new AndroidClasspathContainer, and associate it to the android framework 126 // container id for each projects. 127 // By providing a new association between a container id and a IClasspathContainer, 128 // this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with 129 // IClasspathContainer#getClasspathEntries()), and therefore force recompilation of 130 // the projects. 131 int projectCount = androidProjects.length; 132 133 IClasspathContainer[] containers = new IClasspathContainer[projectCount]; 134 for (int i = 0 ; i < projectCount; i++) { 135 containers[i] = allocateAndroidContainer(androidProjects[i]); 136 } 137 138 // give each project their new container in one call. 139 JavaCore.setClasspathContainer( 140 new Path(AdtConstants.CONTAINER_FRAMEWORK), 141 androidProjects, containers, new NullProgressMonitor()); 142 143 return true; 144 } catch (JavaModelException e) { 145 return false; 146 } 147 } 148 149 /** 150 * Allocates and returns an {@link AndroidClasspathContainer} object with the proper 151 * path to the framework jar file. 152 * @param javaProject The java project that will receive the container. 153 */ 154 private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) { 155 final IProject iProject = javaProject.getProject(); 156 157 String markerMessage = null; 158 boolean outputToConsole = true; 159 IAndroidTarget target = null; 160 161 try { 162 AdtPlugin plugin = AdtPlugin.getDefault(); 163 if (plugin == null) { // This is totally weird, but I've seen it happen! 164 return null; 165 } 166 167 synchronized (Sdk.getLock()) { 168 boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED; 169 170 // check if the project has a valid target. 171 ProjectState state = Sdk.getProjectState(iProject); 172 if (state == null) { 173 // looks like the project state (project.properties) couldn't be read! 174 markerMessage = String.format( 175 "Project has no %1$s file! Edit the project properties to set one.", 176 SdkConstants.FN_PROJECT_PROPERTIES); 177 } else { 178 // this might be null if the sdk is not yet loaded. 179 target = state.getTarget(); 180 181 // if we are loaded and the target is non null, we create a valid 182 // ClassPathContainer 183 if (sdkIsLoaded && target != null) { 184 // first make sure the target has loaded its data 185 Sdk.getCurrent().checkAndLoadTargetData(target, null /*project*/); 186 187 String targetName = target.getClasspathName(); 188 189 return new AndroidClasspathContainer( 190 createClasspathEntries(iProject, target, targetName), 191 new Path(AdtConstants.CONTAINER_FRAMEWORK), 192 targetName, 193 IClasspathContainer.K_DEFAULT_SYSTEM); 194 } 195 196 // In case of error, we'll try different thing to provide the best error message 197 // possible. 198 // Get the project's target's hash string (if it exists) 199 String hashString = state.getTargetHashString(); 200 201 if (hashString == null || hashString.length() == 0) { 202 // if there is no hash string we only show this if the SDK is loaded. 203 // For a project opened at start-up with no target, this would be displayed 204 // twice, once when the project is opened, and once after the SDK has 205 // finished loading. 206 // By testing the sdk is loaded, we only show this once in the console. 207 if (sdkIsLoaded) { 208 markerMessage = String.format( 209 "Project has no target set. Edit the project properties to set one."); 210 } 211 } else if (sdkIsLoaded) { 212 markerMessage = String.format( 213 "Unable to resolve target '%s'", hashString); 214 } else { 215 // this is the case where there is a hashString but the SDK is not yet 216 // loaded and therefore we can't get the target yet. 217 // We check if there is a cache of the needed information. 218 AndroidClasspathContainer container = getContainerFromCache(iProject, 219 target); 220 221 if (container == null) { 222 // either the cache was wrong (ie folder does not exists anymore), or 223 // there was no cache. In this case we need to make sure the project 224 // is resolved again after the SDK is loaded. 225 plugin.setProjectToResolve(javaProject); 226 227 markerMessage = String.format( 228 "Unable to resolve target '%s' until the SDK is loaded.", 229 hashString); 230 231 // let's not log this one to the console as it will happen at 232 // every boot, and it's expected. (we do keep the error marker though). 233 outputToConsole = false; 234 235 } else { 236 // we created a container from the cache, so we register the project 237 // to be checked for cache validity once the SDK is loaded 238 plugin.setProjectToCheck(javaProject); 239 240 // and return the container 241 return container; 242 } 243 } 244 } 245 246 // return a dummy container to replace the one we may have had before. 247 // It'll be replaced by the real when if/when the target is resolved if/when the 248 // SDK finishes loading. 249 return new IClasspathContainer() { 250 @Override 251 public IClasspathEntry[] getClasspathEntries() { 252 return new IClasspathEntry[0]; 253 } 254 255 @Override 256 public String getDescription() { 257 return "Unable to get system library for the project"; 258 } 259 260 @Override 261 public int getKind() { 262 return IClasspathContainer.K_DEFAULT_SYSTEM; 263 } 264 265 @Override 266 public IPath getPath() { 267 return null; 268 } 269 }; 270 } 271 } finally { 272 processError(iProject, markerMessage, AdtConstants.MARKER_TARGET, outputToConsole); 273 } 274 } 275 276 /** 277 * Creates and returns an array of {@link IClasspathEntry} objects for the android 278 * framework and optional libraries. 279 * <p/>This references the OS path to the android.jar and the 280 * java doc directory. This is dynamically created when a project is opened, 281 * and never saved in the project itself, so there's no risk of storing an 282 * obsolete path. 283 * The method also stores the paths used to create the entries in the project persistent 284 * properties. A new {@link AndroidClasspathContainer} can be created from the stored path 285 * using the {@link #getContainerFromCache(IProject)} method. 286 * @param project 287 * @param target The target that contains the libraries. 288 * @param targetName 289 */ 290 private static IClasspathEntry[] createClasspathEntries(IProject project, 291 IAndroidTarget target, String targetName) { 292 293 // get the path from the target 294 String[] paths = getTargetPaths(target); 295 296 // create the classpath entry from the paths 297 IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target); 298 299 // paths now contains all the path required to recreate the IClasspathEntry with no 300 // target info. We encode them in a single string, with each path separated by 301 // OS path separator. 302 StringBuilder sb = new StringBuilder(CACHE_VERSION); 303 for (String p : paths) { 304 sb.append(PATH_SEPARATOR); 305 sb.append(p); 306 } 307 308 // store this in a project persistent property 309 ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString()); 310 ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName); 311 312 return entries; 313 } 314 315 /** 316 * Generates an {@link AndroidClasspathContainer} from the project cache, if possible. 317 */ 318 private static AndroidClasspathContainer getContainerFromCache(IProject project, 319 IAndroidTarget target) { 320 // get the cached info from the project persistent properties. 321 String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE); 322 String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME); 323 if (cache == null || targetNameCache == null) { 324 return null; 325 } 326 327 // the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator. 328 if (cache.startsWith(CACHE_VERSION_SEP) == false) { 329 return null; 330 } 331 332 cache = cache.substring(CACHE_VERSION_SEP.length()); 333 334 // the cache contains multiple paths, separated by a character guaranteed to not be in 335 // the path (\u001C). 336 // The first 3 are for android.jar (jar, source, doc), the rest are for the optional 337 // libraries and should contain at least one doc and a jar (if there are any libraries). 338 // Therefore, the path count should be 3 or 5+ 339 String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR)); 340 if (paths.length < 3 || paths.length == 4) { 341 return null; 342 } 343 344 // now we check the paths actually exist. 345 // There's an exception: If the source folder for android.jar does not exist, this is 346 // not a problem, so we skip it. 347 // Also paths[CACHE_INDEX_DOCS_URI] is a URI to the javadoc, so we test it a 348 // bit differently. 349 try { 350 if (new File(paths[CACHE_INDEX_JAR]).exists() == false || 351 new File(new URI(paths[CACHE_INDEX_DOCS_URI])).exists() == false) { 352 return null; 353 } 354 355 // check the path for the add-ons, if they exist. 356 if (paths.length > CACHE_INDEX_ADD_ON_START) { 357 358 // check the docs path separately from the rest of the paths as it's a URI. 359 if (new File(new URI(paths[CACHE_INDEX_OPT_DOCS_URI])).exists() == false) { 360 return null; 361 } 362 363 // now just check the remaining paths. 364 for (int i = CACHE_INDEX_ADD_ON_START + 1; i < paths.length; i++) { 365 String path = paths[i]; 366 if (path.length() > 0) { 367 File f = new File(path); 368 if (f.exists() == false) { 369 return null; 370 } 371 } 372 } 373 } 374 } catch (URISyntaxException e) { 375 return null; 376 } 377 378 IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths, target); 379 380 return new AndroidClasspathContainer(entries, 381 new Path(AdtConstants.CONTAINER_FRAMEWORK), 382 targetNameCache, IClasspathContainer.K_DEFAULT_SYSTEM); 383 } 384 385 /** 386 * Generates an array of {@link IClasspathEntry} from a set of paths. 387 * @see #getTargetPaths(IAndroidTarget) 388 */ 389 private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths, 390 IAndroidTarget target) { 391 ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>(); 392 393 // First, we create the IClasspathEntry for the framework. 394 // now add the android framework to the class path. 395 // create the path object. 396 IPath androidLib = new Path(paths[CACHE_INDEX_JAR]); 397 398 IPath androidSrc = null; 399 String androidSrcOsPath = null; 400 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 401 if (target != null) { 402 androidSrcOsPath = 403 ProjectHelper.loadStringProperty(root, getAndroidSourceProperty(target)); 404 } 405 if (androidSrcOsPath != null && androidSrcOsPath.trim().length() > 0) { 406 androidSrc = new Path(androidSrcOsPath); 407 } 408 if (androidSrc == null) { 409 androidSrc = new Path(paths[CACHE_INDEX_SRC]); 410 File androidSrcFile = new File(paths[CACHE_INDEX_SRC]); 411 if (!androidSrcFile.isDirectory()) { 412 androidSrc = null; 413 } 414 } 415 416 if (androidSrc == null && target != null) { 417 Bundle bundle = getSourceBundle(); 418 419 if (bundle != null) { 420 AndroidVersion version = target.getVersion(); 421 String apiString = version.getApiString(); 422 String sourcePath = apiString + SOURCES_ZIP; 423 URL sourceURL = bundle.getEntry(sourcePath); 424 if (sourceURL != null) { 425 URL url = null; 426 try { 427 url = FileLocator.resolve(sourceURL); 428 } catch (IOException ignore) { 429 } 430 if (url != null) { 431 androidSrcOsPath = url.getFile(); 432 if (new File(androidSrcOsPath).isFile()) { 433 androidSrc = new Path(androidSrcOsPath); 434 } 435 } 436 } 437 } 438 } 439 440 // create the java doc link. 441 String androidApiURL = ProjectHelper.loadStringProperty(root, PROPERTY_ANDROID_API); 442 String apiURL = null; 443 if (androidApiURL != null && testURL(androidApiURL)) { 444 apiURL = androidApiURL; 445 } else { 446 if (testURL(paths[CACHE_INDEX_DOCS_URI])) { 447 apiURL = paths[CACHE_INDEX_DOCS_URI]; 448 } else if (testURL(ANDROID_API_REFERENCE)) { 449 apiURL = ANDROID_API_REFERENCE; 450 } 451 } 452 453 IClasspathAttribute[] attributes = null; 454 if (apiURL != null && !NULL_API_URL.equals(apiURL)) { 455 IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( 456 IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, apiURL); 457 attributes = new IClasspathAttribute[] { 458 cpAttribute 459 }; 460 } 461 // create the access rule to restrict access to classes in 462 // com.android.internal 463 IAccessRule accessRule = JavaCore.newAccessRule(new Path("com/android/internal/**"), //$NON-NLS-1$ 464 IAccessRule.K_NON_ACCESSIBLE); 465 466 IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(androidLib, 467 androidSrc, // source attachment path 468 null, // default source attachment root path. 469 new IAccessRule[] { accessRule }, 470 attributes, 471 false // not exported. 472 ); 473 474 list.add(frameworkClasspathEntry); 475 476 // now deal with optional libraries 477 if (paths.length >= 5) { 478 String docPath = paths[CACHE_INDEX_OPT_DOCS_URI]; 479 int i = 4; 480 while (i < paths.length) { 481 Path jarPath = new Path(paths[i++]); 482 483 attributes = null; 484 if (docPath.length() > 0) { 485 attributes = new IClasspathAttribute[] { 486 JavaCore.newClasspathAttribute( 487 IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, docPath) 488 }; 489 } 490 491 IClasspathEntry entry = JavaCore.newLibraryEntry( 492 jarPath, 493 null, // source attachment path 494 null, // default source attachment root path. 495 null, 496 attributes, 497 false // not exported. 498 ); 499 list.add(entry); 500 } 501 } 502 503 if (apiURL != null) { 504 ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, apiURL); 505 } 506 if (androidSrc != null && target != null) { 507 ProjectHelper.saveStringProperty(root, getAndroidSourceProperty(target), 508 androidSrc.toOSString()); 509 } 510 return list.toArray(new IClasspathEntry[list.size()]); 511 } 512 513 private static Bundle getSourceBundle() { 514 String bundleId = System.getProperty(COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE, 515 COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE); 516 Bundle bundle = Platform.getBundle(bundleId); 517 return bundle; 518 } 519 520 private static String getAndroidSourceProperty(IAndroidTarget target) { 521 if (target == null) { 522 return null; 523 } 524 String androidSourceProperty = PROPERTY_ANDROID_SOURCE + "_" 525 + target.getVersion().getApiString(); 526 return androidSourceProperty; 527 } 528 529 private static boolean testURL(String androidApiURL) { 530 boolean valid = false; 531 InputStream is = null; 532 try { 533 URL testURL = new URL(androidApiURL); 534 is = testURL.openStream(); 535 valid = true; 536 } catch (Exception ignore) { 537 } finally { 538 if (is != null) { 539 try { 540 is.close(); 541 } catch (IOException ignore) { 542 } 543 } 544 } 545 return valid; 546 } 547 548 /** 549 * Checks the projects' caches. If the cache was valid, the project is removed from the list. 550 * @param projects the list of projects to check. 551 */ 552 public static void checkProjectsCache(ArrayList<IJavaProject> projects) { 553 Sdk currentSdk = Sdk.getCurrent(); 554 int i = 0; 555 projectLoop: while (i < projects.size()) { 556 IJavaProject javaProject = projects.get(i); 557 IProject iProject = javaProject.getProject(); 558 559 // check if the project is opened 560 if (iProject.isOpen() == false) { 561 // remove from the list 562 // we do not increment i in this case. 563 projects.remove(i); 564 565 continue; 566 } 567 568 // project that have been resolved before the sdk was loaded 569 // will have a ProjectState where the IAndroidTarget is null 570 // so we load the target now that the SDK is loaded. 571 IAndroidTarget target = currentSdk.loadTarget(Sdk.getProjectState(iProject)); 572 if (target == null) { 573 // this is really not supposed to happen. This would mean there are cached paths, 574 // but project.properties was deleted. Keep the project in the list to force 575 // a resolve which will display the error. 576 i++; 577 continue; 578 } 579 580 String[] targetPaths = getTargetPaths(target); 581 582 // now get the cached paths 583 String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE); 584 if (cache == null) { 585 // this should not happen. We'll force resolve again anyway. 586 i++; 587 continue; 588 } 589 590 String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR)); 591 if (cachedPaths.length < 3 || cachedPaths.length == 4) { 592 // paths length is wrong. simply resolve the project again 593 i++; 594 continue; 595 } 596 597 // Now we compare the paths. The first 4 can be compared directly. 598 // because of case sensitiveness we need to use File objects 599 600 if (targetPaths.length != cachedPaths.length) { 601 // different paths, force resolve again. 602 i++; 603 continue; 604 } 605 606 // compare the main paths (android.jar, main sources, main javadoc) 607 if (new File(targetPaths[CACHE_INDEX_JAR]).equals( 608 new File(cachedPaths[CACHE_INDEX_JAR])) == false || 609 new File(targetPaths[CACHE_INDEX_SRC]).equals( 610 new File(cachedPaths[CACHE_INDEX_SRC])) == false || 611 new File(targetPaths[CACHE_INDEX_DOCS_URI]).equals( 612 new File(cachedPaths[CACHE_INDEX_DOCS_URI])) == false) { 613 // different paths, force resolve again. 614 i++; 615 continue; 616 } 617 618 if (cachedPaths.length > CACHE_INDEX_OPT_DOCS_URI) { 619 // compare optional libraries javadoc 620 if (new File(targetPaths[CACHE_INDEX_OPT_DOCS_URI]).equals( 621 new File(cachedPaths[CACHE_INDEX_OPT_DOCS_URI])) == false) { 622 // different paths, force resolve again. 623 i++; 624 continue; 625 } 626 627 // testing the optional jar files is a little bit trickier. 628 // The order is not guaranteed to be identical. 629 // From a previous test, we do know however that there is the same number. 630 // The number of libraries should be low enough that we can simply go through the 631 // lists manually. 632 targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) { 633 String targetPath = targetPaths[tpi]; 634 635 // look for a match in the other array 636 for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) { 637 if (new File(targetPath).equals(new File(cachedPaths[cpi]))) { 638 // found a match. Try the next targetPath 639 continue targetLoop; 640 } 641 } 642 643 // if we stop here, we haven't found a match, which means there's a 644 // discrepancy in the libraries. We force a resolve. 645 i++; 646 continue projectLoop; 647 } 648 } 649 650 // at the point the check passes, and we can remove the project from the list. 651 // we do not increment i in this case. 652 projects.remove(i); 653 } 654 } 655 656 /** 657 * Returns the paths necessary to create the {@link IClasspathEntry} for this targets. 658 * <p/>The paths are always in the same order. 659 * <ul> 660 * <li>Path to android.jar</li> 661 * <li>Path to the source code for android.jar</li> 662 * <li>Path to the javadoc for the android platform</li> 663 * </ul> 664 * Additionally, if there are optional libraries, the array will contain: 665 * <ul> 666 * <li>Path to the libraries javadoc</li> 667 * <li>Path to the first .jar file</li> 668 * <li>(more .jar as needed)</li> 669 * </ul> 670 */ 671 private static String[] getTargetPaths(IAndroidTarget target) { 672 ArrayList<String> paths = new ArrayList<String>(); 673 674 // first, we get the path for android.jar 675 // The order is: android.jar, source folder, docs folder 676 paths.add(target.getPath(IAndroidTarget.ANDROID_JAR)); 677 paths.add(target.getPath(IAndroidTarget.SOURCES)); 678 paths.add(AdtPlugin.getUrlDoc()); 679 680 // now deal with optional libraries. 681 IOptionalLibrary[] libraries = target.getOptionalLibraries(); 682 if (libraries != null) { 683 // all the optional libraries use the same javadoc, so we start with this 684 String targetDocPath = target.getPath(IAndroidTarget.DOCS); 685 if (targetDocPath != null) { 686 paths.add(ProjectHelper.getJavaDocPath(targetDocPath)); 687 } else { 688 // we add an empty string, to always have the same count. 689 paths.add(""); 690 } 691 692 // because different libraries could use the same jar file, we make sure we add 693 // each jar file only once. 694 HashSet<String> visitedJars = new HashSet<String>(); 695 for (IOptionalLibrary library : libraries) { 696 String jarPath = library.getJarPath(); 697 if (visitedJars.contains(jarPath) == false) { 698 visitedJars.add(jarPath); 699 paths.add(jarPath); 700 } 701 } 702 } 703 704 return paths.toArray(new String[paths.size()]); 705 } 706 707 @Override 708 public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) { 709 return true; 710 } 711 712 @Override 713 public void requestClasspathContainerUpdate(IPath containerPath, IJavaProject project, 714 IClasspathContainer containerSuggestion) throws CoreException { 715 AdtPlugin plugin = AdtPlugin.getDefault(); 716 717 synchronized (Sdk.getLock()) { 718 boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED; 719 720 // check if the project has a valid target. 721 IAndroidTarget target = null; 722 if (sdkIsLoaded) { 723 target = Sdk.getCurrent().getTarget(project.getProject()); 724 } 725 if (sdkIsLoaded && target != null) { 726 String[] paths = getTargetPaths(target); 727 IPath android_lib = new Path(paths[CACHE_INDEX_JAR]); 728 IClasspathEntry[] entries = containerSuggestion.getClasspathEntries(); 729 for (int i = 0; i < entries.length; i++) { 730 IClasspathEntry entry = entries[i]; 731 if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { 732 IPath entryPath = entry.getPath(); 733 734 if (entryPath != null) { 735 if (entryPath.equals(android_lib)) { 736 IPath entrySrcPath = entry.getSourceAttachmentPath(); 737 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 738 if (entrySrcPath != null) { 739 ProjectHelper.saveStringProperty(root, 740 getAndroidSourceProperty(target), 741 entrySrcPath.toString()); 742 } else { 743 ProjectHelper.saveStringProperty(root, 744 getAndroidSourceProperty(target), null); 745 } 746 IClasspathAttribute[] extraAttributtes = entry.getExtraAttributes(); 747 if (extraAttributtes.length == 0) { 748 ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, 749 NULL_API_URL); 750 } 751 for (int j = 0; j < extraAttributtes.length; j++) { 752 IClasspathAttribute extraAttribute = extraAttributtes[j]; 753 String value = extraAttribute.getValue(); 754 if ((value == null || value.trim().length() == 0) 755 && IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME 756 .equals(extraAttribute.getName())) { 757 value = NULL_API_URL; 758 } 759 if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME 760 .equals(extraAttribute.getName())) { 761 ProjectHelper.saveStringProperty(root, 762 PROPERTY_ANDROID_API, value); 763 764 } 765 } 766 } 767 } 768 } 769 } 770 rebindClasspathEntries(project.getJavaModel(), containerPath); 771 } 772 } 773 } 774 775 private static void rebindClasspathEntries(IJavaModel model, IPath containerPath) 776 throws JavaModelException { 777 ArrayList<IJavaProject> affectedProjects = new ArrayList<IJavaProject>(); 778 779 IJavaProject[] projects = model.getJavaProjects(); 780 for (int i = 0; i < projects.length; i++) { 781 IJavaProject project = projects[i]; 782 IClasspathEntry[] entries = project.getRawClasspath(); 783 for (int k = 0; k < entries.length; k++) { 784 IClasspathEntry curr = entries[k]; 785 if (curr.getEntryKind() == IClasspathEntry.CPE_CONTAINER 786 && containerPath.equals(curr.getPath())) { 787 affectedProjects.add(project); 788 } 789 } 790 } 791 if (!affectedProjects.isEmpty()) { 792 IJavaProject[] affected = affectedProjects 793 .toArray(new IJavaProject[affectedProjects.size()]); 794 updateProjects(affected); 795 } 796 } 797 } 798