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