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