1 /* 2 * Copyright (C) 2008 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.sdk; 18 19 import static com.android.SdkConstants.DOT_XML; 20 import static com.android.SdkConstants.EXT_JAR; 21 import static com.android.SdkConstants.FD_RES; 22 23 import com.android.SdkConstants; 24 import com.android.annotations.NonNull; 25 import com.android.annotations.Nullable; 26 import com.android.ddmlib.IDevice; 27 import com.android.ide.common.rendering.LayoutLibrary; 28 import com.android.ide.common.sdk.LoadStatus; 29 import com.android.ide.eclipse.adt.AdtConstants; 30 import com.android.ide.eclipse.adt.AdtPlugin; 31 import com.android.ide.eclipse.adt.internal.build.DexWrapper; 32 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; 33 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 34 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 35 import com.android.ide.eclipse.adt.internal.project.LibraryClasspathContainerInitializer; 36 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 37 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; 38 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; 39 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; 40 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; 41 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryDifference; 42 import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; 43 import com.android.io.StreamException; 44 import com.android.prefs.AndroidLocation.AndroidLocationException; 45 import com.android.sdklib.AndroidVersion; 46 import com.android.sdklib.IAndroidTarget; 47 import com.android.sdklib.SdkManager; 48 import com.android.sdklib.devices.Device; 49 import com.android.sdklib.devices.DeviceManager; 50 import com.android.sdklib.internal.avd.AvdManager; 51 import com.android.sdklib.internal.project.ProjectProperties; 52 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 53 import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; 54 import com.android.utils.ILogger; 55 56 import org.eclipse.core.resources.IFile; 57 import org.eclipse.core.resources.IFolder; 58 import org.eclipse.core.resources.IMarkerDelta; 59 import org.eclipse.core.resources.IProject; 60 import org.eclipse.core.resources.IResource; 61 import org.eclipse.core.resources.IResourceDelta; 62 import org.eclipse.core.resources.IncrementalProjectBuilder; 63 import org.eclipse.core.runtime.CoreException; 64 import org.eclipse.core.runtime.IPath; 65 import org.eclipse.core.runtime.IProgressMonitor; 66 import org.eclipse.core.runtime.IStatus; 67 import org.eclipse.core.runtime.QualifiedName; 68 import org.eclipse.core.runtime.Status; 69 import org.eclipse.core.runtime.jobs.Job; 70 import org.eclipse.jdt.core.IJavaProject; 71 import org.eclipse.jdt.core.JavaCore; 72 import org.eclipse.jface.preference.IPreferenceStore; 73 import org.eclipse.ui.IEditorDescriptor; 74 import org.eclipse.ui.IEditorInput; 75 import org.eclipse.ui.IEditorPart; 76 import org.eclipse.ui.IEditorReference; 77 import org.eclipse.ui.IFileEditorInput; 78 import org.eclipse.ui.IWorkbenchPage; 79 import org.eclipse.ui.IWorkbenchPartSite; 80 import org.eclipse.ui.IWorkbenchWindow; 81 import org.eclipse.ui.PartInitException; 82 import org.eclipse.ui.PlatformUI; 83 import org.eclipse.ui.ide.IDE; 84 85 import java.io.File; 86 import java.io.IOException; 87 import java.net.MalformedURLException; 88 import java.net.URL; 89 import java.util.ArrayList; 90 import java.util.Arrays; 91 import java.util.Collection; 92 import java.util.HashMap; 93 import java.util.HashSet; 94 import java.util.List; 95 import java.util.Map; 96 import java.util.Map.Entry; 97 import java.util.Set; 98 99 /** 100 * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used 101 * at the same time. 102 * 103 * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of 104 * the Sdk object. 105 * 106 * To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}. 107 */ 108 public final class Sdk { 109 private final static boolean DEBUG = false; 110 111 private final static Object LOCK = new Object(); 112 113 private static Sdk sCurrentSdk = null; 114 115 /** 116 * Map associating {@link IProject} and their state {@link ProjectState}. 117 * <p/>This <b>MUST NOT</b> be accessed directly. Instead use {@link #getProjectState(IProject)}. 118 */ 119 private final static HashMap<IProject, ProjectState> sProjectStateMap = 120 new HashMap<IProject, ProjectState>(); 121 122 /** 123 * Data bundled using during the load of Target data. 124 * <p/>This contains the {@link LoadStatus} and a list of projects that attempted 125 * to compile before the loading was finished. Those projects will be recompiled 126 * at the end of the loading. 127 */ 128 private final static class TargetLoadBundle { 129 LoadStatus status; 130 final HashSet<IJavaProject> projectsToReload = new HashSet<IJavaProject>(); 131 } 132 133 private final SdkManager mManager; 134 private final DexWrapper mDexWrapper; 135 private final AvdManager mAvdManager; 136 private final DeviceManager mDeviceManager; 137 138 /** Map associating an {@link IAndroidTarget} to an {@link AndroidTargetData} */ 139 private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap = 140 new HashMap<IAndroidTarget, AndroidTargetData>(); 141 /** Map associating an {@link IAndroidTarget} and its {@link TargetLoadBundle}. */ 142 private final HashMap<IAndroidTarget, TargetLoadBundle> mTargetDataStatusMap = 143 new HashMap<IAndroidTarget, TargetLoadBundle>(); 144 145 /** 146 * If true the target data will never load anymore. The only way to reload them is to 147 * completely reload the SDK with {@link #loadSdk(String)} 148 */ 149 private boolean mDontLoadTargetData = false; 150 151 private final String mDocBaseUrl; 152 153 /** 154 * Classes implementing this interface will receive notification when targets are changed. 155 */ 156 public interface ITargetChangeListener { 157 /** 158 * Sent when project has its target changed. 159 */ 160 void onProjectTargetChange(IProject changedProject); 161 162 /** 163 * Called when the targets are loaded (either the SDK finished loading when Eclipse starts, 164 * or the SDK is changed). 165 */ 166 void onTargetLoaded(IAndroidTarget target); 167 168 /** 169 * Called when the base content of the SDK is parsed. 170 */ 171 void onSdkLoaded(); 172 } 173 174 /** 175 * Basic abstract implementation of the ITargetChangeListener for the case where both 176 * {@link #onProjectTargetChange(IProject)} and {@link #onTargetLoaded(IAndroidTarget)} 177 * use the same code based on a simple test requiring to know the current IProject. 178 */ 179 public static abstract class TargetChangeListener implements ITargetChangeListener { 180 /** 181 * Returns the {@link IProject} associated with the listener. 182 */ 183 public abstract IProject getProject(); 184 185 /** 186 * Called when the listener needs to take action on the event. This is only called 187 * if {@link #getProject()} and the {@link IAndroidTarget} associated with the project 188 * match the values received in {@link #onProjectTargetChange(IProject)} and 189 * {@link #onTargetLoaded(IAndroidTarget)}. 190 */ 191 public abstract void reload(); 192 193 @Override 194 public void onProjectTargetChange(IProject changedProject) { 195 if (changedProject != null && changedProject.equals(getProject())) { 196 reload(); 197 } 198 } 199 200 @Override 201 public void onTargetLoaded(IAndroidTarget target) { 202 IProject project = getProject(); 203 if (target != null && target.equals(Sdk.getCurrent().getTarget(project))) { 204 reload(); 205 } 206 } 207 208 @Override 209 public void onSdkLoaded() { 210 // do nothing; 211 } 212 } 213 214 /** 215 * Returns the lock object used to synchronize all operations dealing with SDK, targets and 216 * projects. 217 */ 218 @NonNull 219 public static final Object getLock() { 220 return LOCK; 221 } 222 223 /** 224 * Loads an SDK and returns an {@link Sdk} object if success. 225 * <p/>If the SDK failed to load, it displays an error to the user. 226 * @param sdkLocation the OS path to the SDK. 227 */ 228 @Nullable 229 public static Sdk loadSdk(String sdkLocation) { 230 synchronized (LOCK) { 231 if (sCurrentSdk != null) { 232 sCurrentSdk.dispose(); 233 sCurrentSdk = null; 234 } 235 236 final ArrayList<String> logMessages = new ArrayList<String>(); 237 ILogger log = new ILogger() { 238 @Override 239 public void error(Throwable throwable, String errorFormat, Object... arg) { 240 if (errorFormat != null) { 241 logMessages.add(String.format("Error: " + errorFormat, arg)); 242 } 243 244 if (throwable != null) { 245 logMessages.add(throwable.getMessage()); 246 } 247 } 248 249 @Override 250 public void warning(@NonNull String warningFormat, Object... arg) { 251 logMessages.add(String.format("Warning: " + warningFormat, arg)); 252 } 253 254 @Override 255 public void info(@NonNull String msgFormat, Object... arg) { 256 logMessages.add(String.format(msgFormat, arg)); 257 } 258 259 @Override 260 public void verbose(@NonNull String msgFormat, Object... arg) { 261 info(msgFormat, arg); 262 } 263 }; 264 265 // get an SdkManager object for the location 266 SdkManager manager = SdkManager.createManager(sdkLocation, log); 267 if (manager != null) { 268 // load DX. 269 DexWrapper dexWrapper = new DexWrapper(); 270 String dexLocation = 271 sdkLocation + File.separator + 272 SdkConstants.OS_SDK_PLATFORM_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR; 273 IStatus res = dexWrapper.loadDex(dexLocation); 274 if (res != Status.OK_STATUS) { 275 log.error(null, res.getMessage()); 276 dexWrapper = null; 277 } 278 279 // create the AVD Manager 280 AvdManager avdManager = null; 281 try { 282 avdManager = AvdManager.getInstance(manager, log); 283 } catch (AndroidLocationException e) { 284 log.error(e, "Error parsing the AVDs"); 285 } 286 sCurrentSdk = new Sdk(manager, dexWrapper, avdManager); 287 return sCurrentSdk; 288 } else { 289 StringBuilder sb = new StringBuilder("Error Loading the SDK:\n"); 290 for (String msg : logMessages) { 291 sb.append('\n'); 292 sb.append(msg); 293 } 294 AdtPlugin.displayError("Android SDK", sb.toString()); 295 } 296 return null; 297 } 298 } 299 300 /** 301 * Returns the current {@link Sdk} object. 302 */ 303 @Nullable 304 public static Sdk getCurrent() { 305 synchronized (LOCK) { 306 return sCurrentSdk; 307 } 308 } 309 310 /** 311 * Returns the location (OS path) of the current SDK. 312 */ 313 public String getSdkLocation() { 314 return mManager.getLocation(); 315 } 316 317 /** 318 * Returns a <em>new</em> {@link SdkManager} that can parse the SDK located 319 * at the current {@link #getSdkLocation()}. 320 * <p/> 321 * Implementation detail: The {@link Sdk} has its own internal manager with 322 * a custom logger which is not designed to be useful for outsiders. Callers 323 * who need their own {@link SdkManager} for parsing will often want to control 324 * the logger for their own need. 325 * <p/> 326 * This is just a convenient method equivalent to writing: 327 * <pre>SdkManager.createManager(Sdk.getCurrent().getSdkLocation(), log);</pre> 328 * 329 * @param log The logger for the {@link SdkManager}. 330 * @return A new {@link SdkManager} parsing the same location. 331 */ 332 public @NonNull SdkManager getNewSdkManager(@NonNull ILogger log) { 333 return SdkManager.createManager(getSdkLocation(), log); 334 } 335 336 /** 337 * Returns the URL to the local documentation. 338 * Can return null if no documentation is found in the current SDK. 339 * 340 * @return A file:// URL on the local documentation folder if it exists or null. 341 */ 342 @Nullable 343 public String getDocumentationBaseUrl() { 344 return mDocBaseUrl; 345 } 346 347 /** 348 * Returns the list of targets that are available in the SDK. 349 */ 350 public IAndroidTarget[] getTargets() { 351 return mManager.getTargets(); 352 } 353 354 /** 355 * Queries the underlying SDK Manager to check whether the platforms or addons 356 * directories have changed on-disk. Does not reload the SDK. 357 * <p/> 358 * This is a quick test based on the presence of the directories, their timestamps 359 * and a quick checksum of the source.properties files. It's possible to have 360 * false positives (e.g. if a file is manually modified in a platform) or false 361 * negatives (e.g. if a platform data file is changed manually in a 2nd level 362 * directory without altering the source.properties.) 363 */ 364 public boolean haveTargetsChanged() { 365 return mManager.hasChanged(); 366 } 367 368 /** 369 * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}. 370 * 371 * @param hash the {@link IAndroidTarget} hash string. 372 * @return The matching {@link IAndroidTarget} or null. 373 */ 374 @Nullable 375 public IAndroidTarget getTargetFromHashString(@NonNull String hash) { 376 return mManager.getTargetFromHashString(hash); 377 } 378 379 /** 380 * Initializes a new project with a target. This creates the <code>project.properties</code> 381 * file. 382 * @param project the project to initialize 383 * @param target the project's target. 384 * @throws IOException if creating the file failed in any way. 385 * @throws StreamException if processing the project property file fails 386 */ 387 public void initProject(@Nullable IProject project, @Nullable IAndroidTarget target) 388 throws IOException, StreamException { 389 if (project == null || target == null) { 390 return; 391 } 392 393 synchronized (LOCK) { 394 // check if there's already a state? 395 ProjectState state = getProjectState(project); 396 397 ProjectPropertiesWorkingCopy properties = null; 398 399 if (state != null) { 400 properties = state.getProperties().makeWorkingCopy(); 401 } 402 403 if (properties == null) { 404 IPath location = project.getLocation(); 405 if (location == null) { // can return null when the project is being deleted. 406 // do nothing and return null; 407 return; 408 } 409 410 properties = ProjectProperties.create(location.toOSString(), PropertyType.PROJECT); 411 } 412 413 // save the target hash string in the project persistent property 414 properties.setProperty(ProjectProperties.PROPERTY_TARGET, target.hashString()); 415 properties.save(); 416 } 417 } 418 419 /** 420 * Returns the {@link ProjectState} object associated with a given project. 421 * <p/> 422 * This method is the only way to properly get the project's {@link ProjectState} 423 * If the project has not yet been loaded, then it is loaded. 424 * <p/>Because this methods deals with projects, it's not linked to an actual {@link Sdk} 425 * objects, and therefore is static. 426 * <p/>The value returned by {@link ProjectState#getTarget()} will change as {@link Sdk} objects 427 * are replaced. 428 * @param project the request project 429 * @return the ProjectState for the project. 430 */ 431 @Nullable 432 @SuppressWarnings("deprecation") 433 public static ProjectState getProjectState(IProject project) { 434 if (project == null) { 435 return null; 436 } 437 438 synchronized (LOCK) { 439 ProjectState state = sProjectStateMap.get(project); 440 if (state == null) { 441 // load the project.properties from the project folder. 442 IPath location = project.getLocation(); 443 if (location == null) { // can return null when the project is being deleted. 444 // do nothing and return null; 445 return null; 446 } 447 448 String projectLocation = location.toOSString(); 449 450 ProjectProperties properties = ProjectProperties.load(projectLocation, 451 PropertyType.PROJECT); 452 if (properties == null) { 453 // legacy support: look for default.properties and rename it if needed. 454 properties = ProjectProperties.load(projectLocation, 455 PropertyType.LEGACY_DEFAULT); 456 457 if (properties == null) { 458 AdtPlugin.log(IStatus.ERROR, 459 "Failed to load properties file for project '%s'", 460 project.getName()); 461 return null; 462 } else { 463 //legacy mode. 464 // get a working copy with the new type "project" 465 ProjectPropertiesWorkingCopy wc = properties.makeWorkingCopy( 466 PropertyType.PROJECT); 467 // and save it 468 try { 469 wc.save(); 470 471 // delete the old file. 472 ProjectProperties.delete(projectLocation, PropertyType.LEGACY_DEFAULT); 473 474 // make sure to use the new properties 475 properties = ProjectProperties.load(projectLocation, 476 PropertyType.PROJECT); 477 } catch (Exception e) { 478 AdtPlugin.log(IStatus.ERROR, 479 "Failed to rename properties file to %1$s for project '%s2$'", 480 PropertyType.PROJECT.getFilename(), project.getName()); 481 } 482 } 483 } 484 485 state = new ProjectState(project, properties); 486 sProjectStateMap.put(project, state); 487 488 // try to resolve the target 489 if (AdtPlugin.getDefault().getSdkLoadStatus() == LoadStatus.LOADED) { 490 sCurrentSdk.loadTarget(state); 491 } 492 } 493 494 return state; 495 } 496 } 497 498 /** 499 * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}. 500 */ 501 @Nullable 502 public IAndroidTarget getTarget(IProject project) { 503 if (project == null) { 504 return null; 505 } 506 507 ProjectState state = getProjectState(project); 508 if (state != null) { 509 return state.getTarget(); 510 } 511 512 return null; 513 } 514 515 /** 516 * Loads the {@link IAndroidTarget} for a given project. 517 * <p/>This method will get the target hash string from the project properties, and resolve 518 * it to an {@link IAndroidTarget} object and store it inside the {@link ProjectState}. 519 * @param state the state representing the project to load. 520 * @return the target that was loaded. 521 */ 522 @Nullable 523 public IAndroidTarget loadTarget(ProjectState state) { 524 IAndroidTarget target = null; 525 if (state != null) { 526 String hash = state.getTargetHashString(); 527 if (hash != null) { 528 state.setTarget(target = getTargetFromHashString(hash)); 529 } 530 } 531 532 return target; 533 } 534 535 /** 536 * Checks and loads (if needed) the data for a given target. 537 * <p/> The data is loaded in a separate {@link Job}, and opened editors will be notified 538 * through their implementation of {@link ITargetChangeListener#onTargetLoaded(IAndroidTarget)}. 539 * <p/>An optional project as second parameter can be given to be recompiled once the target 540 * data is finished loading. 541 * <p/>The return value is non-null only if the target data has already been loaded (and in this 542 * case is the status of the load operation) 543 * @param target the target to load. 544 * @param project an optional project to be recompiled when the target data is loaded. 545 * If the target is already loaded, nothing happens. 546 * @return The load status if the target data is already loaded. 547 */ 548 @NonNull 549 public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) { 550 boolean loadData = false; 551 552 synchronized (LOCK) { 553 if (mDontLoadTargetData) { 554 return LoadStatus.FAILED; 555 } 556 557 TargetLoadBundle bundle = mTargetDataStatusMap.get(target); 558 if (bundle == null) { 559 bundle = new TargetLoadBundle(); 560 mTargetDataStatusMap.put(target,bundle); 561 562 // set status to loading 563 bundle.status = LoadStatus.LOADING; 564 565 // add project to bundle 566 if (project != null) { 567 bundle.projectsToReload.add(project); 568 } 569 570 // and set the flag to start the loading below 571 loadData = true; 572 } else if (bundle.status == LoadStatus.LOADING) { 573 // add project to bundle 574 if (project != null) { 575 bundle.projectsToReload.add(project); 576 } 577 578 return bundle.status; 579 } else if (bundle.status == LoadStatus.LOADED || bundle.status == LoadStatus.FAILED) { 580 return bundle.status; 581 } 582 } 583 584 if (loadData) { 585 Job job = new Job(String.format("Loading data for %1$s", target.getFullName())) { 586 @Override 587 protected IStatus run(IProgressMonitor monitor) { 588 AdtPlugin plugin = AdtPlugin.getDefault(); 589 try { 590 IStatus status = new AndroidTargetParser(target).run(monitor); 591 592 IJavaProject[] javaProjectArray = null; 593 594 synchronized (LOCK) { 595 TargetLoadBundle bundle = mTargetDataStatusMap.get(target); 596 597 if (status.getCode() != IStatus.OK) { 598 bundle.status = LoadStatus.FAILED; 599 bundle.projectsToReload.clear(); 600 } else { 601 bundle.status = LoadStatus.LOADED; 602 603 // Prepare the array of project to recompile. 604 // The call is done outside of the synchronized block. 605 javaProjectArray = bundle.projectsToReload.toArray( 606 new IJavaProject[bundle.projectsToReload.size()]); 607 608 // and update the UI of the editors that depend on the target data. 609 plugin.updateTargetListeners(target); 610 } 611 } 612 613 if (javaProjectArray != null) { 614 ProjectHelper.updateProjects(javaProjectArray); 615 } 616 617 return status; 618 } catch (Throwable t) { 619 synchronized (LOCK) { 620 TargetLoadBundle bundle = mTargetDataStatusMap.get(target); 621 bundle.status = LoadStatus.FAILED; 622 } 623 624 AdtPlugin.log(t, "Exception in checkAndLoadTargetData."); //$NON-NLS-1$ 625 return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, 626 String.format( 627 "Parsing Data for %1$s failed", //$NON-NLS-1$ 628 target.hashString()), 629 t); 630 } 631 } 632 }; 633 job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs 634 job.schedule(); 635 } 636 637 // The only way to go through here is when the loading starts through the Job. 638 // Therefore the current status of the target is LOADING. 639 return LoadStatus.LOADING; 640 } 641 642 /** 643 * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}. 644 */ 645 @Nullable 646 public AndroidTargetData getTargetData(IAndroidTarget target) { 647 synchronized (LOCK) { 648 return mTargetDataMap.get(target); 649 } 650 } 651 652 /** 653 * Return the {@link AndroidTargetData} for a given {@link IProject}. 654 */ 655 @Nullable 656 public AndroidTargetData getTargetData(IProject project) { 657 synchronized (LOCK) { 658 IAndroidTarget target = getTarget(project); 659 if (target != null) { 660 return getTargetData(target); 661 } 662 } 663 664 return null; 665 } 666 667 /** 668 * Returns a {@link DexWrapper} object to be used to execute dx commands. If dx.jar was not 669 * loaded properly, then this will return <code>null</code>. 670 */ 671 public DexWrapper getDexWrapper() { 672 return mDexWrapper; 673 } 674 675 /** 676 * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could 677 * be <code>null</code>. 678 */ 679 @Nullable 680 public AvdManager getAvdManager() { 681 return mAvdManager; 682 } 683 684 @Nullable 685 public static AndroidVersion getDeviceVersion(@NonNull IDevice device) { 686 try { 687 Map<String, String> props = device.getProperties(); 688 String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL); 689 if (apiLevel == null) { 690 return null; 691 } 692 693 return new AndroidVersion(Integer.parseInt(apiLevel), 694 props.get((IDevice.PROP_BUILD_CODENAME))); 695 } catch (NumberFormatException e) { 696 return null; 697 } 698 } 699 700 @NonNull 701 public DeviceManager getDeviceManager() { 702 return mDeviceManager; 703 } 704 705 /** Returns the devices provided by the SDK, including user created devices */ 706 @NonNull 707 public List<Device> getDevices() { 708 return mDeviceManager.getDevices(getSdkLocation()); 709 } 710 711 /** 712 * Returns a list of {@link ProjectState} representing projects depending, directly or 713 * indirectly on a given library project. 714 * @param project the library project. 715 * @return a possibly empty list of ProjectState. 716 */ 717 @NonNull 718 public static Set<ProjectState> getMainProjectsFor(IProject project) { 719 synchronized (LOCK) { 720 // first get the project directly depending on this. 721 Set<ProjectState> list = new HashSet<ProjectState>(); 722 723 // loop on all project and see if ProjectState.getLibrary returns a non null 724 // project. 725 for (Entry<IProject, ProjectState> entry : sProjectStateMap.entrySet()) { 726 if (project != entry.getKey()) { 727 LibraryState library = entry.getValue().getLibrary(project); 728 if (library != null) { 729 list.add(entry.getValue()); 730 } 731 } 732 } 733 734 // now look for projects depending on the projects directly depending on the library. 735 HashSet<ProjectState> result = new HashSet<ProjectState>(list); 736 for (ProjectState p : list) { 737 if (p.isLibrary()) { 738 Set<ProjectState> set = getMainProjectsFor(p.getProject()); 739 result.addAll(set); 740 } 741 } 742 743 return result; 744 } 745 } 746 747 /** 748 * Unload the SDK's target data. 749 * 750 * If <var>preventReload</var>, this effect is final until the SDK instance is changed 751 * through {@link #loadSdk(String)}. 752 * 753 * The goal is to unload the targets to be able to replace existing targets with new ones, 754 * before calling {@link #loadSdk(String)} to fully reload the SDK. 755 * 756 * @param preventReload prevent the data from being loaded again for the remaining live of 757 * this {@link Sdk} instance. 758 */ 759 public void unloadTargetData(boolean preventReload) { 760 synchronized (LOCK) { 761 mDontLoadTargetData = preventReload; 762 763 // dispose of the target data. 764 for (AndroidTargetData data : mTargetDataMap.values()) { 765 data.dispose(); 766 } 767 768 mTargetDataMap.clear(); 769 } 770 } 771 772 private Sdk(SdkManager manager, DexWrapper dexWrapper, AvdManager avdManager) { 773 mManager = manager; 774 mDexWrapper = dexWrapper; 775 mAvdManager = avdManager; 776 777 // listen to projects closing 778 GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor(); 779 // need to register the resource event listener first because the project listener 780 // is called back during registration with project opened in the workspace. 781 monitor.addResourceEventListener(mResourceEventListener); 782 monitor.addProjectListener(mProjectListener); 783 monitor.addFileListener(mFileListener, 784 IResourceDelta.CHANGED | IResourceDelta.ADDED | IResourceDelta.REMOVED); 785 786 // pre-compute some paths 787 mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + 788 SdkConstants.OS_SDK_DOCS_FOLDER); 789 790 mDeviceManager = new DeviceManager(AdtPlugin.getDefault()); 791 792 // update whatever ProjectState is already present with new IAndroidTarget objects. 793 synchronized (LOCK) { 794 for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) { 795 entry.getValue().setTarget( 796 getTargetFromHashString(entry.getValue().getTargetHashString())); 797 } 798 } 799 } 800 801 /** 802 * Cleans and unloads the SDK. 803 */ 804 private void dispose() { 805 GlobalProjectMonitor monitor = GlobalProjectMonitor.getMonitor(); 806 monitor.removeProjectListener(mProjectListener); 807 monitor.removeFileListener(mFileListener); 808 monitor.removeResourceEventListener(mResourceEventListener); 809 810 // the IAndroidTarget objects are now obsolete so update the project states. 811 synchronized (LOCK) { 812 for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) { 813 entry.getValue().setTarget(null); 814 } 815 816 // dispose of the target data. 817 for (AndroidTargetData data : mTargetDataMap.values()) { 818 data.dispose(); 819 } 820 821 mTargetDataMap.clear(); 822 } 823 } 824 825 void setTargetData(IAndroidTarget target, AndroidTargetData data) { 826 synchronized (LOCK) { 827 mTargetDataMap.put(target, data); 828 } 829 } 830 831 /** 832 * Returns the URL to the local documentation. 833 * Can return null if no documentation is found in the current SDK. 834 * 835 * @param osDocsPath Path to the documentation folder in the current SDK. 836 * The folder may not actually exist. 837 * @return A file:// URL on the local documentation folder if it exists or null. 838 */ 839 private String getDocumentationBaseUrl(String osDocsPath) { 840 File f = new File(osDocsPath); 841 842 if (f.isDirectory()) { 843 try { 844 // Note: to create a file:// URL, one would typically use something like 845 // f.toURI().toURL().toString(). However this generates a broken path on 846 // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of 847 // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll 848 // do the correct thing manually. 849 850 String path = f.getAbsolutePath(); 851 if (File.separatorChar != '/') { 852 path = path.replace(File.separatorChar, '/'); 853 } 854 855 // For some reason the URL class doesn't add the mandatory "//" after 856 // the "file:" protocol name, so it has to be hacked into the path. 857 URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$ 858 String result = url.toString(); 859 return result; 860 } catch (MalformedURLException e) { 861 // ignore malformed URLs 862 } 863 } 864 865 return null; 866 } 867 868 /** 869 * Delegate listener for project changes. 870 */ 871 private IProjectListener mProjectListener = new IProjectListener() { 872 @Override 873 public void projectClosed(IProject project) { 874 onProjectRemoved(project, false /*deleted*/); 875 } 876 877 @Override 878 public void projectDeleted(IProject project) { 879 onProjectRemoved(project, true /*deleted*/); 880 } 881 882 private void onProjectRemoved(IProject removedProject, boolean deleted) { 883 try { 884 if (removedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 885 return; 886 } 887 } catch (CoreException e) { 888 // this can only happen if the project does not exist or is not open, neither 889 // of which can happen here since we're processing a Project removed/deleted event 890 // which is processed before the project is actually removed/closed. 891 } 892 893 if (DEBUG) { 894 System.out.println(">>> CLOSED: " + removedProject.getName()); 895 } 896 897 // get the target project 898 synchronized (LOCK) { 899 // Don't use getProject() as it could create the ProjectState if it's not 900 // there yet and this is not what we want. We want the current object. 901 // Therefore, direct access to the map. 902 ProjectState removedState = sProjectStateMap.get(removedProject); 903 if (removedState != null) { 904 // 1. clear the layout lib cache associated with this project 905 IAndroidTarget target = removedState.getTarget(); 906 if (target != null) { 907 // get the bridge for the target, and clear the cache for this project. 908 AndroidTargetData data = mTargetDataMap.get(target); 909 if (data != null) { 910 LayoutLibrary layoutLib = data.getLayoutLibrary(); 911 if (layoutLib != null && layoutLib.getStatus() == LoadStatus.LOADED) { 912 layoutLib.clearCaches(removedProject); 913 } 914 } 915 } 916 917 // 2. if the project is a library, make sure to update the 918 // LibraryState for any project referencing it. 919 // Also, record the updated projects that are libraries, to update 920 // projects that depend on them. 921 for (ProjectState projectState : sProjectStateMap.values()) { 922 LibraryState libState = projectState.getLibrary(removedProject); 923 if (libState != null) { 924 // Close the library right away. 925 // This remove links between the LibraryState and the projectState. 926 // This is because in case of a rename of a project, projectClosed and 927 // projectOpened will be called before any other job is run, so we 928 // need to make sure projectOpened is closed with the main project 929 // state up to date. 930 libState.close(); 931 932 // record that this project changed, and in case it's a library 933 // that its parents need to be updated as well. 934 markProject(projectState, projectState.isLibrary()); 935 } 936 } 937 938 // now remove the project for the project map. 939 sProjectStateMap.remove(removedProject); 940 } 941 } 942 943 if (DEBUG) { 944 System.out.println("<<<"); 945 } 946 } 947 948 @Override 949 public void projectOpened(IProject project) { 950 onProjectOpened(project); 951 } 952 953 @Override 954 public void projectOpenedWithWorkspace(IProject project) { 955 // no need to force recompilation when projects are opened with the workspace. 956 onProjectOpened(project); 957 } 958 959 @Override 960 public void allProjectsOpenedWithWorkspace() { 961 // Correct currently open editors 962 fixOpenLegacyEditors(); 963 } 964 965 private void onProjectOpened(final IProject openedProject) { 966 try { 967 if (openedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 968 return; 969 } 970 } catch (CoreException e) { 971 // this can only happen if the project does not exist or is not open, neither 972 // of which can happen here since we're processing a Project opened event. 973 } 974 975 976 ProjectState openedState = getProjectState(openedProject); 977 if (openedState != null) { 978 if (DEBUG) { 979 System.out.println(">>> OPENED: " + openedProject.getName()); 980 } 981 982 synchronized (LOCK) { 983 final boolean isLibrary = openedState.isLibrary(); 984 final boolean hasLibraries = openedState.hasLibraries(); 985 986 if (isLibrary || hasLibraries) { 987 boolean foundLibraries = false; 988 // loop on all the existing project and update them based on this new 989 // project 990 for (ProjectState projectState : sProjectStateMap.values()) { 991 if (projectState != openedState) { 992 // If the project has libraries, check if this project 993 // is a reference. 994 if (hasLibraries) { 995 // ProjectState#needs() both checks if this is a missing library 996 // and updates LibraryState to contains the new values. 997 // This must always be called. 998 LibraryState libState = openedState.needs(projectState); 999 1000 if (libState != null) { 1001 // found a library! Add the main project to the list of 1002 // modified project 1003 foundLibraries = true; 1004 } 1005 } 1006 1007 // if the project is a library check if the other project depend 1008 // on it. 1009 if (isLibrary) { 1010 // ProjectState#needs() both checks if this is a missing library 1011 // and updates LibraryState to contains the new values. 1012 // This must always be called. 1013 LibraryState libState = projectState.needs(openedState); 1014 1015 if (libState != null) { 1016 // There's a dependency! Add the project to the list of 1017 // modified project, but also to a list of projects 1018 // that saw one of its dependencies resolved. 1019 markProject(projectState, projectState.isLibrary()); 1020 } 1021 } 1022 } 1023 } 1024 1025 // if the project has a libraries and we found at least one, we add 1026 // the project to the list of modified project. 1027 // Since we already went through the parent, no need to update them. 1028 if (foundLibraries) { 1029 markProject(openedState, false /*updateParents*/); 1030 } 1031 } 1032 } 1033 1034 // Correct file editor associations. 1035 fixEditorAssociations(openedProject); 1036 1037 if (DEBUG) { 1038 System.out.println("<<<"); 1039 } 1040 } 1041 } 1042 1043 @Override 1044 public void projectRenamed(IProject project, IPath from) { 1045 // we don't actually care about this anymore. 1046 } 1047 }; 1048 1049 /** 1050 * Delegate listener for file changes. 1051 */ 1052 private IFileListener mFileListener = new IFileListener() { 1053 @Override 1054 public void fileChanged(final @NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas, 1055 int kind, @Nullable String extension, int flags) { 1056 if (SdkConstants.FN_PROJECT_PROPERTIES.equals(file.getName()) && 1057 file.getParent() == file.getProject()) { 1058 try { 1059 // reload the content of the project.properties file and update 1060 // the target. 1061 IProject iProject = file.getProject(); 1062 1063 if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 1064 return; 1065 } 1066 1067 ProjectState state = Sdk.getProjectState(iProject); 1068 1069 // get the current target 1070 IAndroidTarget oldTarget = state.getTarget(); 1071 1072 // get the current library flag 1073 boolean wasLibrary = state.isLibrary(); 1074 1075 LibraryDifference diff = state.reloadProperties(); 1076 1077 // load the (possibly new) target. 1078 IAndroidTarget newTarget = loadTarget(state); 1079 1080 // reload the libraries if needed 1081 if (diff.hasDiff()) { 1082 if (diff.added) { 1083 synchronized (LOCK) { 1084 for (ProjectState projectState : sProjectStateMap.values()) { 1085 if (projectState != state) { 1086 // need to call needs to do the libraryState link, 1087 // but no need to look at the result, as we'll compare 1088 // the result of getFullLibraryProjects() 1089 // this is easier to due to indirect dependencies. 1090 state.needs(projectState); 1091 } 1092 } 1093 } 1094 } 1095 1096 markProject(state, wasLibrary || state.isLibrary()); 1097 } 1098 1099 // apply the new target if needed. 1100 if (newTarget != oldTarget) { 1101 IJavaProject javaProject = BaseProjectHelper.getJavaProject( 1102 file.getProject()); 1103 if (javaProject != null) { 1104 ProjectHelper.updateProject(javaProject); 1105 } 1106 1107 // update the editors to reload with the new target 1108 AdtPlugin.getDefault().updateTargetListeners(iProject); 1109 } 1110 } catch (CoreException e) { 1111 // This can't happen as it's only for closed project (or non existing) 1112 // but in that case we can't get a fileChanged on this file. 1113 } 1114 } else if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { 1115 // check if it's an add/remove on a jar files inside libs 1116 if (EXT_JAR.equals(extension) && 1117 file.getProjectRelativePath().segmentCount() == 2 && 1118 file.getParent().getName().equals(SdkConstants.FD_NATIVE_LIBS)) { 1119 // need to update the project and whatever depend on it. 1120 1121 processJarFileChange(file); 1122 } 1123 } 1124 } 1125 1126 private void processJarFileChange(final IFile file) { 1127 try { 1128 IProject iProject = file.getProject(); 1129 1130 if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { 1131 return; 1132 } 1133 1134 List<IJavaProject> projectList = new ArrayList<IJavaProject>(); 1135 IJavaProject javaProject = BaseProjectHelper.getJavaProject(iProject); 1136 if (javaProject != null) { 1137 projectList.add(javaProject); 1138 } 1139 1140 ProjectState state = Sdk.getProjectState(iProject); 1141 1142 if (state != null) { 1143 Collection<ProjectState> parents = state.getFullParentProjects(); 1144 for (ProjectState s : parents) { 1145 javaProject = BaseProjectHelper.getJavaProject(s.getProject()); 1146 if (javaProject != null) { 1147 projectList.add(javaProject); 1148 } 1149 } 1150 1151 ProjectHelper.updateProjects( 1152 projectList.toArray(new IJavaProject[projectList.size()])); 1153 } 1154 } catch (CoreException e) { 1155 // This can't happen as it's only for closed project (or non existing) 1156 // but in that case we can't get a fileChanged on this file. 1157 } 1158 } 1159 }; 1160 1161 /** List of modified projects. This is filled in 1162 * {@link IProjectListener#projectOpened(IProject)}, 1163 * {@link IProjectListener#projectOpenedWithWorkspace(IProject)}, 1164 * {@link IProjectListener#projectClosed(IProject)}, and 1165 * {@link IProjectListener#projectDeleted(IProject)} and processed in 1166 * {@link IResourceEventListener#resourceChangeEventEnd()}. 1167 */ 1168 private final List<ProjectState> mModifiedProjects = new ArrayList<ProjectState>(); 1169 private final List<ProjectState> mModifiedChildProjects = new ArrayList<ProjectState>(); 1170 1171 private void markProject(ProjectState projectState, boolean updateParents) { 1172 if (mModifiedProjects.contains(projectState) == false) { 1173 if (DEBUG) { 1174 System.out.println("\tMARKED: " + projectState.getProject().getName()); 1175 } 1176 mModifiedProjects.add(projectState); 1177 } 1178 1179 // if the project is resolved also add it to this list. 1180 if (updateParents) { 1181 if (mModifiedChildProjects.contains(projectState) == false) { 1182 if (DEBUG) { 1183 System.out.println("\tMARKED(child): " + projectState.getProject().getName()); 1184 } 1185 mModifiedChildProjects.add(projectState); 1186 } 1187 } 1188 } 1189 1190 /** 1191 * Delegate listener for resource changes. This is called before and after any calls to the 1192 * project and file listeners (for a given resource change event). 1193 */ 1194 private IResourceEventListener mResourceEventListener = new IResourceEventListener() { 1195 @Override 1196 public void resourceChangeEventStart() { 1197 mModifiedProjects.clear(); 1198 mModifiedChildProjects.clear(); 1199 } 1200 1201 @Override 1202 public void resourceChangeEventEnd() { 1203 if (mModifiedProjects.size() == 0) { 1204 return; 1205 } 1206 1207 // first make sure all the parents are updated 1208 updateParentProjects(); 1209 1210 // for all modified projects, update their library list 1211 // and gather their IProject 1212 final List<IJavaProject> projectList = new ArrayList<IJavaProject>(); 1213 for (ProjectState state : mModifiedProjects) { 1214 state.updateFullLibraryList(); 1215 projectList.add(JavaCore.create(state.getProject())); 1216 } 1217 1218 Job job = new Job("Android Library Update") { //$NON-NLS-1$ 1219 @Override 1220 protected IStatus run(IProgressMonitor monitor) { 1221 LibraryClasspathContainerInitializer.updateProjects( 1222 projectList.toArray(new IJavaProject[projectList.size()])); 1223 1224 for (IJavaProject javaProject : projectList) { 1225 try { 1226 javaProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, 1227 monitor); 1228 } catch (CoreException e) { 1229 // pass 1230 } 1231 } 1232 return Status.OK_STATUS; 1233 } 1234 }; 1235 job.setPriority(Job.BUILD); 1236 job.schedule(); 1237 } 1238 }; 1239 1240 /** 1241 * Updates all existing projects with a given list of new/updated libraries. 1242 * This loops through all opened projects and check if they depend on any of the given 1243 * library project, and if they do, they are linked together. 1244 */ 1245 private void updateParentProjects() { 1246 if (mModifiedChildProjects.size() == 0) { 1247 return; 1248 } 1249 1250 ArrayList<ProjectState> childProjects = new ArrayList<ProjectState>(mModifiedChildProjects); 1251 mModifiedChildProjects.clear(); 1252 synchronized (LOCK) { 1253 // for each project for which we must update its parent, we loop on the parent 1254 // projects and adds them to the list of modified projects. If they are themselves 1255 // libraries, we add them too. 1256 for (ProjectState state : childProjects) { 1257 if (DEBUG) { 1258 System.out.println(">>> Updating parents of " + state.getProject().getName()); 1259 } 1260 List<ProjectState> parents = state.getParentProjects(); 1261 for (ProjectState parent : parents) { 1262 markProject(parent, parent.isLibrary()); 1263 } 1264 if (DEBUG) { 1265 System.out.println("<<<"); 1266 } 1267 } 1268 } 1269 1270 // done, but there may be parents that are also libraries. Need to update their parents. 1271 updateParentProjects(); 1272 } 1273 1274 /** 1275 * Fix editor associations for the given project, if not already done. 1276 * <p/> 1277 * Eclipse has a per-file setting for which editor should be used for each file 1278 * (see {@link IDE#setDefaultEditor(IFile, String)}). 1279 * We're using this flag to pick between the various XML editors (layout, drawable, etc) 1280 * since they all have the same file name extension. 1281 * <p/> 1282 * Unfortunately, the file setting can be "wrong" for two reasons: 1283 * <ol> 1284 * <li> The editor type was added <b>after</b> a file had been seen by the IDE. 1285 * For example, we added new editors for animations and for drawables around 1286 * ADT 12, but any file seen by ADT in earlier versions will continue to use 1287 * the vanilla Eclipse XML editor instead. 1288 * <li> A bug in ADT 14 and ADT 15 (see issue 21124) meant that files created in new 1289 * folders would end up with wrong editor associations. Even though that bug 1290 * is fixed in ADT 16, the fix only affects new files, it cannot retroactively 1291 * fix editor associations that were set incorrectly by ADT 14 or 15. 1292 * </ol> 1293 * <p/> 1294 * This method attempts to fix the editor bindings retroactively by scanning all the 1295 * resource XML files and resetting the editor associations. 1296 * Since this is a potentially slow operation, this is only done "once"; we use a 1297 * persistent project property to avoid looking repeatedly. In the future if we add 1298 * additional editors, we can rev the scanned version value. 1299 */ 1300 private void fixEditorAssociations(final IProject project) { 1301 QualifiedName KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "editorbinding"); //$NON-NLS-1$ 1302 1303 try { 1304 String value = project.getPersistentProperty(KEY); 1305 int currentVersion = 0; 1306 if (value != null) { 1307 try { 1308 currentVersion = Integer.parseInt(value); 1309 } catch (Exception ingore) { 1310 } 1311 } 1312 1313 // The target version we're comparing to. This must be incremented each time 1314 // we change the processing here so that a new version of the plugin would 1315 // try to fix existing user projects. 1316 final int targetVersion = 2; 1317 1318 if (currentVersion >= targetVersion) { 1319 return; 1320 } 1321 1322 // Set to specific version such that we can rev the version in the future 1323 // to trigger further scanning 1324 project.setPersistentProperty(KEY, Integer.toString(targetVersion)); 1325 1326 // Now update the actual editor associations. 1327 Job job = new Job("Update Android editor bindings") { //$NON-NLS-1$ 1328 @Override 1329 protected IStatus run(IProgressMonitor monitor) { 1330 try { 1331 for (IResource folderResource : project.getFolder(FD_RES).members()) { 1332 if (folderResource instanceof IFolder) { 1333 IFolder folder = (IFolder) folderResource; 1334 1335 for (IResource resource : folder.members()) { 1336 if (resource instanceof IFile && 1337 resource.getName().endsWith(DOT_XML)) { 1338 fixXmlFile((IFile) resource); 1339 } 1340 } 1341 } 1342 } 1343 1344 // TODO change AndroidManifest.xml ID too 1345 1346 } catch (CoreException e) { 1347 AdtPlugin.log(e, null); 1348 } 1349 1350 return Status.OK_STATUS; 1351 } 1352 1353 /** 1354 * Attempt to fix the editor ID for the given /res XML file. 1355 */ 1356 private void fixXmlFile(final IFile file) { 1357 // Fix the default editor ID for this resource. 1358 // This has no effect on currently open editors. 1359 IEditorDescriptor desc = IDE.getDefaultEditor(file); 1360 1361 if (desc == null || !CommonXmlEditor.ID.equals(desc.getId())) { 1362 IDE.setDefaultEditor(file, CommonXmlEditor.ID); 1363 } 1364 } 1365 }; 1366 job.setPriority(Job.BUILD); 1367 job.schedule(); 1368 } catch (CoreException e) { 1369 AdtPlugin.log(e, null); 1370 } 1371 } 1372 1373 /** 1374 * Tries to fix all currently open Android legacy editors. 1375 * <p/> 1376 * If an editor is found to match one of the legacy ids, we'll try to close it. 1377 * If that succeeds, we try to reopen it using the new common editor ID. 1378 * <p/> 1379 * This method must be run from the UI thread. 1380 */ 1381 private void fixOpenLegacyEditors() { 1382 1383 AdtPlugin adt = AdtPlugin.getDefault(); 1384 if (adt == null) { 1385 return; 1386 } 1387 1388 final IPreferenceStore store = adt.getPreferenceStore(); 1389 int currentValue = store.getInt(AdtPrefs.PREFS_FIX_LEGACY_EDITORS); 1390 // The target version we're comparing to. This must be incremented each time 1391 // we change the processing here so that a new version of the plugin would 1392 // try to fix existing editors. 1393 final int targetValue = 1; 1394 1395 if (currentValue >= targetValue) { 1396 return; 1397 } 1398 1399 // To be able to close and open editors we need to make sure this is done 1400 // in the UI thread, which this isn't invoked from. 1401 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { 1402 @Override 1403 public void run() { 1404 HashSet<String> legacyIds = 1405 new HashSet<String>(Arrays.asList(CommonXmlEditor.LEGACY_EDITOR_IDS)); 1406 1407 for (IWorkbenchWindow win : PlatformUI.getWorkbench().getWorkbenchWindows()) { 1408 for (IWorkbenchPage page : win.getPages()) { 1409 for (IEditorReference ref : page.getEditorReferences()) { 1410 try { 1411 IEditorInput input = ref.getEditorInput(); 1412 if (input instanceof IFileEditorInput) { 1413 IFile file = ((IFileEditorInput)input).getFile(); 1414 IEditorPart part = ref.getEditor(true /*restore*/); 1415 if (part != null) { 1416 IWorkbenchPartSite site = part.getSite(); 1417 if (site != null) { 1418 String id = site.getId(); 1419 if (legacyIds.contains(id)) { 1420 // This editor matches one of legacy editor IDs. 1421 fixEditor(page, part, input, file, id); 1422 } 1423 } 1424 } 1425 } 1426 } catch (Exception e) { 1427 // ignore 1428 } 1429 } 1430 } 1431 } 1432 1433 // Remember that we managed to do fix all editors 1434 store.setValue(AdtPrefs.PREFS_FIX_LEGACY_EDITORS, targetValue); 1435 } 1436 1437 private void fixEditor( 1438 IWorkbenchPage page, 1439 IEditorPart part, 1440 IEditorInput input, 1441 IFile file, 1442 String id) { 1443 IDE.setDefaultEditor(file, CommonXmlEditor.ID); 1444 1445 boolean ok = page.closeEditor(part, true /*save*/); 1446 1447 AdtPlugin.log(IStatus.INFO, 1448 "Closed legacy editor ID %s for %s: %s", //$NON-NLS-1$ 1449 id, 1450 file.getFullPath(), 1451 ok ? "Success" : "Failed");//$NON-NLS-1$ //$NON-NLS-2$ 1452 1453 if (ok) { 1454 // Try to reopen it with the new ID 1455 try { 1456 page.openEditor(input, CommonXmlEditor.ID); 1457 } catch (PartInitException e) { 1458 AdtPlugin.log(e, 1459 "Failed to reopen %s", //$NON-NLS-1$ 1460 file.getFullPath()); 1461 } 1462 } 1463 } 1464 }); 1465 } 1466 } 1467