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                             shell = AdtPlugin.getShell();
   1185                         }
   1186                         if (shell == null) {
   1187                             return;
   1188                         }
   1189 
   1190                         String customLabel = null;
   1191                         switch(solution) {
   1192                         case OPEN_ANDROID_PREFS:
   1193                             customLabel = "Open Preferences";
   1194                             break;
   1195                         case OPEN_P2_UPDATE:
   1196                             customLabel = "Check for Updates";
   1197                             break;
   1198                         case OPEN_SDK_MANAGER:
   1199                             customLabel = "Open SDK Manager";
   1200                             break;
   1201                         }
   1202 
   1203                         String btnLabels[] = new String[customLabel == null ? 1 : 2];
   1204                         btnLabels[0] = customLabel;
   1205                         btnLabels[btnLabels.length - 1] = IDialogConstants.CLOSE_LABEL;
   1206 
   1207                         MessageDialog dialog = new MessageDialog(
   1208                                 shell, // parent
   1209                                 mTitle,
   1210                                 null, // dialogTitleImage
   1211                                 message,
   1212                                 dialogImageType,
   1213                                 btnLabels,
   1214                                 btnLabels.length - 1);
   1215                         int index = dialog.open();
   1216 
   1217                         if (customLabel != null && index == 0) {
   1218                             switch(solution) {
   1219                             case OPEN_ANDROID_PREFS:
   1220                                 openAndroidPrefs();
   1221                                 break;
   1222                             case OPEN_P2_UPDATE:
   1223                                 openP2Update();
   1224                                 break;
   1225                             case OPEN_SDK_MANAGER:
   1226                                 openSdkManager();
   1227                                 break;
   1228                             }
   1229                         }
   1230                     }
   1231                 });
   1232             }
   1233 
   1234             private void openSdkManager() {
   1235                 // Open the standalone external SDK Manager since we know
   1236                 // that ADT on Windows is bound to be locking some SDK folders.
   1237                 //
   1238                 // Also when this is invoked because SdkManagerAction.run() fails, this
   1239                 // test will fail and we'll fallback on using the internal one.
   1240                 if (SdkManagerAction.openExternalSdkManager()) {
   1241                     return;
   1242                 }
   1243 
   1244                 // Otherwise open the regular SDK Manager bundled within ADT
   1245                 if (!SdkManagerAction.openAdtSdkManager()) {
   1246                     // We failed because the SDK location is undefined. In this case
   1247                     // let's open the preferences instead.
   1248                     openAndroidPrefs();
   1249                 }
   1250             }
   1251 
   1252             private void openP2Update() {
   1253                 Display disp = getDisplay();
   1254                 if (disp == null) {
   1255                     return;
   1256                 }
   1257                 disp.asyncExec(new Runnable() {
   1258                     @Override
   1259                     public void run() {
   1260                         String cmdId = "org.eclipse.equinox.p2.ui.sdk.update";  //$NON-NLS-1$
   1261                         IWorkbench wb = PlatformUI.getWorkbench();
   1262                         if (wb == null) {
   1263                             return;
   1264                         }
   1265 
   1266                         ICommandService cs = (ICommandService) wb.getService(ICommandService.class);
   1267                         IHandlerService is = (IHandlerService) wb.getService(IHandlerService.class);
   1268                         if (cs == null || is == null) {
   1269                             return;
   1270                         }
   1271 
   1272                         Command cmd = cs.getCommand(cmdId);
   1273                         if (cmd != null && cmd.isDefined()) {
   1274                             try {
   1275                                 is.executeCommand(cmdId, null/*event*/);
   1276                             } catch (Exception ignore) {
   1277                                 AdtPlugin.log(ignore, "Failed to execute command %s", cmdId);
   1278                             }
   1279                         }
   1280                     }
   1281                 });
   1282             }
   1283 
   1284             private void openAndroidPrefs() {
   1285                 PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(
   1286                         getDisplay().getActiveShell(),
   1287                         "com.android.ide.eclipse.preferences.main", //$NON-NLS-1$ preferencePageId
   1288                         null,  // displayedIds
   1289                         null); // data
   1290                 dialog.open();
   1291             }
   1292         });
   1293     }
   1294 
   1295     /**
   1296      * Internal helper to perform the actual sdk location and id check.
   1297      * <p/>
   1298      * This is useful for callers who want to override what happens when the check
   1299      * fails. Otherwise consider calling {@link #checkSdkLocationAndId()} that will
   1300      * present a modal dialog to the user in case of failure.
   1301      *
   1302      * @param osSdkLocation The sdk directory, an OS path. Can be null.
   1303      * @param errorHandler An checkSdkErrorHandler that can display a warning or an error.
   1304      * @return False if there was an error or the result from the errorHandler invocation.
   1305      */
   1306     public boolean checkSdkLocationAndId(@Nullable String osSdkLocation,
   1307                                          @NonNull CheckSdkErrorHandler errorHandler) {
   1308         if (osSdkLocation == null || osSdkLocation.trim().length() == 0) {
   1309             return errorHandler.handleError(
   1310                     Solution.OPEN_ANDROID_PREFS,
   1311                     "Location of the Android SDK has not been setup in the preferences.");
   1312         }
   1313 
   1314         if (!osSdkLocation.endsWith(File.separator)) {
   1315             osSdkLocation = osSdkLocation + File.separator;
   1316         }
   1317 
   1318         File osSdkFolder = new File(osSdkLocation);
   1319         if (osSdkFolder.isDirectory() == false) {
   1320             return errorHandler.handleError(
   1321                     Solution.OPEN_ANDROID_PREFS,
   1322                     String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
   1323         }
   1324 
   1325         String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
   1326         File toolsFolder = new File(osTools);
   1327         if (toolsFolder.isDirectory() == false) {
   1328             return errorHandler.handleError(
   1329                     Solution.OPEN_ANDROID_PREFS,
   1330                     String.format(Messages.Could_Not_Find_Folder_In_SDK,
   1331                             SdkConstants.FD_TOOLS, osSdkLocation));
   1332         }
   1333 
   1334         // first check the min plug-in requirement as its error message is easier to figure
   1335         // out for the user
   1336         if (VersionCheck.checkVersion(osSdkLocation, errorHandler) == false) {
   1337             return false;
   1338         }
   1339 
   1340         // check that we have both the tools component and the platform-tools component.
   1341         String platformTools = osSdkLocation + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER;
   1342         if (checkFolder(platformTools) == false) {
   1343             return errorHandler.handleWarning(
   1344                     Solution.OPEN_SDK_MANAGER,
   1345                     "SDK Platform Tools component is missing!\n" +
   1346                     "Please use the SDK Manager to install it.");
   1347         }
   1348 
   1349         String tools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
   1350         if (checkFolder(tools) == false) {
   1351             return errorHandler.handleError(
   1352                     Solution.OPEN_SDK_MANAGER,
   1353                     "SDK Tools component is missing!\n" +
   1354                     "Please use the SDK Manager to install it.");
   1355         }
   1356 
   1357         // check the path to various tools we use to make sure nothing is missing. This is
   1358         // not meant to be exhaustive.
   1359         String[] filesToCheck = new String[] {
   1360                 osSdkLocation + getOsRelativeAdb(),
   1361                 osSdkLocation + getOsRelativeEmulator()
   1362         };
   1363         for (String file : filesToCheck) {
   1364             if (checkFile(file) == false) {
   1365                 return errorHandler.handleError(
   1366                         Solution.OPEN_ANDROID_PREFS,
   1367                         String.format(Messages.Could_Not_Find, file));
   1368             }
   1369         }
   1370 
   1371         return true;
   1372     }
   1373 
   1374     /**
   1375      * Checks if a path reference a valid existing file.
   1376      * @param osPath the os path to check.
   1377      * @return true if the file exists and is, in fact, a file.
   1378      */
   1379     private boolean checkFile(String osPath) {
   1380         File file = new File(osPath);
   1381         if (file.isFile() == false) {
   1382             return false;
   1383         }
   1384 
   1385         return true;
   1386     }
   1387 
   1388     /**
   1389      * Checks if a path reference a valid existing folder.
   1390      * @param osPath the os path to check.
   1391      * @return true if the folder exists and is, in fact, a folder.
   1392      */
   1393     private boolean checkFolder(String osPath) {
   1394         File file = new File(osPath);
   1395         if (file.isDirectory() == false) {
   1396             return false;
   1397         }
   1398 
   1399         return true;
   1400     }
   1401 
   1402     /**
   1403      * Parses the SDK resources.
   1404      */
   1405     private void parseSdkContent(long delay) {
   1406         // Perform the update in a thread (here an Eclipse runtime job)
   1407         // since this should never block the caller (especially the start method)
   1408         Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
   1409             @SuppressWarnings("unchecked")
   1410             @Override
   1411             protected IStatus run(IProgressMonitor monitor) {
   1412                 try {
   1413 
   1414                     if (mParseSdkContentIsRunning) {
   1415                         return new Status(IStatus.WARNING, PLUGIN_ID,
   1416                                 "An Android SDK is already being loaded. Please try again later.");
   1417                     }
   1418 
   1419                     mParseSdkContentIsRunning = true;
   1420 
   1421                     SubMonitor progress = SubMonitor.convert(monitor,
   1422                             "Initialize SDK Manager", 100);
   1423 
   1424                     Sdk sdk = Sdk.loadSdk(AdtPrefs.getPrefs().getOsSdkFolder());
   1425 
   1426                     if (sdk != null) {
   1427                         ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
   1428                         synchronized (Sdk.getLock()) {
   1429                             mSdkLoadedStatus = LoadStatus.LOADED;
   1430 
   1431                             progress.setTaskName("Check Projects");
   1432 
   1433                             for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
   1434                                 IProject iProject = javaProject.getProject();
   1435                                 if (iProject.isOpen()) {
   1436                                     // project that have been resolved before the sdk was loaded
   1437                                     // will have a ProjectState where the IAndroidTarget is null
   1438                                     // so we load the target now that the SDK is loaded.
   1439                                     sdk.loadTargetAndBuildTools(Sdk.getProjectState(iProject));
   1440                                     list.add(javaProject);
   1441                                 }
   1442                             }
   1443 
   1444                             // done with this list.
   1445                             mPostLoadProjectsToResolve.clear();
   1446                         }
   1447 
   1448                         // check the projects that need checking.
   1449                         // The method modifies the list (it removes the project that
   1450                         // do not need to be resolved again).
   1451                         AndroidClasspathContainerInitializer.checkProjectsCache(
   1452                                 mPostLoadProjectsToCheck);
   1453 
   1454                         list.addAll(mPostLoadProjectsToCheck);
   1455 
   1456                         // update the project that needs recompiling.
   1457                         if (list.size() > 0) {
   1458                             IJavaProject[] array = list.toArray(
   1459                                     new IJavaProject[list.size()]);
   1460                             ProjectHelper.updateProjects(array);
   1461                         }
   1462 
   1463                         progress.worked(10);
   1464                     } else {
   1465                         // SDK failed to Load!
   1466                         // Sdk#loadSdk() has already displayed an error.
   1467                         synchronized (Sdk.getLock()) {
   1468                             mSdkLoadedStatus = LoadStatus.FAILED;
   1469                         }
   1470                     }
   1471 
   1472                     // Notify resource changed listeners
   1473                     progress.setTaskName("Refresh UI");
   1474                     progress.setWorkRemaining(mTargetChangeListeners.size());
   1475 
   1476                     // Clone the list before iterating, to avoid ConcurrentModification
   1477                     // exceptions
   1478                     final List<ITargetChangeListener> listeners =
   1479                         (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1480                     final SubMonitor progress2 = progress;
   1481                     AdtPlugin.getDisplay().asyncExec(new Runnable() {
   1482                         @Override
   1483                         public void run() {
   1484                             for (ITargetChangeListener listener : listeners) {
   1485                                 try {
   1486                                     listener.onSdkLoaded();
   1487                                 } catch (Exception e) {
   1488                                     AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
   1489                                 } finally {
   1490                                     progress2.worked(1);
   1491                                 }
   1492                             }
   1493                         }
   1494                     });
   1495                 } catch (Throwable t) {
   1496                     log(t, "Unknown exception in parseSdkContent.");    //$NON-NLS-1$
   1497                     return new Status(IStatus.ERROR, PLUGIN_ID,
   1498                             "parseSdkContent failed", t);               //$NON-NLS-1$
   1499 
   1500                 } finally {
   1501                     mParseSdkContentIsRunning = false;
   1502                     if (monitor != null) {
   1503                         monitor.done();
   1504                     }
   1505                 }
   1506 
   1507                 return Status.OK_STATUS;
   1508             }
   1509         };
   1510         job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
   1511         job.setRule(ResourcesPlugin.getWorkspace().getRoot());
   1512         if (delay > 0) {
   1513             job.schedule(delay);
   1514         } else {
   1515             job.schedule();
   1516         }
   1517     }
   1518 
   1519     /** Returns the global android console */
   1520     public MessageConsole getAndroidConsole() {
   1521         return mAndroidConsole;
   1522     }
   1523 
   1524     // ----- Methods for Editors -------
   1525 
   1526     public void startEditors() {
   1527         sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
   1528                 "/icons/android.png"); //$NON-NLS-1$
   1529         sAndroidLogo = sAndroidLogoDesc.createImage();
   1530 
   1531         // Add a resource listener to handle compiled resources.
   1532         IWorkspace ws = ResourcesPlugin.getWorkspace();
   1533         mResourceMonitor = GlobalProjectMonitor.startMonitoring(ws);
   1534 
   1535         if (mResourceMonitor != null) {
   1536             try {
   1537                 setupEditors(mResourceMonitor);
   1538                 ResourceManager.setup(mResourceMonitor);
   1539                 LintDeltaProcessor.startListening(mResourceMonitor);
   1540             } catch (Throwable t) {
   1541                 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
   1542             }
   1543         }
   1544     }
   1545 
   1546     /**
   1547      * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
   1548      * method saves this plug-in's preference and dialog stores and shuts down
   1549      * its image registry (if they are in use). Subclasses may extend this
   1550      * method, but must send super <b>last</b>. A try-finally statement should
   1551      * be used where necessary to ensure that <code>super.shutdown()</code> is
   1552      * always done.
   1553      *
   1554      * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
   1555      */
   1556     public void stopEditors() {
   1557         sAndroidLogo.dispose();
   1558 
   1559         IconFactory.getInstance().dispose();
   1560 
   1561         LintDeltaProcessor.stopListening(mResourceMonitor);
   1562 
   1563         // Remove the resource listener that handles compiled resources.
   1564         IWorkspace ws = ResourcesPlugin.getWorkspace();
   1565         GlobalProjectMonitor.stopMonitoring(ws);
   1566 
   1567         if (mRed != null) {
   1568             mRed.dispose();
   1569             mRed = null;
   1570         }
   1571     }
   1572 
   1573     /**
   1574      * Returns an Image for the small Android logo.
   1575      *
   1576      * Callers should not dispose it.
   1577      */
   1578     public static Image getAndroidLogo() {
   1579         return sAndroidLogo;
   1580     }
   1581 
   1582     /**
   1583      * Returns an {@link ImageDescriptor} for the small Android logo.
   1584      *
   1585      * Callers should not dispose it.
   1586      */
   1587     public static ImageDescriptor getAndroidLogoDesc() {
   1588         return sAndroidLogoDesc;
   1589     }
   1590 
   1591     /**
   1592      * Returns the ResourceMonitor object.
   1593      */
   1594     public GlobalProjectMonitor getResourceMonitor() {
   1595         return mResourceMonitor;
   1596     }
   1597 
   1598     /**
   1599      * Sets up the editor resource listener.
   1600      * <p>
   1601      * The listener handles:
   1602      * <ul>
   1603      * <li> Discovering newly created files, and ensuring that if they are in an Android
   1604      *      project, they default to the right XML editor.
   1605      * <li> Discovering deleted files, and closing the corresponding editors if necessary.
   1606      *      This is only done for XML files, since other editors such as Java editors handles
   1607      *      it on their own.
   1608      * <ul>
   1609      *
   1610      * This is called by the {@link AdtPlugin} during initialization.
   1611      *
   1612      * @param monitor The main Resource Monitor object.
   1613      */
   1614     public void setupEditors(GlobalProjectMonitor monitor) {
   1615         monitor.addFileListener(new IFileListener() {
   1616             @Override
   1617             public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
   1618                     int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
   1619                 if (!isAndroidProject) {
   1620                     return;
   1621                 }
   1622                 if (flags == IResourceDelta.MARKERS || !SdkConstants.EXT_XML.equals(extension)) {
   1623                     // ONLY the markers changed, or not XML file: not relevant to this listener
   1624                     return;
   1625                 }
   1626 
   1627                 if (kind == IResourceDelta.REMOVED) {
   1628                     AdtUtils.closeEditors(file, false /*save*/);
   1629                     return;
   1630                 }
   1631 
   1632                 // The resources files must have a file path similar to
   1633                 //    project/res/.../*.xml
   1634                 // There is no support for sub folders, so the segment count must be 4
   1635                 if (file.getFullPath().segmentCount() == 4) {
   1636                     // check if we are inside the res folder.
   1637                     String segment = file.getFullPath().segment(1);
   1638                     if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
   1639                         // we are inside a res/ folder, get the ResourceFolderType of the
   1640                         // parent folder.
   1641                         String[] folderSegments = file.getParent().getName().split(
   1642                                 SdkConstants.RES_QUALIFIER_SEP);
   1643 
   1644                         // get the enum for the resource type.
   1645                         ResourceFolderType type = ResourceFolderType.getTypeByName(
   1646                                 folderSegments[0]);
   1647 
   1648                         if (type != null) {
   1649                             if (kind == IResourceDelta.ADDED) {
   1650                                 // A new file {@code /res/type-config/some.xml} was added.
   1651                                 // All the /res XML files are handled by the same common editor now.
   1652                                 IDE.setDefaultEditor(file, CommonXmlEditor.ID);
   1653                             }
   1654                         } else {
   1655                             // if the res folder is null, this means the name is invalid,
   1656                             // in this case we remove whatever android editors that was set
   1657                             // as the default editor.
   1658                             IEditorDescriptor desc = IDE.getDefaultEditor(file);
   1659                             String editorId = desc.getId();
   1660                             if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) {
   1661                                 // reset the default editor.
   1662                                 IDE.setDefaultEditor(file, null);
   1663                             }
   1664                         }
   1665                     }
   1666                 }
   1667             }
   1668         }, IResourceDelta.ADDED | IResourceDelta.REMOVED);
   1669 
   1670         monitor.addProjectListener(new IProjectListener() {
   1671             @Override
   1672             public void projectClosed(IProject project) {
   1673                 // Close any editors referencing this project
   1674                 AdtUtils.closeEditors(project, true /*save*/);
   1675             }
   1676 
   1677             @Override
   1678             public void projectDeleted(IProject project) {
   1679                 // Close any editors referencing this project
   1680                 AdtUtils.closeEditors(project, false /*save*/);
   1681             }
   1682 
   1683             @Override
   1684             public void projectOpenedWithWorkspace(IProject project) {
   1685             }
   1686 
   1687             @Override
   1688             public void allProjectsOpenedWithWorkspace() {
   1689             }
   1690 
   1691             @Override
   1692             public void projectOpened(IProject project) {
   1693             }
   1694 
   1695             @Override
   1696             public void projectRenamed(IProject project, IPath from) {
   1697             }
   1698         });
   1699     }
   1700 
   1701     /**
   1702      * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
   1703      * a project has its target changed.
   1704      */
   1705     public void addTargetListener(ITargetChangeListener listener) {
   1706         mTargetChangeListeners.add(listener);
   1707     }
   1708 
   1709     /**
   1710      * Removes an existing {@link ITargetChangeListener}.
   1711      * @see #addTargetListener(ITargetChangeListener)
   1712      */
   1713     public void removeTargetListener(ITargetChangeListener listener) {
   1714         mTargetChangeListeners.remove(listener);
   1715     }
   1716 
   1717     /**
   1718      * Updates all the {@link ITargetChangeListener}s that a target has changed for a given project.
   1719      * <p/>Only editors related to that project should reload.
   1720      */
   1721     @SuppressWarnings("unchecked")
   1722     public void updateTargetListeners(final IProject project) {
   1723         final List<ITargetChangeListener> listeners =
   1724             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1725 
   1726         AdtPlugin.getDisplay().asyncExec(new Runnable() {
   1727             @Override
   1728             public void run() {
   1729                 for (ITargetChangeListener listener : listeners) {
   1730                     try {
   1731                         listener.onProjectTargetChange(project);
   1732                     } catch (Exception e) {
   1733                         AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
   1734                     }
   1735                 }
   1736             }
   1737         });
   1738     }
   1739 
   1740     /**
   1741      * Updates all the {@link ITargetChangeListener}s that a target data was loaded.
   1742      * <p/>Only editors related to a project using this target should reload.
   1743      */
   1744     @SuppressWarnings("unchecked")
   1745     public void updateTargetListeners(final IAndroidTarget target) {
   1746         final List<ITargetChangeListener> listeners =
   1747             (List<ITargetChangeListener>)mTargetChangeListeners.clone();
   1748 
   1749         Display display = AdtPlugin.getDisplay();
   1750         if (display == null || display.isDisposed()) {
   1751             return;
   1752         }
   1753         display.asyncExec(new Runnable() {
   1754             @Override
   1755             public void run() {
   1756                 for (ITargetChangeListener listener : listeners) {
   1757                     try {
   1758                         listener.onTargetLoaded(target);
   1759                     } catch (Exception e) {
   1760                         AdtPlugin.log(e, "Failed to update a TargetChangeListener.");  //$NON-NLS-1$
   1761                     }
   1762                 }
   1763             }
   1764         });
   1765     }
   1766 
   1767     public static synchronized OutputStream getOutStream() {
   1768         return sPlugin.mAndroidConsoleStream;
   1769     }
   1770 
   1771     public static synchronized OutputStream getErrorStream() {
   1772         return sPlugin.mAndroidConsoleErrorStream;
   1773     }
   1774 
   1775     /**
   1776      * Sets the named persistent property for the given file to the given value
   1777      *
   1778      * @param file the file to associate the property with
   1779      * @param qname the name of the property
   1780      * @param value the new value, or null to clear the property
   1781      */
   1782     public static void setFileProperty(IFile file, QualifiedName qname, String value) {
   1783         try {
   1784             file.setPersistentProperty(qname, value);
   1785         } catch (CoreException e) {
   1786             log(e, "Cannot set property %1$s to %2$s", qname, value);
   1787         }
   1788     }
   1789 
   1790     /**
   1791      * Gets the named persistent file property from the given file
   1792      *
   1793      * @param file the file to look up properties for
   1794      * @param qname the name of the property to look up
   1795      * @return the property value, or null
   1796      */
   1797     public static String getFileProperty(IFile file, QualifiedName qname) {
   1798         try {
   1799             return file.getPersistentProperty(qname);
   1800         } catch (CoreException e) {
   1801             log(e, "Cannot get property %1$s", qname);
   1802         }
   1803 
   1804         return null;
   1805     }
   1806 
   1807     /**
   1808      * Conditionally reparses the content of the SDK if it has changed on-disk
   1809      * and updates opened projects.
   1810      * <p/>
   1811      * The operation is asynchronous and happens in a background eclipse job.
   1812      */
   1813     public void refreshSdk() {
   1814         // SDK can't have changed if we haven't loaded it yet.
   1815         final Sdk sdk = Sdk.getCurrent();
   1816         if (sdk == null) {
   1817             return;
   1818         }
   1819 
   1820         Job job = new Job("Check Android SDK") {
   1821             @Override
   1822             protected IStatus run(IProgressMonitor monitor) {
   1823                 // SDK has changed if its location path is different.
   1824                 boolean changed = sdk.getSdkLocation() == null ||
   1825                                  !sdk.getSdkLocation().equals(AdtPrefs.getPrefs().getOsSdkFolder());
   1826                 if (!changed) {
   1827                     // Check whether the target directories has potentially changed.
   1828                     changed = sdk.haveTargetsChanged();
   1829                 }
   1830 
   1831                 if (changed) {
   1832                     monitor.setTaskName("Reload Android SDK");
   1833                     reparseSdk();
   1834                 }
   1835 
   1836                 monitor.done();
   1837                 return Status.OK_STATUS;
   1838             }
   1839         };
   1840         job.setRule(ResourcesPlugin.getWorkspace().getRoot());
   1841         job.setPriority(Job.SHORT); // a short background job, not interactive.
   1842         job.schedule();
   1843     }
   1844 
   1845     /**
   1846      * Reparses the content of the SDK and updates opened projects.
   1847      * The operation is asynchronous and happens in a background eclipse job.
   1848      * <p/>
   1849      * This reloads the SDK all the time. To only perform this when it has potentially
   1850      * changed, call {@link #refreshSdk()} instead.
   1851      */
   1852     public void reparseSdk() {
   1853         // add all the opened Android projects to the list of projects to be updated
   1854         // after the SDK is reloaded
   1855         synchronized (Sdk.getLock()) {
   1856             // get the project to refresh.
   1857             IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null /*filter*/);
   1858             mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects));
   1859         }
   1860 
   1861         // parse the SDK resources at the new location
   1862         parseSdkContent(0 /*immediately*/);
   1863     }
   1864 
   1865     /**
   1866      * Prints messages, associated with a project to the specified stream
   1867      * @param stream The stream to write to
   1868      * @param tag The tag associated to the message. Can be null
   1869      * @param objects The objects to print through their toString() method (or directly for
   1870      * {@link String} objects.
   1871      */
   1872     public static synchronized void printToStream(MessageConsoleStream stream, String tag,
   1873             Object... objects) {
   1874         String dateTag = AndroidPrintStream.getMessageTag(tag);
   1875 
   1876         for (Object obj : objects) {
   1877             stream.print(dateTag);
   1878             stream.print(" "); //$NON-NLS-1$
   1879             if (obj instanceof String) {
   1880                 stream.println((String)obj);
   1881             } else if (obj == null) {
   1882                 stream.println("(null)");  //$NON-NLS-1$
   1883             } else {
   1884                 stream.println(obj.toString());
   1885             }
   1886         }
   1887     }
   1888 
   1889     // --------- ILogger methods -----------
   1890 
   1891     @Override
   1892     public void error(@Nullable Throwable t, @Nullable String format, Object... args) {
   1893         if (t != null) {
   1894             log(t, format, args);
   1895         } else {
   1896             log(IStatus.ERROR, format, args);
   1897         }
   1898     }
   1899 
   1900     @Override
   1901     public void info(@NonNull String format, Object... args) {
   1902         log(IStatus.INFO, format, args);
   1903     }
   1904 
   1905     @Override
   1906     public void verbose(@NonNull String format, Object... args) {
   1907         log(IStatus.INFO, format, args);
   1908     }
   1909 
   1910     @Override
   1911     public void warning(@NonNull String format, Object... args) {
   1912         log(IStatus.WARNING, format, args);
   1913     }
   1914 
   1915     /**
   1916      * Opens the given URL in a browser tab
   1917      *
   1918      * @param url the URL to open in a browser
   1919      */
   1920     public static void openUrl(URL url) {
   1921         IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
   1922         IWebBrowser browser;
   1923         try {
   1924             browser = support.createBrowser(PLUGIN_ID);
   1925             browser.openURL(url);
   1926         } catch (PartInitException e) {
   1927             log(e, null);
   1928         }
   1929     }
   1930 
   1931     /**
   1932      * Opens a Java class for the given fully qualified class name
   1933      *
   1934      * @param project the project containing the class
   1935      * @param fqcn the fully qualified class name of the class to be opened
   1936      * @return true if the class was opened, false otherwise
   1937      */
   1938     public static boolean openJavaClass(IProject project, String fqcn) {
   1939         if (fqcn == null) {
   1940             return false;
   1941         }
   1942 
   1943         // Handle inner classes
   1944         if (fqcn.indexOf('$') != -1) {
   1945             fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$
   1946         }
   1947 
   1948         try {
   1949             if (project.hasNature(JavaCore.NATURE_ID)) {
   1950                 IJavaProject javaProject = JavaCore.create(project);
   1951                 IJavaElement result = javaProject.findType(fqcn);
   1952                 if (result != null) {
   1953                     return JavaUI.openInEditor(result) != null;
   1954                 }
   1955             }
   1956         } catch (Throwable e) {
   1957             log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$
   1958         }
   1959 
   1960         return false;
   1961     }
   1962 
   1963     /**
   1964      * For a stack trace entry, specifying a class, method, and optionally
   1965      * fileName and line number, open the corresponding line in the editor.
   1966      *
   1967      * @param fqcn the fully qualified name of the class
   1968      * @param method the method name
   1969      * @param fileName the file name, or null
   1970      * @param lineNumber the line number or -1
   1971      * @return true if the target location could be opened, false otherwise
   1972      */
   1973     public static boolean openStackTraceLine(@Nullable String fqcn,
   1974             @Nullable String method, @Nullable String fileName, int lineNumber) {
   1975         return new SourceRevealer().revealMethod(fqcn + '.' + method, fileName, lineNumber, null);
   1976     }
   1977 
   1978     /**
   1979      * Opens the given file and shows the given (optional) region in the editor (or
   1980      * if no region is specified, opens the editor tab.)
   1981      *
   1982      * @param file the file to be opened
   1983      * @param region an optional region which if set will be selected and shown to the
   1984      *            user
   1985      * @throws PartInitException if something goes wrong
   1986      */
   1987     public static void openFile(IFile file, IRegion region) throws PartInitException {
   1988         openFile(file, region, true);
   1989     }
   1990 
   1991     // TODO: Make an openEditor which does the above, and make the above pass false for showEditor
   1992 
   1993     /**
   1994      * Opens the given file and shows the given (optional) region
   1995      *
   1996      * @param file the file to be opened
   1997      * @param region an optional region which if set will be selected and shown to the
   1998      *            user
   1999      * @param showEditorTab if true, front the editor tab after opening the file
   2000      * @return the editor that was opened, or null if no editor was opened
   2001      * @throws PartInitException if something goes wrong
   2002      */
   2003     public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab)
   2004             throws PartInitException {
   2005         IWorkbenchPage page = AdtUtils.getActiveWorkbenchPage();
   2006         if (page == null) {
   2007             return null;
   2008         }
   2009         IEditorPart targetEditor = IDE.openEditor(page, file, true);
   2010         if (targetEditor instanceof AndroidXmlEditor) {
   2011             AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor;
   2012             if (region != null) {
   2013                 editor.show(region.getOffset(), region.getLength(), showEditorTab);
   2014             } else if (showEditorTab) {
   2015                 editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
   2016             }
   2017         } else if (targetEditor instanceof AbstractTextEditor) {
   2018             AbstractTextEditor editor = (AbstractTextEditor) targetEditor;
   2019             if (region != null) {
   2020                 editor.setHighlightRange(region.getOffset(), region.getLength(),
   2021                         true /* moveCursor*/);
   2022             }
   2023         }
   2024 
   2025         return targetEditor;
   2026     }
   2027 }
   2028