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