1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt; 18 19 import com.android.ddmuilib.console.DdmConsole; 20 import com.android.ddmuilib.console.IDdmConsole; 21 import com.android.ide.common.log.ILogger; 22 import com.android.ide.common.resources.ResourceFile; 23 import com.android.ide.common.resources.ResourceFolder; 24 import com.android.ide.common.sdk.LoadStatus; 25 import com.android.ide.eclipse.adt.internal.VersionCheck; 26 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 27 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 28 import com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditor; 29 import com.android.ide.eclipse.adt.internal.editors.color.ColorEditor; 30 import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditor; 31 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; 32 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder; 33 import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditor; 34 import com.android.ide.eclipse.adt.internal.editors.resources.ResourcesEditor; 35 import com.android.ide.eclipse.adt.internal.editors.xml.XmlEditor; 36 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 37 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 38 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; 39 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 40 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 41 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; 42 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; 43 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 44 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 45 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 46 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; 47 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; 48 import com.android.ide.eclipse.ddms.DdmsPlugin; 49 import com.android.io.StreamException; 50 import com.android.resources.ResourceFolderType; 51 import com.android.sdklib.IAndroidTarget; 52 import com.android.sdklib.SdkConstants; 53 54 import org.eclipse.core.resources.IFile; 55 import org.eclipse.core.resources.IFolder; 56 import org.eclipse.core.resources.IMarkerDelta; 57 import org.eclipse.core.resources.IProject; 58 import org.eclipse.core.resources.IResourceDelta; 59 import org.eclipse.core.resources.IWorkspace; 60 import org.eclipse.core.resources.ResourcesPlugin; 61 import org.eclipse.core.runtime.CoreException; 62 import org.eclipse.core.runtime.IProgressMonitor; 63 import org.eclipse.core.runtime.IStatus; 64 import org.eclipse.core.runtime.QualifiedName; 65 import org.eclipse.core.runtime.Status; 66 import org.eclipse.core.runtime.SubMonitor; 67 import org.eclipse.core.runtime.jobs.Job; 68 import org.eclipse.jdt.core.IJavaElement; 69 import org.eclipse.jdt.core.IJavaProject; 70 import org.eclipse.jdt.core.JavaCore; 71 import org.eclipse.jdt.ui.JavaUI; 72 import org.eclipse.jface.dialogs.MessageDialog; 73 import org.eclipse.jface.preference.IPreferenceStore; 74 import org.eclipse.jface.resource.ImageDescriptor; 75 import org.eclipse.jface.text.IRegion; 76 import org.eclipse.jface.util.IPropertyChangeListener; 77 import org.eclipse.jface.util.PropertyChangeEvent; 78 import org.eclipse.swt.graphics.Color; 79 import org.eclipse.swt.graphics.Image; 80 import org.eclipse.swt.widgets.Display; 81 import org.eclipse.swt.widgets.Shell; 82 import org.eclipse.ui.IEditorDescriptor; 83 import org.eclipse.ui.IEditorPart; 84 import org.eclipse.ui.IWorkbench; 85 import org.eclipse.ui.IWorkbenchPage; 86 import org.eclipse.ui.IWorkbenchWindow; 87 import org.eclipse.ui.PartInitException; 88 import org.eclipse.ui.PlatformUI; 89 import org.eclipse.ui.browser.IWebBrowser; 90 import org.eclipse.ui.browser.IWorkbenchBrowserSupport; 91 import org.eclipse.ui.console.ConsolePlugin; 92 import org.eclipse.ui.console.IConsole; 93 import org.eclipse.ui.console.IConsoleConstants; 94 import org.eclipse.ui.console.MessageConsole; 95 import org.eclipse.ui.console.MessageConsoleStream; 96 import org.eclipse.ui.ide.IDE; 97 import org.eclipse.ui.part.FileEditorInput; 98 import org.eclipse.ui.plugin.AbstractUIPlugin; 99 import org.osgi.framework.Bundle; 100 import org.osgi.framework.BundleContext; 101 102 import java.io.BufferedInputStream; 103 import java.io.BufferedReader; 104 import java.io.File; 105 import java.io.FileNotFoundException; 106 import java.io.FileReader; 107 import java.io.FileWriter; 108 import java.io.IOException; 109 import java.io.InputStream; 110 import java.io.InputStreamReader; 111 import java.io.OutputStream; 112 import java.io.Reader; 113 import java.net.MalformedURLException; 114 import java.net.URL; 115 import java.util.ArrayList; 116 import java.util.Arrays; 117 import java.util.List; 118 119 /** 120 * The activator class controls the plug-in life cycle 121 */ 122 public class AdtPlugin extends AbstractUIPlugin implements ILogger { 123 /** 124 * Temporary logging code to help track down 125 * http://code.google.com/p/android/issues/detail?id=15003 126 * 127 * Deactivated right now. 128 * TODO remove this and associated logging code once we're done with issue 15003. 129 */ 130 public static final boolean DEBUG_XML_FILE_INIT = false; 131 132 /** The plug-in ID */ 133 public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$ 134 135 /** singleton instance */ 136 private static AdtPlugin sPlugin; 137 138 private static Image sAndroidLogo; 139 private static ImageDescriptor sAndroidLogoDesc; 140 141 /** The global android console */ 142 private MessageConsole mAndroidConsole; 143 144 /** Stream to write in the android console */ 145 private MessageConsoleStream mAndroidConsoleStream; 146 147 /** Stream to write error messages to the android console */ 148 private MessageConsoleStream mAndroidConsoleErrorStream; 149 150 /** Color used in the error console */ 151 private Color mRed; 152 153 /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */ 154 private LoadStatus mSdkIsLoaded = LoadStatus.LOADING; 155 /** Project to update once the SDK is loaded. 156 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 157 private final ArrayList<IJavaProject> mPostLoadProjectsToResolve = 158 new ArrayList<IJavaProject>(); 159 /** Project to check validity of cache vs actual once the SDK is loaded. 160 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 161 private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>(); 162 163 private GlobalProjectMonitor mResourceMonitor; 164 private ArrayList<ITargetChangeListener> mTargetChangeListeners = 165 new ArrayList<ITargetChangeListener>(); 166 167 protected boolean mSdkIsLoading; 168 169 /** 170 * An error handler for checkSdkLocationAndId() that will handle the generated error 171 * or warning message. Each method must return a boolean that will in turn be returned by 172 * checkSdkLocationAndId. 173 */ 174 public static abstract class CheckSdkErrorHandler { 175 /** Handle an error message during sdk location check. Returns whatever 176 * checkSdkLocationAndId() should returns. 177 */ 178 public abstract boolean handleError(String message); 179 180 /** Handle a warning message during sdk location check. Returns whatever 181 * checkSdkLocationAndId() should returns. 182 */ 183 public abstract boolean handleWarning(String message); 184 } 185 186 /** 187 * The constructor 188 */ 189 public AdtPlugin() { 190 sPlugin = this; 191 } 192 193 /* 194 * (non-Javadoc) 195 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 196 */ 197 @Override 198 public void start(BundleContext context) throws Exception { 199 super.start(context); 200 201 Display display = getDisplay(); 202 203 // set the default android console. 204 mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$ 205 ConsolePlugin.getDefault().getConsoleManager().addConsoles( 206 new IConsole[] { mAndroidConsole }); 207 208 // get the stream to write in the android console. 209 mAndroidConsoleStream = mAndroidConsole.newMessageStream(); 210 mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream(); 211 mRed = new Color(display, 0xFF, 0x00, 0x00); 212 213 // because this can be run, in some cases, by a non ui thread, and because 214 // changing the console properties update the ui, we need to make this change 215 // in the ui thread. 216 display.asyncExec(new Runnable() { 217 public void run() { 218 mAndroidConsoleErrorStream.setColor(mRed); 219 } 220 }); 221 222 // set up the ddms console to use this objects 223 DdmConsole.setConsole(new IDdmConsole() { 224 public void printErrorToConsole(String message) { 225 AdtPlugin.printErrorToConsole((String)null, message); 226 } 227 public void printErrorToConsole(String[] messages) { 228 AdtPlugin.printErrorToConsole((String)null, (Object[])messages); 229 } 230 public void printToConsole(String message) { 231 AdtPlugin.printToConsole((String)null, message); 232 } 233 public void printToConsole(String[] messages) { 234 AdtPlugin.printToConsole((String)null, (Object[])messages); 235 } 236 }); 237 238 // get the eclipse store 239 IPreferenceStore eclipseStore = getPreferenceStore(); 240 AdtPrefs.init(eclipseStore); 241 242 // set the listener for the preference change 243 eclipseStore.addPropertyChangeListener(new IPropertyChangeListener() { 244 public void propertyChange(PropertyChangeEvent event) { 245 // load the new preferences 246 AdtPrefs.getPrefs().loadValues(event); 247 248 // if the SDK changed, we have to do some extra work 249 if (AdtPrefs.PREFS_SDK_DIR.equals(event.getProperty())) { 250 251 // finally restart adb, in case it's a different version 252 DdmsPlugin.setToolsLocation(getOsAbsoluteAdb(), true /* startAdb */, 253 getOsAbsoluteHprofConv(), getOsAbsoluteTraceview()); 254 255 // get the SDK location and build id. 256 if (checkSdkLocationAndId()) { 257 // if sdk if valid, reparse it 258 259 reparseSdk(); 260 } 261 } 262 } 263 }); 264 265 // load preferences. 266 AdtPrefs.getPrefs().loadValues(null /*event*/); 267 268 // initialize editors 269 startEditors(); 270 271 // Listen on resource file edits for updates to file inclusion 272 IncludeFinder.start(); 273 274 // Parse the SDK content. 275 // This is deferred in separate jobs to avoid blocking the bundle start. 276 final boolean isSdkLocationValid = checkSdkLocationAndId(); 277 if (isSdkLocationValid) { 278 // parse the SDK resources. 279 // Wait 2 seconds before starting the job. This leaves some time to the 280 // other bundles to initialize. 281 parseSdkContent(2000 /*milliseconds*/); 282 } 283 } 284 285 /* 286 * (non-Javadoc) 287 * 288 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 289 */ 290 @Override 291 public void stop(BundleContext context) throws Exception { 292 super.stop(context); 293 294 stopEditors(); 295 IncludeFinder.stop(); 296 297 mRed.dispose(); 298 synchronized (AdtPlugin.class) { 299 sPlugin = null; 300 } 301 } 302 303 /** 304 * Returns the shared instance 305 * 306 * @return the shared instance 307 */ 308 public static synchronized AdtPlugin getDefault() { 309 return sPlugin; 310 } 311 312 public static Display getDisplay() { 313 IWorkbench bench = null; 314 synchronized (AdtPlugin.class) { 315 bench = sPlugin.getWorkbench(); 316 } 317 318 if (bench != null) { 319 return bench.getDisplay(); 320 } 321 return null; 322 } 323 324 /** Returns the adb path relative to the sdk folder */ 325 public static String getOsRelativeAdb() { 326 return SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_ADB; 327 } 328 329 /** Returns the zipalign path relative to the sdk folder */ 330 public static String getOsRelativeZipAlign() { 331 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ZIPALIGN; 332 } 333 334 /** Returns the emulator path relative to the sdk folder */ 335 public static String getOsRelativeEmulator() { 336 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR; 337 } 338 339 /** Returns the adb path relative to the sdk folder */ 340 public static String getOsRelativeProguard() { 341 return SdkConstants.OS_SDK_TOOLS_PROGUARD_BIN_FOLDER + SdkConstants.FN_PROGUARD; 342 } 343 344 /** Returns the absolute adb path */ 345 public static String getOsAbsoluteAdb() { 346 return getOsSdkFolder() + getOsRelativeAdb(); 347 } 348 349 /** Returns the absolute zipalign path */ 350 public static String getOsAbsoluteZipAlign() { 351 return getOsSdkFolder() + getOsRelativeZipAlign(); 352 } 353 354 /** Returns the absolute traceview path */ 355 public static String getOsAbsoluteTraceview() { 356 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + 357 AdtConstants.FN_TRACEVIEW; 358 } 359 360 /** Returns the absolute emulator path */ 361 public static String getOsAbsoluteEmulator() { 362 return getOsSdkFolder() + getOsRelativeEmulator(); 363 } 364 365 public static String getOsAbsoluteHprofConv() { 366 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + 367 AdtConstants.FN_HPROF_CONV; 368 } 369 370 /** Returns the absolute proguard path */ 371 public static String getOsAbsoluteProguard() { 372 return getOsSdkFolder() + getOsRelativeProguard(); 373 } 374 375 /** 376 * Returns a Url file path to the javaDoc folder. 377 */ 378 public static String getUrlDoc() { 379 return ProjectHelper.getJavaDocPath( 380 getOsSdkFolder() + AdtConstants.WS_JAVADOC_FOLDER_LEAF); 381 } 382 383 /** 384 * Returns the SDK folder. 385 * Guaranteed to be terminated by a platform-specific path separator. 386 */ 387 public static synchronized String getOsSdkFolder() { 388 if (sPlugin == null) { 389 return null; 390 } 391 392 return AdtPrefs.getPrefs().getOsSdkFolder(); 393 } 394 395 public static String getOsSdkToolsFolder() { 396 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER; 397 } 398 399 /** 400 * Returns an image descriptor for the image file at the given 401 * plug-in relative path 402 * 403 * @param path the path 404 * @return the image descriptor 405 */ 406 public static ImageDescriptor getImageDescriptor(String path) { 407 return imageDescriptorFromPlugin(PLUGIN_ID, path); 408 } 409 410 /** 411 * Reads the contents of an {@link IFile} and return it as a String 412 * 413 * @param file the file to be read 414 * @return the String read from the file, or null if there was an error 415 */ 416 public static String readFile(IFile file) { 417 InputStream contents = null; 418 try { 419 contents = file.getContents(); 420 String charset = file.getCharset(); 421 return readFile(new InputStreamReader(contents, charset)); 422 } catch (CoreException e) { 423 // pass -- ignore files we can't read 424 } catch (IOException e) { 425 // pass -- ignore files we can't read. 426 427 // Note that IFile.getContents() indicates it throws a CoreException but 428 // experience shows that if the file does not exists it really throws 429 // IOException. 430 // New InputStreamReader() throws UnsupportedEncodingException 431 // which is handled by this IOException catch. 432 433 } finally { 434 try { 435 if (contents != null) { 436 contents.close(); 437 } 438 } catch (IOException e) { 439 // ignore 440 } 441 } 442 443 return null; 444 } 445 446 /** 447 * Reads the contents of an {@link File} and return it as a String 448 * 449 * @param file the file to be read 450 * @return the String read from the file, or null if there was an error 451 */ 452 public static String readFile(File file) { 453 try { 454 return readFile(new FileReader(file)); 455 } catch (FileNotFoundException e) { 456 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 457 } 458 459 return null; 460 } 461 462 /** 463 * Writes the given content out to the given {@link File}. The file will be deleted if 464 * it already exists. 465 * 466 * @param file the target file 467 * @param content the content to be written into the file 468 */ 469 public static void writeFile(File file, String content) { 470 if (file.exists()) { 471 file.delete(); 472 } 473 FileWriter fw = null; 474 try { 475 fw = new FileWriter(file); 476 fw.write(content); 477 } catch (IOException e) { 478 AdtPlugin.log(e, null); 479 } finally { 480 if (fw != null) { 481 try { 482 fw.close(); 483 } catch (IOException e) { 484 AdtPlugin.log(e, null); 485 } 486 } 487 } 488 } 489 490 /** 491 * Returns true iff the given file contains the given String. 492 * 493 * @param file the file to look for the string in 494 * @param string the string to be searched for 495 * @return true if the file is found and contains the given string anywhere within it 496 */ 497 public static boolean fileContains(IFile file, String string) { 498 InputStream contents = null; 499 try { 500 contents = file.getContents(); 501 String charset = file.getCharset(); 502 return streamContains(new InputStreamReader(contents, charset), string); 503 } catch (Exception e) { 504 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 505 } 506 507 return false; 508 } 509 510 /** 511 * Returns true iff the given file contains the given String. 512 * 513 * @param file the file to look for the string in 514 * @param string the string to be searched for 515 * @return true if the file is found and contains the given string anywhere within it 516 */ 517 public static boolean fileContains(File file, String string) { 518 try { 519 return streamContains(new FileReader(file), string); 520 } catch (Exception e) { 521 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 522 } 523 524 return false; 525 } 526 527 /** 528 * Returns true iff the given input stream contains the given String. 529 * 530 * @param r the stream to look for the string in 531 * @param string the string to be searched for 532 * @return true if the file is found and contains the given string anywhere within it 533 */ 534 public static boolean streamContains(Reader r, String string) { 535 if (string.length() == 0) { 536 return true; 537 } 538 539 PushbackReader reader = null; 540 try { 541 reader = new PushbackReader(r, string.length()); 542 char first = string.charAt(0); 543 while (true) { 544 int c = reader.read(); 545 if (c == -1) { 546 return false; 547 } else if (c == first) { 548 boolean matches = true; 549 for (int i = 1; i < string.length(); i++) { 550 c = reader.read(); 551 if (c == -1) { 552 return false; 553 } else if (string.charAt(i) != (char)c) { 554 matches = false; 555 // Back up the characters that did not match 556 reader.backup(i-1); 557 break; 558 } 559 } 560 if (matches) { 561 return true; 562 } 563 } 564 } 565 } catch (Exception e) { 566 AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$ 567 } finally { 568 try { 569 if (reader != null) { 570 reader.close(); 571 } 572 } catch (IOException e) { 573 AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$ 574 } 575 } 576 577 return false; 578 579 } 580 581 /** 582 * A special reader that allows backing up in the input (up to a predefined maximum 583 * number of characters) 584 * <p> 585 * NOTE: This class ONLY works with the {@link #read()} method!! 586 */ 587 private static class PushbackReader extends BufferedReader { 588 /** 589 * Rolling/circular buffer. Can be a char rather than int since we never store EOF 590 * in it. 591 */ 592 private char[] mStorage; 593 594 /** Points to the head of the queue. When equal to the tail, the queue is empty. */ 595 private int mHead; 596 597 /** 598 * Points to the tail of the queue. This will move with each read of the actual 599 * wrapped reader, and the characters previous to it in the circular buffer are 600 * the most recently read characters. 601 */ 602 private int mTail; 603 604 /** 605 * Creates a new reader with a given maximum number of backup characters 606 * 607 * @param reader the reader to wrap 608 * @param max the maximum number of characters to allow rollback for 609 */ 610 public PushbackReader(Reader reader, int max) { 611 super(reader); 612 mStorage = new char[max + 1]; 613 } 614 615 @Override 616 public int read() throws IOException { 617 // Have we backed up? If so we should serve characters 618 // from the storage 619 if (mHead != mTail) { 620 char c = mStorage[mHead]; 621 mHead = (mHead + 1) % mStorage.length; 622 return c; 623 } 624 assert mHead == mTail; 625 626 // No backup -- read the next character, but stash it into storage 627 // as well such that we can retrieve it if we must. 628 int c = super.read(); 629 mStorage[mHead] = (char) c; 630 mHead = mTail = (mHead + 1) % mStorage.length; 631 return c; 632 } 633 634 /** 635 * Backs up the reader a given number of characters. The next N reads will yield 636 * the N most recently read characters prior to this backup. 637 * 638 * @param n the number of characters to be backed up 639 */ 640 public void backup(int n) { 641 if (n >= mStorage.length) { 642 throw new IllegalArgumentException("Exceeded backup limit"); 643 } 644 assert n < mStorage.length; 645 mHead -= n; 646 if (mHead < 0) { 647 mHead += mStorage.length; 648 } 649 } 650 } 651 652 /** 653 * Reads the contents of a {@link ResourceFile} and returns it as a String 654 * 655 * @param file the file to be read 656 * @return the contents as a String, or null if reading failed 657 */ 658 public static String readFile(ResourceFile file) { 659 InputStream contents = null; 660 try { 661 contents = file.getFile().getContents(); 662 return readFile(new InputStreamReader(contents)); 663 } catch (StreamException e) { 664 // pass -- ignore files we can't read 665 } finally { 666 try { 667 if (contents != null) { 668 contents.close(); 669 } 670 } catch (IOException e) { 671 AdtPlugin.log(e, "Can't read layout file"); //$NON-NLS-1$ 672 } 673 } 674 675 return null; 676 } 677 678 /** 679 * Reads the contents of an {@link InputStreamReader} and return it as a String 680 * 681 * @param inputStream the input stream to be read from 682 * @return the String read from the stream, or null if there was an error 683 */ 684 public static String readFile(Reader inputStream) { 685 BufferedReader reader = null; 686 try { 687 reader = new BufferedReader(inputStream); 688 StringBuilder sb = new StringBuilder(2000); 689 while (true) { 690 int c = reader.read(); 691 if (c == -1) { 692 return sb.toString(); 693 } else { 694 sb.append((char)c); 695 } 696 } 697 } catch (IOException e) { 698 // pass -- ignore files we can't read 699 } finally { 700 try { 701 if (reader != null) { 702 reader.close(); 703 } 704 } catch (IOException e) { 705 AdtPlugin.log(e, "Can't read input stream"); //$NON-NLS-1$ 706 } 707 } 708 709 return null; 710 } 711 712 /** 713 * Reads and returns the content of a text file embedded in the plugin jar 714 * file. 715 * @param filepath the file path to the text file 716 * @return null if the file could not be read 717 */ 718 public static String readEmbeddedTextFile(String filepath) { 719 try { 720 InputStream is = readEmbeddedFileAsStream(filepath); 721 if (is != null) { 722 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 723 724 String line; 725 StringBuilder total = new StringBuilder(reader.readLine()); 726 while ((line = reader.readLine()) != null) { 727 total.append('\n'); 728 total.append(line); 729 } 730 731 return total.toString(); 732 } 733 } catch (IOException e) { 734 // we'll just return null 735 AdtPlugin.log(e, "Failed to read text file '%s'", filepath); //$NON-NLS-1$ 736 } 737 738 return null; 739 } 740 741 /** 742 * Reads and returns the content of a binary file embedded in the plugin jar 743 * file. 744 * @param filepath the file path to the text file 745 * @return null if the file could not be read 746 */ 747 public static byte[] readEmbeddedFile(String filepath) { 748 try { 749 InputStream is = readEmbeddedFileAsStream(filepath); 750 if (is != null) { 751 // create a buffered reader to facilitate reading. 752 BufferedInputStream stream = new BufferedInputStream(is); 753 754 // get the size to read. 755 int avail = stream.available(); 756 757 // create the buffer and reads it. 758 byte[] buffer = new byte[avail]; 759 stream.read(buffer); 760 761 // and return. 762 return buffer; 763 } 764 } catch (IOException e) { 765 // we'll just return null;. 766 AdtPlugin.log(e, "Failed to read binary file '%s'", filepath); //$NON-NLS-1$ 767 } 768 769 return null; 770 } 771 772 /** 773 * Reads and returns the content of a binary file embedded in the plugin jar 774 * file. 775 * @param filepath the file path to the text file 776 * @return null if the file could not be read 777 */ 778 public static InputStream readEmbeddedFileAsStream(String filepath) { 779 // attempt to read an embedded file 780 try { 781 URL url = getEmbeddedFileUrl(AdtConstants.WS_SEP + filepath); 782 if (url != null) { 783 return url.openStream(); 784 } 785 } catch (MalformedURLException e) { 786 // we'll just return null. 787 AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$ 788 } catch (IOException e) { 789 // we'll just return null;. 790 AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$ 791 } 792 793 return null; 794 } 795 796 /** 797 * Returns the URL of a binary file embedded in the plugin jar file. 798 * @param filepath the file path to the text file 799 * @return null if the file was not found. 800 */ 801 public static URL getEmbeddedFileUrl(String filepath) { 802 Bundle bundle = null; 803 synchronized (AdtPlugin.class) { 804 if (sPlugin != null) { 805 bundle = sPlugin.getBundle(); 806 } else { 807 AdtPlugin.log(IStatus.WARNING, "ADT Plugin is missing"); //$NON-NLS-1$ 808 return null; 809 } 810 } 811 812 // attempt to get a file to one of the template. 813 String path = filepath; 814 if (!path.startsWith(AdtConstants.WS_SEP)) { 815 path = AdtConstants.WS_SEP + path; 816 } 817 818 URL url = bundle.getEntry(path); 819 820 if (url == null) { 821 AdtPlugin.log(IStatus.INFO, "Bundle file URL not found at path '%s'", path); //$NON-NLS-1$ 822 } 823 824 return url; 825 } 826 827 /** 828 * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread, 829 * therefore this method can be called from any thread. 830 * @param title The title of the dialog box 831 * @param message The error message 832 */ 833 public final static void displayError(final String title, final String message) { 834 // get the current Display 835 final Display display = getDisplay(); 836 837 // dialog box only run in ui thread.. 838 display.asyncExec(new Runnable() { 839 public void run() { 840 Shell shell = display.getActiveShell(); 841 MessageDialog.openError(shell, title, message); 842 } 843 }); 844 } 845 846 /** 847 * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread, 848 * therefore this method can be called from any thread. 849 * @param title The title of the dialog box 850 * @param message The warning message 851 */ 852 public final static void displayWarning(final String title, final String message) { 853 // get the current Display 854 final Display display = getDisplay(); 855 856 // dialog box only run in ui thread.. 857 display.asyncExec(new Runnable() { 858 public void run() { 859 Shell shell = display.getActiveShell(); 860 MessageDialog.openWarning(shell, title, message); 861 } 862 }); 863 } 864 865 /** 866 * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread, 867 * therefore this message can be called from any thread. 868 * @param title The title of the dialog box 869 * @param message The error message 870 * @return true if OK was clicked. 871 */ 872 public final static boolean displayPrompt(final String title, final String message) { 873 // get the current Display and Shell 874 final Display display = getDisplay(); 875 876 // we need to ask the user what he wants to do. 877 final boolean[] result = new boolean[1]; 878 display.syncExec(new Runnable() { 879 public void run() { 880 Shell shell = display.getActiveShell(); 881 result[0] = MessageDialog.openQuestion(shell, title, message); 882 } 883 }); 884 return result[0]; 885 } 886 887 /** 888 * Logs a message to the default Eclipse log. 889 * 890 * @param severity The severity code. Valid values are: {@link IStatus#OK}, 891 * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or 892 * {@link IStatus#CANCEL}. 893 * @param format The format string, like for {@link String#format(String, Object...)}. 894 * @param args The arguments for the format string, like for 895 * {@link String#format(String, Object...)}. 896 */ 897 public static void log(int severity, String format, Object ... args) { 898 if (format == null) { 899 return; 900 } 901 902 String message = String.format(format, args); 903 Status status = new Status(severity, PLUGIN_ID, message); 904 905 if (getDefault() != null) { 906 getDefault().getLog().log(status); 907 } else { 908 // During UnitTests, we generally don't have a plugin object. It's ok 909 // to log to stdout or stderr in this case. 910 (severity < IStatus.ERROR ? System.out : System.err).println(status.toString()); 911 } 912 } 913 914 /** 915 * Logs an exception to the default Eclipse log. 916 * <p/> 917 * The status severity is always set to ERROR. 918 * 919 * @param exception the exception to log. 920 * @param format The format string, like for {@link String#format(String, Object...)}. 921 * @param args The arguments for the format string, like for 922 * {@link String#format(String, Object...)}. 923 */ 924 public static void log(Throwable exception, String format, Object ... args) { 925 String message = null; 926 if (format != null) { 927 message = String.format(format, args); 928 } else { 929 message = ""; 930 } 931 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 932 933 if (getDefault() != null) { 934 getDefault().getLog().log(status); 935 } else { 936 // During UnitTests, we generally don't have a plugin object. It's ok 937 // to log to stderr in this case. 938 System.err.println(status.toString()); 939 } 940 } 941 942 /** 943 * This is a mix between log(Throwable) and printErrorToConsole. 944 * <p/> 945 * This logs the exception with an ERROR severity and the given printf-like format message. 946 * The same message is then printed on the Android error console with the associated tag. 947 * 948 * @param exception the exception to log. 949 * @param format The format string, like for {@link String#format(String, Object...)}. 950 * @param args The arguments for the format string, like for 951 * {@link String#format(String, Object...)}. 952 */ 953 public static synchronized void logAndPrintError(Throwable exception, String tag, 954 String format, Object ... args) { 955 if (sPlugin != null) { 956 String message = String.format(format, args); 957 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 958 getDefault().getLog().log(status); 959 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message); 960 showAndroidConsole(); 961 } 962 } 963 964 /** 965 * Prints one or more error message to the android console. 966 * @param tag A tag to be associated with the message. Can be null. 967 * @param objects the objects to print through their <code>toString</code> method. 968 */ 969 public static synchronized void printErrorToConsole(String tag, Object... objects) { 970 if (sPlugin != null) { 971 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects); 972 973 showAndroidConsole(); 974 } 975 } 976 977 /** 978 * Prints one or more error message to the android console. 979 * @param objects the objects to print through their <code>toString</code> method. 980 */ 981 public static void printErrorToConsole(Object... objects) { 982 printErrorToConsole((String)null, objects); 983 } 984 985 /** 986 * Prints one or more error message to the android console. 987 * @param project The project to which the message is associated. Can be null. 988 * @param objects the objects to print through their <code>toString</code> method. 989 */ 990 public static void printErrorToConsole(IProject project, Object... objects) { 991 String tag = project != null ? project.getName() : null; 992 printErrorToConsole(tag, objects); 993 } 994 995 /** 996 * Prints one or more build messages to the android console, filtered by Build output verbosity. 997 * @param level {@link BuildVerbosity} level of the message. 998 * @param project The project to which the message is associated. Can be null. 999 * @param objects the objects to print through their <code>toString</code> method. 1000 * @see BuildVerbosity#ALWAYS 1001 * @see BuildVerbosity#NORMAL 1002 * @see BuildVerbosity#VERBOSE 1003 */ 1004 public static synchronized void printBuildToConsole(BuildVerbosity level, IProject project, 1005 Object... objects) { 1006 if (sPlugin != null) { 1007 if (level.getLevel() <= AdtPrefs.getPrefs().getBuildVerbosity().getLevel()) { 1008 String tag = project != null ? project.getName() : null; 1009 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 1010 } 1011 } 1012 } 1013 1014 /** 1015 * Prints one or more message to the android console. 1016 * @param tag The tag to be associated with the message. Can be null. 1017 * @param objects the objects to print through their <code>toString</code> method. 1018 */ 1019 public static synchronized void printToConsole(String tag, Object... objects) { 1020 if (sPlugin != null) { 1021 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 1022 } 1023 } 1024 1025 /** 1026 * Prints one or more message to the android console. 1027 * @param project The project to which the message is associated. Can be null. 1028 * @param objects the objects to print through their <code>toString</code> method. 1029 */ 1030 public static void printToConsole(IProject project, Object... objects) { 1031 String tag = project != null ? project.getName() : null; 1032 printToConsole(tag, objects); 1033 } 1034 1035 /** Force the display of the android console */ 1036 public static void showAndroidConsole() { 1037 // first make sure the console is in the workbench 1038 EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true); 1039 1040 // now make sure it's not docked. 1041 ConsolePlugin.getDefault().getConsoleManager().showConsoleView( 1042 AdtPlugin.getDefault().getAndroidConsole()); 1043 } 1044 1045 /** 1046 * Returns whether the {@link IAndroidTarget}s have been loaded from the SDK. 1047 */ 1048 public final LoadStatus getSdkLoadStatus() { 1049 synchronized (Sdk.getLock()) { 1050 return mSdkIsLoaded; 1051 } 1052 } 1053 1054 /** 1055 * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes 1056 * to load. 1057 */ 1058 public final void setProjectToResolve(IJavaProject javaProject) { 1059 synchronized (Sdk.getLock()) { 1060 mPostLoadProjectsToResolve.add(javaProject); 1061 } 1062 } 1063 1064 /** 1065 * Sets the given {@link IJavaProject} to have its target checked for consistency 1066 * once the SDK finishes to load. This is used if the target is resolved using cached 1067 * information while the SDK is loading. 1068 */ 1069 public final void setProjectToCheck(IJavaProject javaProject) { 1070 // only lock on 1071 synchronized (Sdk.getLock()) { 1072 mPostLoadProjectsToCheck.add(javaProject); 1073 } 1074 } 1075 1076 /** 1077 * Checks the location of the SDK is valid and if it is, grab the SDK API version 1078 * from the SDK. 1079 * @return false if the location is not correct. 1080 */ 1081 private boolean checkSdkLocationAndId() { 1082 String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder(); 1083 if (sdkLocation == null || sdkLocation.length() == 0) { 1084 return false; 1085 } 1086 1087 return checkSdkLocationAndId(sdkLocation, new CheckSdkErrorHandler() { 1088 @Override 1089 public boolean handleError(String message) { 1090 AdtPlugin.displayError("Android SDK Verification", message); 1091 return false; 1092 } 1093 1094 @Override 1095 public boolean handleWarning(String message) { 1096 AdtPlugin.displayWarning("Android SDK Verification", message); 1097 return true; 1098 } 1099 }); 1100 } 1101 1102 /** 1103 * Internal helper to perform the actual sdk location and id check. 1104 * 1105 * @param osSdkLocation The sdk directory, an OS path. 1106 * @param errorHandler An checkSdkErrorHandler that can display a warning or an error. 1107 * @return False if there was an error or the result from the errorHandler invocation. 1108 */ 1109 public boolean checkSdkLocationAndId(String osSdkLocation, CheckSdkErrorHandler errorHandler) { 1110 if (osSdkLocation.endsWith(File.separator) == false) { 1111 osSdkLocation = osSdkLocation + File.separator; 1112 } 1113 1114 File osSdkFolder = new File(osSdkLocation); 1115 if (osSdkFolder.isDirectory() == false) { 1116 return errorHandler.handleError( 1117 String.format(Messages.Could_Not_Find_Folder, osSdkLocation)); 1118 } 1119 1120 String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; 1121 File toolsFolder = new File(osTools); 1122 if (toolsFolder.isDirectory() == false) { 1123 return errorHandler.handleError( 1124 String.format(Messages.Could_Not_Find_Folder_In_SDK, 1125 SdkConstants.FD_TOOLS, osSdkLocation)); 1126 } 1127 1128 // first check the min plug-in requirement as its error message is easier to figure 1129 // out for the user 1130 if (VersionCheck.checkVersion(osSdkLocation, errorHandler) == false) { 1131 return false; 1132 } 1133 1134 // check that we have both the tools component and the platform-tools component. 1135 String platformTools = osSdkLocation + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER; 1136 if (checkFolder(platformTools) == false) { 1137 return errorHandler.handleWarning("SDK Platform Tools component is missing!\n" + 1138 "Please use the SDK Manager to install it."); 1139 } 1140 1141 String tools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; 1142 if (checkFolder(tools) == false) { 1143 return errorHandler.handleError("SDK Tools component is missing!\n" + 1144 "Please use the SDK Manager to install it."); 1145 } 1146 1147 // check the path to various tools we use to make sure nothing is missing. This is 1148 // not meant to be exhaustive. 1149 String[] filesToCheck = new String[] { 1150 osSdkLocation + getOsRelativeAdb(), 1151 osSdkLocation + getOsRelativeEmulator() 1152 }; 1153 for (String file : filesToCheck) { 1154 if (checkFile(file) == false) { 1155 return errorHandler.handleError(String.format(Messages.Could_Not_Find, file)); 1156 } 1157 } 1158 1159 return true; 1160 } 1161 1162 /** 1163 * Checks if a path reference a valid existing file. 1164 * @param osPath the os path to check. 1165 * @return true if the file exists and is, in fact, a file. 1166 */ 1167 private boolean checkFile(String osPath) { 1168 File file = new File(osPath); 1169 if (file.isFile() == false) { 1170 return false; 1171 } 1172 1173 return true; 1174 } 1175 1176 /** 1177 * Checks if a path reference a valid existing folder. 1178 * @param osPath the os path to check. 1179 * @return true if the folder exists and is, in fact, a folder. 1180 */ 1181 private boolean checkFolder(String osPath) { 1182 File file = new File(osPath); 1183 if (file.isDirectory() == false) { 1184 return false; 1185 } 1186 1187 return true; 1188 } 1189 1190 /** 1191 * Parses the SDK resources. 1192 */ 1193 private void parseSdkContent(long delay) { 1194 // Perform the update in a thread (here an Eclipse runtime job) 1195 // since this should never block the caller (especially the start method) 1196 Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) { 1197 @SuppressWarnings("unchecked") 1198 @Override 1199 protected IStatus run(IProgressMonitor monitor) { 1200 try { 1201 1202 if (mSdkIsLoading) { 1203 return new Status(IStatus.WARNING, PLUGIN_ID, 1204 "An Android SDK is already being loaded. Please try again later."); 1205 } 1206 1207 mSdkIsLoading = true; 1208 1209 SubMonitor progress = SubMonitor.convert(monitor, 1210 "Initialize SDK Manager", 100); 1211 1212 Sdk sdk = Sdk.loadSdk(AdtPrefs.getPrefs().getOsSdkFolder()); 1213 1214 if (sdk != null) { 1215 1216 ArrayList<IJavaProject> list = new ArrayList<IJavaProject>(); 1217 synchronized (Sdk.getLock()) { 1218 mSdkIsLoaded = LoadStatus.LOADED; 1219 1220 progress.setTaskName("Check Projects"); 1221 1222 for (IJavaProject javaProject : mPostLoadProjectsToResolve) { 1223 IProject iProject = javaProject.getProject(); 1224 if (iProject.isOpen()) { 1225 // project that have been resolved before the sdk was loaded 1226 // will have a ProjectState where the IAndroidTarget is null 1227 // so we load the target now that the SDK is loaded. 1228 sdk.loadTarget(Sdk.getProjectState(iProject)); 1229 list.add(javaProject); 1230 } 1231 } 1232 1233 // done with this list. 1234 mPostLoadProjectsToResolve.clear(); 1235 } 1236 1237 // check the projects that need checking. 1238 // The method modifies the list (it removes the project that 1239 // do not need to be resolved again). 1240 AndroidClasspathContainerInitializer.checkProjectsCache( 1241 mPostLoadProjectsToCheck); 1242 1243 list.addAll(mPostLoadProjectsToCheck); 1244 1245 // update the project that needs recompiling. 1246 if (list.size() > 0) { 1247 IJavaProject[] array = list.toArray( 1248 new IJavaProject[list.size()]); 1249 AndroidClasspathContainerInitializer.updateProjects(array); 1250 } 1251 1252 progress.worked(10); 1253 } else { 1254 // SDK failed to Load! 1255 // Sdk#loadSdk() has already displayed an error. 1256 synchronized (Sdk.getLock()) { 1257 mSdkIsLoaded = LoadStatus.FAILED; 1258 } 1259 } 1260 1261 // Notify resource changed listeners 1262 progress.setTaskName("Refresh UI"); 1263 progress.setWorkRemaining(mTargetChangeListeners.size()); 1264 1265 // Clone the list before iterating, to avoid ConcurrentModification 1266 // exceptions 1267 final List<ITargetChangeListener> listeners = 1268 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1269 final SubMonitor progress2 = progress; 1270 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1271 public void run() { 1272 for (ITargetChangeListener listener : listeners) { 1273 try { 1274 listener.onSdkLoaded(); 1275 } catch (Exception e) { 1276 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1277 } finally { 1278 progress2.worked(1); 1279 } 1280 } 1281 } 1282 }); 1283 } catch (Throwable t) { 1284 log(t, "Unknown exception in parseSdkContent."); //$NON-NLS-1$ 1285 return new Status(IStatus.ERROR, PLUGIN_ID, 1286 "parseSdkContent failed", t); //$NON-NLS-1$ 1287 1288 } finally { 1289 mSdkIsLoading = false; 1290 if (monitor != null) { 1291 monitor.done(); 1292 } 1293 } 1294 1295 return Status.OK_STATUS; 1296 } 1297 }; 1298 job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs 1299 if (delay > 0) { 1300 job.schedule(delay); 1301 } else { 1302 job.schedule(); 1303 } 1304 } 1305 1306 /** Returns the global android console */ 1307 public MessageConsole getAndroidConsole() { 1308 return mAndroidConsole; 1309 } 1310 1311 // ----- Methods for Editors ------- 1312 1313 public void startEditors() { 1314 sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID, 1315 "/icons/android.png"); //$NON-NLS-1$ 1316 sAndroidLogo = sAndroidLogoDesc.createImage(); 1317 1318 // Add a resource listener to handle compiled resources. 1319 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1320 mResourceMonitor = GlobalProjectMonitor.startMonitoring(ws); 1321 1322 if (mResourceMonitor != null) { 1323 try { 1324 setupDefaultEditor(mResourceMonitor); 1325 ResourceManager.setup(mResourceMonitor); 1326 } catch (Throwable t) { 1327 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$ 1328 } 1329 } 1330 } 1331 1332 /** 1333 * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code> 1334 * method saves this plug-in's preference and dialog stores and shuts down 1335 * its image registry (if they are in use). Subclasses may extend this 1336 * method, but must send super <b>last</b>. A try-finally statement should 1337 * be used where necessary to ensure that <code>super.shutdown()</code> is 1338 * always done. 1339 * 1340 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 1341 */ 1342 public void stopEditors() { 1343 sAndroidLogo.dispose(); 1344 1345 IconFactory.getInstance().dispose(); 1346 1347 // Remove the resource listener that handles compiled resources. 1348 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1349 GlobalProjectMonitor.stopMonitoring(ws); 1350 1351 mRed.dispose(); 1352 } 1353 1354 /** 1355 * Returns an Image for the small Android logo. 1356 * 1357 * Callers should not dispose it. 1358 */ 1359 public static Image getAndroidLogo() { 1360 return sAndroidLogo; 1361 } 1362 1363 /** 1364 * Returns an {@link ImageDescriptor} for the small Android logo. 1365 * 1366 * Callers should not dispose it. 1367 */ 1368 public static ImageDescriptor getAndroidLogoDesc() { 1369 return sAndroidLogoDesc; 1370 } 1371 1372 /** 1373 * Returns the ResourceMonitor object. 1374 */ 1375 public GlobalProjectMonitor getResourceMonitor() { 1376 return mResourceMonitor; 1377 } 1378 1379 /** 1380 * Sets up the editor to register default editors for resource files when needed. 1381 * 1382 * This is called by the {@link AdtPlugin} during initialization. 1383 * 1384 * @param monitor The main Resource Monitor object. 1385 */ 1386 public void setupDefaultEditor(GlobalProjectMonitor monitor) { 1387 monitor.addFileListener(new IFileListener() { 1388 1389 private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$ 1390 1391 /* (non-Javadoc) 1392 * Sent when a file changed. 1393 * @param file The file that changed. 1394 * @param markerDeltas The marker deltas for the file. 1395 * @param kind The change kind. This is equivalent to 1396 * {@link IResourceDelta#accept(IResourceDeltaVisitor)} 1397 * 1398 * @see IFileListener#fileChanged 1399 */ 1400 public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { 1401 if (AdtConstants.EXT_XML.equals(file.getFileExtension())) { 1402 // The resources files must have a file path similar to 1403 // project/res/.../*.xml 1404 // There is no support for sub folders, so the segment count must be 4 1405 if (DEBUG_XML_FILE_INIT) { 1406 AdtPlugin.log(IStatus.INFO, "fileChanged %1$s", 1407 file.getFullPath().toOSString()); 1408 } 1409 1410 if (file.getFullPath().segmentCount() == 4) { 1411 // check if we are inside the res folder. 1412 String segment = file.getFullPath().segment(1); 1413 if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) { 1414 // we are inside a res/ folder, get the actual ResourceFolder 1415 ProjectResources resources = ResourceManager.getInstance(). 1416 getProjectResources(file.getProject()); 1417 1418 // This happens when importing old Android projects in Eclipse 1419 // that lack the container (probably because resources fail to build 1420 // properly.) 1421 if (resources == null) { 1422 log(IStatus.INFO, 1423 "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$ 1424 file.getFullPath().toOSString(), 1425 file.getProject().getName()); 1426 return; 1427 } 1428 1429 ResourceFolder resFolder = resources.getResourceFolder( 1430 (IFolder)file.getParent()); 1431 1432 if (resFolder != null) { 1433 if (kind == IResourceDelta.ADDED) { 1434 resourceAdded(file, resFolder.getType()); 1435 } else if (kind == IResourceDelta.CHANGED) { 1436 resourceChanged(file, resFolder.getType()); 1437 } 1438 } else { 1439 if (DEBUG_XML_FILE_INIT) { 1440 AdtPlugin.log(IStatus.INFO, " The resource folder was null"); 1441 } 1442 1443 // if the res folder is null, this means the name is invalid, 1444 // in this case we remove whatever android editors that was set 1445 // as the default editor. 1446 IEditorDescriptor desc = IDE.getDefaultEditor(file); 1447 String editorId = desc.getId(); 1448 if (DEBUG_XML_FILE_INIT) { 1449 AdtPlugin.log(IStatus.INFO, "Old editor id=%1$s", editorId); 1450 } 1451 if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) { 1452 // reset the default editor. 1453 IDE.setDefaultEditor(file, null); 1454 if (DEBUG_XML_FILE_INIT) { 1455 AdtPlugin.log(IStatus.INFO, " Resetting editor id to default"); 1456 } 1457 } 1458 } 1459 } else { 1460 if (DEBUG_XML_FILE_INIT) { 1461 AdtPlugin.log(IStatus.INFO, " Not in resources/, ignoring"); 1462 } 1463 } 1464 } 1465 } 1466 } 1467 1468 private void resourceAdded(IFile file, ResourceFolderType type) { 1469 if (DEBUG_XML_FILE_INIT) { 1470 AdtPlugin.log(IStatus.INFO, "resourceAdded %1$s - type=%1$s", 1471 file.getFullPath().toOSString(), type); 1472 } 1473 // set the default editor based on the type. 1474 if (type == ResourceFolderType.LAYOUT) { 1475 if (DEBUG_XML_FILE_INIT) { 1476 AdtPlugin.log(IStatus.INFO, " set default editor id to layout"); 1477 } 1478 IDE.setDefaultEditor(file, LayoutEditor.ID); 1479 } else if (type == ResourceFolderType.VALUES) { 1480 IDE.setDefaultEditor(file, ResourcesEditor.ID); 1481 } else if (type == ResourceFolderType.MENU) { 1482 IDE.setDefaultEditor(file, MenuEditor.ID); 1483 } else if (type == ResourceFolderType.COLOR) { 1484 IDE.setDefaultEditor(file, ColorEditor.ID); 1485 } else if (type == ResourceFolderType.DRAWABLE) { 1486 IDE.setDefaultEditor(file, DrawableEditor.ID); 1487 } else if (type == ResourceFolderType.ANIMATOR 1488 || type == ResourceFolderType.ANIM) { 1489 IDE.setDefaultEditor(file, AnimationEditor.ID); 1490 } else if (type == ResourceFolderType.XML) { 1491 if (XmlEditor.canHandleFile(file)) { 1492 if (DEBUG_XML_FILE_INIT) { 1493 AdtPlugin.log(IStatus.INFO, " set default editor id to XmlEditor.id"); 1494 } 1495 IDE.setDefaultEditor(file, XmlEditor.ID); 1496 } else { 1497 if (DEBUG_XML_FILE_INIT) { 1498 AdtPlugin.log(IStatus.INFO, " set default editor id unknown"); 1499 } 1500 // set a property to determine later if the XML can be handled 1501 QualifiedName qname = new QualifiedName( 1502 AdtPlugin.PLUGIN_ID, 1503 UNKNOWN_EDITOR); 1504 setFileProperty(file, qname, "1"); //$NON-NLS-1$ 1505 } 1506 } 1507 } 1508 1509 private void resourceChanged(IFile file, ResourceFolderType type) { 1510 if (DEBUG_XML_FILE_INIT) { 1511 AdtPlugin.log(IStatus.INFO, "resourceChanged %1$s - type=%1$s", 1512 file.getFullPath().toOSString(), type); 1513 } 1514 if (type == ResourceFolderType.XML) { 1515 IEditorDescriptor ed = IDE.getDefaultEditor(file); 1516 if (ed == null || !ed.getId().equals(XmlEditor.ID)) { 1517 QualifiedName qname = new QualifiedName( 1518 AdtPlugin.PLUGIN_ID, 1519 UNKNOWN_EDITOR); 1520 String prop = getFileProperty(file, qname); 1521 if (prop != null && XmlEditor.canHandleFile(file)) { 1522 try { 1523 // remove the property & set editor 1524 setFileProperty(file, qname, null); 1525 1526 // the window can be null sometimes 1527 IWorkbench wb = PlatformUI.getWorkbench(); 1528 IWorkbenchWindow win = wb == null ? null : 1529 wb.getActiveWorkbenchWindow(); 1530 IWorkbenchPage page = win == null ? null : 1531 win.getActivePage(); 1532 1533 IEditorPart oldEditor = page == null ? null : 1534 page.findEditor(new FileEditorInput(file)); 1535 if (page != null && 1536 oldEditor != null && 1537 AdtPlugin.displayPrompt("Android XML Editor", 1538 String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?", 1539 file.getFullPath()))) { 1540 IDE.setDefaultEditor(file, XmlEditor.ID); 1541 IEditorPart newEditor = page.openEditor( 1542 new FileEditorInput(file), 1543 XmlEditor.ID, 1544 true, /* activate */ 1545 IWorkbenchPage.MATCH_NONE); 1546 1547 if (newEditor != null) { 1548 page.closeEditor(oldEditor, true /* save */); 1549 } 1550 } 1551 } catch (CoreException e) { 1552 // page.openEditor may have failed 1553 } 1554 } 1555 } 1556 } 1557 } 1558 1559 }, IResourceDelta.ADDED | IResourceDelta.CHANGED); 1560 } 1561 1562 /** 1563 * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when 1564 * a project has its target changed. 1565 */ 1566 public void addTargetListener(ITargetChangeListener listener) { 1567 mTargetChangeListeners.add(listener); 1568 } 1569 1570 /** 1571 * Removes an existing {@link ITargetChangeListener}. 1572 * @see #addTargetListener(ITargetChangeListener) 1573 */ 1574 public void removeTargetListener(ITargetChangeListener listener) { 1575 mTargetChangeListeners.remove(listener); 1576 } 1577 1578 /** 1579 * Updates all the {@link ITargetChangeListener}s that a target has changed for a given project. 1580 * <p/>Only editors related to that project should reload. 1581 */ 1582 @SuppressWarnings("unchecked") 1583 public void updateTargetListeners(final IProject project) { 1584 final List<ITargetChangeListener> listeners = 1585 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1586 1587 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1588 public void run() { 1589 for (ITargetChangeListener listener : listeners) { 1590 try { 1591 listener.onProjectTargetChange(project); 1592 } catch (Exception e) { 1593 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1594 } 1595 } 1596 } 1597 }); 1598 } 1599 1600 /** 1601 * Updates all the {@link ITargetChangeListener}s that a target data was loaded. 1602 * <p/>Only editors related to a project using this target should reload. 1603 */ 1604 @SuppressWarnings("unchecked") 1605 public void updateTargetListeners(final IAndroidTarget target) { 1606 final List<ITargetChangeListener> listeners = 1607 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1608 1609 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1610 public void run() { 1611 for (ITargetChangeListener listener : listeners) { 1612 try { 1613 listener.onTargetLoaded(target); 1614 } catch (Exception e) { 1615 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1616 } 1617 } 1618 } 1619 }); 1620 } 1621 1622 public static synchronized OutputStream getOutStream() { 1623 return sPlugin.mAndroidConsoleStream; 1624 } 1625 1626 public static synchronized OutputStream getErrorStream() { 1627 return sPlugin.mAndroidConsoleErrorStream; 1628 } 1629 1630 /** 1631 * Sets the named persistent property for the given file to the given value 1632 * 1633 * @param file the file to associate the property with 1634 * @param qname the name of the property 1635 * @param value the new value, or null to clear the property 1636 */ 1637 public static void setFileProperty(IFile file, QualifiedName qname, String value) { 1638 try { 1639 file.setPersistentProperty(qname, value); 1640 } catch (CoreException e) { 1641 log(e, "Cannot set property %1$s to %2$s", qname, value); 1642 } 1643 } 1644 1645 /** 1646 * Gets the named persistent file property from the given file 1647 * 1648 * @param file the file to look up properties for 1649 * @param qname the name of the property to look up 1650 * @return the property value, or null 1651 */ 1652 public static String getFileProperty(IFile file, QualifiedName qname) { 1653 try { 1654 return file.getPersistentProperty(qname); 1655 } catch (CoreException e) { 1656 log(e, "Cannot get property %1$s", qname); 1657 } 1658 1659 return null; 1660 } 1661 1662 /** 1663 * Reparses the content of the SDK and updates opened projects. 1664 */ 1665 public void reparseSdk() { 1666 // add all the opened Android projects to the list of projects to be updated 1667 // after the SDK is reloaded 1668 synchronized (Sdk.getLock()) { 1669 // get the project to refresh. 1670 IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null /*filter*/); 1671 mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects)); 1672 } 1673 1674 // parse the SDK resources at the new location 1675 parseSdkContent(0 /*immediately*/); 1676 } 1677 1678 /** 1679 * Prints messages, associated with a project to the specified stream 1680 * @param stream The stream to write to 1681 * @param tag The tag associated to the message. Can be null 1682 * @param objects The objects to print through their toString() method (or directly for 1683 * {@link String} objects. 1684 */ 1685 public static synchronized void printToStream(MessageConsoleStream stream, String tag, 1686 Object... objects) { 1687 String dateTag = AndroidPrintStream.getMessageTag(tag); 1688 1689 for (Object obj : objects) { 1690 stream.print(dateTag); 1691 stream.print(" "); //$NON-NLS-1$ 1692 if (obj instanceof String) { 1693 stream.println((String)obj); 1694 } else if (obj == null) { 1695 stream.println("(null)"); //$NON-NLS-1$ 1696 } else { 1697 stream.println(obj.toString()); 1698 } 1699 } 1700 } 1701 1702 // --------- ILogger methods ----------- 1703 1704 public void error(Throwable t, String format, Object... args) { 1705 if (t != null) { 1706 log(t, format, args); 1707 } else { 1708 log(IStatus.ERROR, format, args); 1709 } 1710 } 1711 1712 public void printf(String format, Object... args) { 1713 log(IStatus.INFO, format, args); 1714 } 1715 1716 public void warning(String format, Object... args) { 1717 log(IStatus.WARNING, format, args); 1718 } 1719 1720 /** 1721 * Opens the given URL in a browser tab 1722 * 1723 * @param url the URL to open in a browser 1724 */ 1725 public static void openUrl(URL url) { 1726 IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport(); 1727 IWebBrowser browser; 1728 try { 1729 browser = support.createBrowser(PLUGIN_ID); 1730 browser.openURL(url); 1731 } catch (PartInitException e) { 1732 log(e, null); 1733 } 1734 } 1735 1736 /** 1737 * Opens a Java class for the given fully qualified class name 1738 * 1739 * @param project the project containing the class 1740 * @param fqcn the fully qualified class name of the class to be opened 1741 * @return true if the class was opened, false otherwise 1742 */ 1743 public static boolean openJavaClass(IProject project, String fqcn) { 1744 if (fqcn == null) { 1745 return false; 1746 } 1747 1748 // Handle inner classes 1749 if (fqcn.indexOf('$') != -1) { 1750 fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$ 1751 } 1752 1753 try { 1754 if (project.hasNature(JavaCore.NATURE_ID)) { 1755 IJavaProject javaProject = JavaCore.create(project); 1756 IJavaElement result = javaProject.findType(fqcn); 1757 if (result != null) { 1758 return JavaUI.openInEditor(result) != null; 1759 } 1760 } 1761 } catch (Throwable e) { 1762 log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$ 1763 } 1764 1765 return false; 1766 } 1767 1768 /** 1769 * Opens the given file and shows the given (optional) region in the editor (or 1770 * if no region is specified, opens the editor tab.) 1771 * 1772 * @param file the file to be opened 1773 * @param region an optional region which if set will be selected and shown to the 1774 * user 1775 * @throws PartInitException if something goes wrong 1776 */ 1777 public static void openFile(IFile file, IRegion region) throws PartInitException { 1778 openFile(file, region, true); 1779 } 1780 1781 /** 1782 * Opens the given file and shows the given (optional) region 1783 * 1784 * @param file the file to be opened 1785 * @param region an optional region which if set will be selected and shown to the 1786 * user 1787 * @param showEditorTab if true, front the editor tab after opening the file 1788 * @return the editor that was opened, or null if no editor was opened 1789 * @throws PartInitException if something goes wrong 1790 */ 1791 public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab) 1792 throws PartInitException { 1793 IWorkbench workbench = PlatformUI.getWorkbench(); 1794 if (workbench == null) { 1795 return null; 1796 } 1797 IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); 1798 if (activeWorkbenchWindow == null) { 1799 return null; 1800 } 1801 IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); 1802 if (page == null) { 1803 return null; 1804 } 1805 IEditorPart targetEditor = IDE.openEditor(page, file, true); 1806 if (targetEditor instanceof AndroidXmlEditor) { 1807 AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor; 1808 if (region != null) { 1809 editor.show(region.getOffset(), region.getLength(), showEditorTab); 1810 } else if (showEditorTab) { 1811 editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); 1812 } 1813 } 1814 1815 return targetEditor; 1816 } 1817 } 1818