com.android.ide.eclipse.adt.AdtPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.android.ide.eclipse.adt.AdtPlugin.java

Source

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt;

import static com.android.SdkConstants.CURRENT_PLATFORM;
import static com.android.SdkConstants.PLATFORM_DARWIN;
import static com.android.SdkConstants.PLATFORM_LINUX;
import static com.android.SdkConstants.PLATFORM_WINDOWS;

import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler.Solution;
import com.android.ide.eclipse.adt.internal.VersionCheck;
import com.android.ide.eclipse.adt.internal.actions.SdkManagerAction;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.io.StreamException;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
import com.android.utils.ILogger;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;

import org.eclipse.core.commands.Command;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.wb.internal.core.DesignerPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * The activator class controls the plug-in life cycle
 */
public class AdtPlugin extends AbstractUIPlugin implements ILogger {
    /** The plug-in ID */
    public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$

    /** singleton instance */
    private static AdtPlugin sPlugin;

    private static Image sAndroidLogo;
    private static ImageDescriptor sAndroidLogoDesc;

    /** The global android console */
    private MessageConsole mAndroidConsole;

    /** Stream to write in the android console */
    private MessageConsoleStream mAndroidConsoleStream;

    /** Stream to write error messages to the android console */
    private MessageConsoleStream mAndroidConsoleErrorStream;

    /** Color used in the error console */
    private Color mRed;

    /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
    private LoadStatus mSdkLoadedStatus = LoadStatus.LOADING;
    /** Project to update once the SDK is loaded.
     * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
    private final Set<IJavaProject> mPostLoadProjectsToResolve = Sets.newHashSet();
    /** Project to check validity of cache vs actual once the SDK is loaded.
     * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
    private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();

    private GlobalProjectMonitor mResourceMonitor;
    private ArrayList<ITargetChangeListener> mTargetChangeListeners = new ArrayList<ITargetChangeListener>();

    /**
     * This variable indicates that the job inside parseSdkContent() is currently
     * trying to load the SDK, to avoid re-entrance.
     * To check whether this succeeds or not, please see {@link #getSdkLoadStatus()}.
     */
    private volatile boolean mParseSdkContentIsRunning;

    /**
     * An error handler for checkSdkLocationAndId() that will handle the generated error
     * or warning message. Each method must return a boolean that will in turn be returned by
     * checkSdkLocationAndId.
     */
    public static abstract class CheckSdkErrorHandler {

        public enum Solution {
            NONE, OPEN_SDK_MANAGER, OPEN_ANDROID_PREFS, OPEN_P2_UPDATE
        }

        /**
         * Handle an error message during sdk location check. Returns whatever
         * checkSdkLocationAndId() should returns.
         */
        public abstract boolean handleError(Solution solution, String message);

        /**
         * Handle a warning message during sdk location check. Returns whatever
         * checkSdkLocationAndId() should returns.
         */
        public abstract boolean handleWarning(Solution solution, String message);
    }

    /**
     * The constructor
     */
    public AdtPlugin() {
        sPlugin = this;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start(BundleContext context) throws Exception {
        super.start(context);

        // set the default android console.
        mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
        ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { mAndroidConsole });

        // get the stream to write in the android console.
        mAndroidConsoleStream = mAndroidConsole.newMessageStream();
        mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();

        // get the eclipse store
        IPreferenceStore eclipseStore = getPreferenceStore();
        AdtPrefs.init(eclipseStore);

        // set the listener for the preference change
        eclipseStore.addPropertyChangeListener(new IPropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent event) {
                // load the new preferences
                AdtPrefs.getPrefs().loadValues(event);

                // if the SDK changed, we have to do some extra work
                if (AdtPrefs.PREFS_SDK_DIR.equals(event.getProperty())) {

                    // finally restart adb, in case it's a different version
                    DdmsPlugin.setToolsLocation(getOsAbsoluteAdb(), true /* startAdb */, getOsAbsoluteHprofConv(),
                            getOsAbsoluteTraceview());

                    // get the SDK location and build id.
                    if (checkSdkLocationAndId()) {
                        // if sdk if valid, reparse it

                        reparseSdk();
                    }
                }
            }
        });

        // load preferences.
        AdtPrefs.getPrefs().loadValues(null /*event*/);

        // initialize property-sheet library
        DesignerPlugin.initialize(this, PLUGIN_ID, CURRENT_PLATFORM == PLATFORM_WINDOWS,
                CURRENT_PLATFORM == PLATFORM_DARWIN, CURRENT_PLATFORM == PLATFORM_LINUX);

        // initialize editors
        startEditors();

        // Listen on resource file edits for updates to file inclusion
        IncludeFinder.start();
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop(BundleContext context) throws Exception {
        super.stop(context);

        stopEditors();
        IncludeFinder.stop();

        DesignerPlugin.dispose();

        if (mRed != null) {
            mRed.dispose();
            mRed = null;
        }

        synchronized (AdtPlugin.class) {
            sPlugin = null;
        }
    }

    /** Called when the workbench has been started */
    public void workbenchStarted() {
        // Parse the SDK content.
        // This is deferred in separate jobs to avoid blocking the bundle start.
        final boolean isSdkLocationValid = checkSdkLocationAndId();
        if (isSdkLocationValid) {
            // parse the SDK resources.
            // Wait 2 seconds before starting the job. This leaves some time to the
            // other bundles to initialize.
            parseSdkContent(2000 /*milliseconds*/);
        }

        Display display = getDisplay();
        mRed = new Color(display, 0xFF, 0x00, 0x00);

        // because this can be run, in some cases, by a non ui thread, and because
        // changing the console properties update the ui, we need to make this change
        // in the ui thread.
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                mAndroidConsoleErrorStream.setColor(mRed);
            }
        });
    }

    /**
     * Returns the shared instance
     *
     * @return the shared instance
     */
    public static synchronized AdtPlugin getDefault() {
        return sPlugin;
    }

    /**
     * Returns the current display, if any
     *
     * @return the display
     */
    @NonNull
    public static Display getDisplay() {
        synchronized (AdtPlugin.class) {
            if (sPlugin != null) {
                IWorkbench bench = sPlugin.getWorkbench();
                if (bench != null) {
                    Display display = bench.getDisplay();
                    if (display != null) {
                        return display;
                    }
                }
            }
        }

        Display display = Display.getCurrent();
        if (display != null) {
            return display;
        }

        return Display.getDefault();
    }

    /**
     * Returns the shell, if any
     *
     * @return the shell, if any
     */
    @Nullable
    public static Shell getShell() {
        Display display = AdtPlugin.getDisplay();
        Shell shell = display.getActiveShell();
        if (shell == null) {
            Shell[] shells = display.getShells();
            if (shells.length > 0) {
                shell = shells[0];
            }
        }

        return shell;
    }

    /** Returns the adb path relative to the sdk folder */
    public static String getOsRelativeAdb() {
        return SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_ADB;
    }

    /** Returns the emulator path relative to the sdk folder */
    public static String getOsRelativeEmulator() {
        return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR;
    }

    /** Returns the adb path relative to the sdk folder */
    public static String getOsRelativeProguard() {
        return SdkConstants.OS_SDK_TOOLS_PROGUARD_BIN_FOLDER + SdkConstants.FN_PROGUARD;
    }

    /** Returns the absolute adb path */
    public static String getOsAbsoluteAdb() {
        return getOsSdkFolder() + getOsRelativeAdb();
    }

    /** Returns the absolute traceview path */
    public static String getOsAbsoluteTraceview() {
        return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + AdtConstants.FN_TRACEVIEW;
    }

    /** Returns the absolute emulator path */
    public static String getOsAbsoluteEmulator() {
        return getOsSdkFolder() + getOsRelativeEmulator();
    }

    public static String getOsAbsoluteHprofConv() {
        return getOsSdkFolder() + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + AdtConstants.FN_HPROF_CONV;
    }

    /** Returns the absolute proguard path */
    public static String getOsAbsoluteProguard() {
        return getOsSdkFolder() + getOsRelativeProguard();
    }

    /**
     * Returns a Url file path to the javaDoc folder.
     */
    public static String getUrlDoc() {
        return ProjectHelper.getJavaDocPath(getOsSdkFolder() + AdtConstants.WS_JAVADOC_FOLDER_LEAF);
    }

    /**
     * Returns the SDK folder.
     * Guaranteed to be terminated by a platform-specific path separator.
     */
    public static synchronized String getOsSdkFolder() {
        if (sPlugin == null) {
            return null;
        }

        return AdtPrefs.getPrefs().getOsSdkFolder();
    }

    public static String getOsSdkToolsFolder() {
        return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
    }

    /**
     * Returns an image descriptor for the image file at the given
     * plug-in relative path
     *
     * @param path the path
     * @return the image descriptor
     */
    public static ImageDescriptor getImageDescriptor(String path) {
        return imageDescriptorFromPlugin(PLUGIN_ID, path);
    }

    /**
     * Reads the contents of an {@link IFile} and return it as a String
     *
     * @param file the file to be read
     * @return the String read from the file, or null if there was an error
     */
    @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet
    @Nullable
    public static String readFile(@NonNull IFile file) {
        InputStream contents = null;
        InputStreamReader reader = null;
        try {
            contents = file.getContents();
            String charset = file.getCharset();
            reader = new InputStreamReader(contents, charset);
            return readFile(reader);
        } catch (CoreException e) {
            // pass -- ignore files we can't read
        } catch (IOException e) {
            // pass -- ignore files we can't read.

            // Note that IFile.getContents() indicates it throws a CoreException but
            // experience shows that if the file does not exists it really throws
            // IOException.
            // New InputStreamReader() throws UnsupportedEncodingException
            // which is handled by this IOException catch.

        } finally {
            Closeables.closeQuietly(reader);
            Closeables.closeQuietly(contents);
        }

        return null;
    }

    /**
     * Reads the contents of an {@link File} and return it as a String
     *
     * @param file the file to be read
     * @return the String read from the file, or null if there was an error
     */
    public static String readFile(File file) {
        try {
            return readFile(new FileReader(file));
        } catch (FileNotFoundException e) {
            AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
        }

        return null;
    }

    /**
     * Writes the given content out to the given {@link File}. The file will be deleted if
     * it already exists.
     *
     * @param file the target file
     * @param content the content to be written into the file
     */
    public static void writeFile(File file, String content) {
        if (file.exists()) {
            file.delete();
        }
        FileWriter fw = null;
        try {
            fw = new FileWriter(file);
            fw.write(content);
        } catch (IOException e) {
            AdtPlugin.log(e, null);
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    AdtPlugin.log(e, null);
                }
            }
        }
    }

    /**
     * Returns true iff the given file contains the given String.
     *
     * @param file the file to look for the string in
     * @param string the string to be searched for
     * @return true if the file is found and contains the given string anywhere within it
     */
    @SuppressWarnings("resource") // Closed by streamContains
    public static boolean fileContains(IFile file, String string) {
        InputStream contents = null;
        try {
            contents = file.getContents();
            String charset = file.getCharset();
            return streamContains(new InputStreamReader(contents, charset), string);
        } catch (Exception e) {
            AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
        }

        return false;
    }

    /**
     * Returns true iff the given file contains the given String.
     *
     * @param file the file to look for the string in
     * @param string the string to be searched for
     * @return true if the file is found and contains the given string anywhere within it
     */
    public static boolean fileContains(File file, String string) {
        try {
            return streamContains(new FileReader(file), string);
        } catch (Exception e) {
            AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$
        }

        return false;
    }

    /**
     * Returns true iff the given input stream contains the given String.
     *
     * @param r the stream to look for the string in
     * @param string the string to be searched for
     * @return true if the file is found and contains the given string anywhere within it
     */
    public static boolean streamContains(Reader r, String string) {
        if (string.length() == 0) {
            return true;
        }

        PushbackReader reader = null;
        try {
            reader = new PushbackReader(r, string.length());
            char first = string.charAt(0);
            while (true) {
                int c = reader.read();
                if (c == -1) {
                    return false;
                } else if (c == first) {
                    boolean matches = true;
                    for (int i = 1; i < string.length(); i++) {
                        c = reader.read();
                        if (c == -1) {
                            return false;
                        } else if (string.charAt(i) != (char) c) {
                            matches = false;
                            // Back up the characters that did not match
                            reader.backup(i - 1);
                            break;
                        }
                    }
                    if (matches) {
                        return true;
                    }
                }
            }
        } catch (Exception e) {
            AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$
            }
        }

        return false;

    }

    /**
     * A special reader that allows backing up in the input (up to a predefined maximum
     * number of characters)
     * <p>
     * NOTE: This class ONLY works with the {@link #read()} method!!
     */
    private static class PushbackReader extends BufferedReader {
        /**
         * Rolling/circular buffer. Can be a char rather than int since we never store EOF
         * in it.
         */
        private char[] mStorage;

        /** Points to the head of the queue. When equal to the tail, the queue is empty. */
        private int mHead;

        /**
         * Points to the tail of the queue. This will move with each read of the actual
         * wrapped reader, and the characters previous to it in the circular buffer are
         * the most recently read characters.
         */
        private int mTail;

        /**
         * Creates a new reader with a given maximum number of backup characters
         *
         * @param reader the reader to wrap
         * @param max the maximum number of characters to allow rollback for
         */
        public PushbackReader(Reader reader, int max) {
            super(reader);
            mStorage = new char[max + 1];
        }

        @Override
        public int read() throws IOException {
            // Have we backed up? If so we should serve characters
            // from the storage
            if (mHead != mTail) {
                char c = mStorage[mHead];
                mHead = (mHead + 1) % mStorage.length;
                return c;
            }
            assert mHead == mTail;

            // No backup -- read the next character, but stash it into storage
            // as well such that we can retrieve it if we must.
            int c = super.read();
            mStorage[mHead] = (char) c;
            mHead = mTail = (mHead + 1) % mStorage.length;
            return c;
        }

        /**
         * Backs up the reader a given number of characters. The next N reads will yield
         * the N most recently read characters prior to this backup.
         *
         * @param n the number of characters to be backed up
         */
        public void backup(int n) {
            if (n >= mStorage.length) {
                throw new IllegalArgumentException("Exceeded backup limit");
            }
            assert n < mStorage.length;
            mHead -= n;
            if (mHead < 0) {
                mHead += mStorage.length;
            }
        }
    }

    /**
     * Reads the contents of a {@link ResourceFile} and returns it as a String
     *
     * @param file the file to be read
     * @return the contents as a String, or null if reading failed
     */
    public static String readFile(ResourceFile file) {
        InputStream contents = null;
        try {
            contents = file.getFile().getContents();
            return readFile(new InputStreamReader(contents));
        } catch (StreamException e) {
            // pass -- ignore files we can't read
        } finally {
            try {
                if (contents != null) {
                    contents.close();
                }
            } catch (IOException e) {
                AdtPlugin.log(e, "Can't read layout file"); //$NON-NLS-1$
            }
        }

        return null;
    }

    /**
     * Reads the contents of a {@link Reader} and return it as a String. This
     * method will close the input reader.
     *
     * @param reader the reader to be read from
     * @return the String read from reader, or null if there was an error
     */
    public static String readFile(Reader reader) {
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(reader);
            StringBuilder sb = new StringBuilder(2000);
            while (true) {
                int c = bufferedReader.read();
                if (c == -1) {
                    return sb.toString();
                } else {
                    sb.append((char) c);
                }
            }
        } catch (IOException e) {
            // pass -- ignore files we can't read
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (IOException e) {
                AdtPlugin.log(e, "Can't read input stream"); //$NON-NLS-1$
            }
        }

        return null;
    }

    /**
     * Reads and returns the content of a text file embedded in the plugin jar
     * file.
     * @param filepath the file path to the text file
     * @return null if the file could not be read
     */
    public static String readEmbeddedTextFile(String filepath) {
        try {
            InputStream is = readEmbeddedFileAsStream(filepath);
            if (is != null) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                try {
                    String line;
                    StringBuilder total = new StringBuilder(reader.readLine());
                    while ((line = reader.readLine()) != null) {
                        total.append('\n');
                        total.append(line);
                    }

                    return total.toString();
                } finally {
                    reader.close();
                }
            }
        } catch (IOException e) {
            // we'll just return null
            AdtPlugin.log(e, "Failed to read text file '%s'", filepath); //$NON-NLS-1$
        }

        return null;
    }

    /**
     * Reads and returns the content of a binary file embedded in the plugin jar
     * file.
     * @param filepath the file path to the text file
     * @return null if the file could not be read
     */
    public static byte[] readEmbeddedFile(String filepath) {
        try {
            InputStream is = readEmbeddedFileAsStream(filepath);
            if (is != null) {
                // create a buffered reader to facilitate reading.
                BufferedInputStream stream = new BufferedInputStream(is);
                try {
                    // get the size to read.
                    int avail = stream.available();

                    // create the buffer and reads it.
                    byte[] buffer = new byte[avail];
                    stream.read(buffer);

                    // and return.
                    return buffer;
                } finally {
                    stream.close();
                }
            }
        } catch (IOException e) {
            // we'll just return null;.
            AdtPlugin.log(e, "Failed to read binary file '%s'", filepath); //$NON-NLS-1$
        }

        return null;
    }

    /**
     * Reads and returns the content of a binary file embedded in the plugin jar
     * file.
     * @param filepath the file path to the text file
     * @return null if the file could not be read
     */
    public static InputStream readEmbeddedFileAsStream(String filepath) {
        // attempt to read an embedded file
        try {
            URL url = getEmbeddedFileUrl(AdtConstants.WS_SEP + filepath);
            if (url != null) {
                return url.openStream();
            }
        } catch (MalformedURLException e) {
            // we'll just return null.
            AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$
        } catch (IOException e) {
            // we'll just return null;.
            AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$
        }

        return null;
    }

    /**
     * Returns the URL of a binary file embedded in the plugin jar file.
     * @param filepath the file path to the text file
     * @return null if the file was not found.
     */
    public static URL getEmbeddedFileUrl(String filepath) {
        Bundle bundle = null;
        synchronized (AdtPlugin.class) {
            if (sPlugin != null) {
                bundle = sPlugin.getBundle();
            } else {
                AdtPlugin.log(IStatus.WARNING, "ADT Plugin is missing"); //$NON-NLS-1$
                return null;
            }
        }

        // attempt to get a file to one of the template.
        String path = filepath;
        if (!path.startsWith(AdtConstants.WS_SEP)) {
            path = AdtConstants.WS_SEP + path;
        }

        URL url = bundle.getEntry(path);

        if (url == null) {
            AdtPlugin.log(IStatus.INFO, "Bundle file URL not found at path '%s'", path); //$NON-NLS-1$
        }

        return url;
    }

    /**
     * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread,
     * therefore this method can be called from any thread.
     * @param title The title of the dialog box
     * @param message The error message
     */
    public final static void displayError(final String title, final String message) {
        // get the current Display
        final Display display = getDisplay();

        // dialog box only run in ui thread..
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                Shell shell = display.getActiveShell();
                MessageDialog.openError(shell, title, message);
            }
        });
    }

    /**
     * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread,
     * therefore this method can be called from any thread.
     * @param title The title of the dialog box
     * @param message The warning message
     */
    public final static void displayWarning(final String title, final String message) {
        // get the current Display
        final Display display = getDisplay();

        // dialog box only run in ui thread..
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                Shell shell = display.getActiveShell();
                MessageDialog.openWarning(shell, title, message);
            }
        });
    }

    /**
     * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread,
     * therefore this message can be called from any thread.
     * @param title The title of the dialog box
     * @param message The error message
     * @return true if OK was clicked.
     */
    public final static boolean displayPrompt(final String title, final String message) {
        // get the current Display and Shell
        final Display display = getDisplay();

        // we need to ask the user what he wants to do.
        final boolean[] result = new boolean[1];
        display.syncExec(new Runnable() {
            @Override
            public void run() {
                Shell shell = display.getActiveShell();
                result[0] = MessageDialog.openQuestion(shell, title, message);
            }
        });
        return result[0];
    }

    /**
     * Logs a message to the default Eclipse log.
     *
     * @param severity The severity code. Valid values are: {@link IStatus#OK},
     * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
     * {@link IStatus#CANCEL}.
     * @param format The format string, like for {@link String#format(String, Object...)}.
     * @param args The arguments for the format string, like for
     * {@link String#format(String, Object...)}.
     */
    public static void log(int severity, String format, Object... args) {
        if (format == null) {
            return;
        }

        String message = String.format(format, args);
        Status status = new Status(severity, PLUGIN_ID, message);

        if (getDefault() != null) {
            getDefault().getLog().log(status);
        } else {
            // During UnitTests, we generally don't have a plugin object. It's ok
            // to log to stdout or stderr in this case.
            (severity < IStatus.ERROR ? System.out : System.err).println(status.toString());
        }
    }

    /**
     * Logs an exception to the default Eclipse log.
     * <p/>
     * The status severity is always set to ERROR.
     *
     * @param exception the exception to log.
     * @param format The format string, like for {@link String#format(String, Object...)}.
     * @param args The arguments for the format string, like for
     * {@link String#format(String, Object...)}.
     */
    public static void log(Throwable exception, String format, Object... args) {
        String message = null;
        if (format != null) {
            message = String.format(format, args);
        } else {
            message = "";
        }
        Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);

        if (getDefault() != null) {
            getDefault().getLog().log(status);
        } else {
            // During UnitTests, we generally don't have a plugin object. It's ok
            // to log to stderr in this case.
            System.err.println(status.toString());
        }
    }

    /**
     * This is a mix between log(Throwable) and printErrorToConsole.
     * <p/>
     * This logs the exception with an ERROR severity and the given printf-like format message.
     * The same message is then printed on the Android error console with the associated tag.
     *
     * @param exception the exception to log.
     * @param format The format string, like for {@link String#format(String, Object...)}.
     * @param args The arguments for the format string, like for
     * {@link String#format(String, Object...)}.
     */
    public static synchronized void logAndPrintError(Throwable exception, String tag, String format,
            Object... args) {
        if (sPlugin != null) {
            String message = String.format(format, args);
            Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception);
            getDefault().getLog().log(status);
            printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message);
            showAndroidConsole();
        }
    }

    /**
     * Prints one or more error message to the android console.
     * @param tag A tag to be associated with the message. Can be null.
     * @param objects the objects to print through their <code>toString</code> method.
     */
    public static synchronized void printErrorToConsole(String tag, Object... objects) {
        if (sPlugin != null) {
            printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects);

            showAndroidConsole();
        }
    }

    /**
     * Prints one or more error message to the android console.
     * @param objects the objects to print through their <code>toString</code> method.
     */
    public static void printErrorToConsole(Object... objects) {
        printErrorToConsole((String) null, objects);
    }

    /**
     * Prints one or more error message to the android console.
     * @param project The project to which the message is associated. Can be null.
     * @param objects the objects to print through their <code>toString</code> method.
     */
    public static void printErrorToConsole(IProject project, Object... objects) {
        String tag = project != null ? project.getName() : null;
        printErrorToConsole(tag, objects);
    }

    /**
     * Prints one or more build messages to the android console, filtered by Build output verbosity.
     * @param level {@link BuildVerbosity} level of the message.
     * @param project The project to which the message is associated. Can be null.
     * @param objects the objects to print through their <code>toString</code> method.
     * @see BuildVerbosity#ALWAYS
     * @see BuildVerbosity#NORMAL
     * @see BuildVerbosity#VERBOSE
     */
    public static synchronized void printBuildToConsole(BuildVerbosity level, IProject project, Object... objects) {
        if (sPlugin != null) {
            if (level.getLevel() <= AdtPrefs.getPrefs().getBuildVerbosity().getLevel()) {
                String tag = project != null ? project.getName() : null;
                printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
            }
        }
    }

    /**
     * Prints one or more message to the android console.
     * @param tag The tag to be associated with the message. Can be null.
     * @param objects the objects to print through their <code>toString</code> method.
     */
    public static synchronized void printToConsole(String tag, Object... objects) {
        if (sPlugin != null) {
            printToStream(sPlugin.mAndroidConsoleStream, tag, objects);
        }
    }

    /**
     * Prints one or more message to the android console.
     * @param project The project to which the message is associated. Can be null.
     * @param objects the objects to print through their <code>toString</code> method.
     */
    public static void printToConsole(IProject project, Object... objects) {
        String tag = project != null ? project.getName() : null;
        printToConsole(tag, objects);
    }

    /** Force the display of the android console */
    public static void showAndroidConsole() {
        // first make sure the console is in the workbench
        EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true);

        // now make sure it's not docked.
        ConsolePlugin.getDefault().getConsoleManager().showConsoleView(AdtPlugin.getDefault().getAndroidConsole());
    }

    /**
     * Returns whether the {@link IAndroidTarget}s have been loaded from the SDK.
     */
    public final LoadStatus getSdkLoadStatus() {
        synchronized (Sdk.getLock()) {
            return mSdkLoadedStatus;
        }
    }

    /**
     * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes
     * to load.
     */
    public final void setProjectToResolve(IJavaProject javaProject) {
        synchronized (Sdk.getLock()) {
            mPostLoadProjectsToResolve.add(javaProject);
        }
    }

    /**
     * Sets the given {@link IJavaProject} to have its target checked for consistency
     * once the SDK finishes to load. This is used if the target is resolved using cached
     * information while the SDK is loading.
     */
    public final void setProjectToCheck(IJavaProject javaProject) {
        // only lock on
        synchronized (Sdk.getLock()) {
            mPostLoadProjectsToCheck.add(javaProject);
        }
    }

    /**
     * Checks the location of the SDK in the prefs is valid.
     * If it is not, display a warning dialog to the user and try to display
     * some useful link to fix the situation (setup the preferences, perform an
     * update, etc.)
     *
     * @return True if the SDK location points to an SDK.
     *  If false, the user has already been presented with a modal dialog explaining that.
     */
    public boolean checkSdkLocationAndId() {
        String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder();

        return checkSdkLocationAndId(sdkLocation, new CheckSdkErrorHandler() {
            private String mTitle = "Android SDK";

            /**
             * Handle an error, which is the case where the check did not find any SDK.
             * This returns false to {@link AdtPlugin#checkSdkLocationAndId()}.
             */
            @Override
            public boolean handleError(Solution solution, String message) {
                displayMessage(solution, message, MessageDialog.ERROR);
                return false;
            }

            /**
             * Handle an warning, which is the case where the check found an SDK
             * but it might need to be repaired or is missing an expected component.
             *
             * This returns true to {@link AdtPlugin#checkSdkLocationAndId()}.
             */
            @Override
            public boolean handleWarning(Solution solution, String message) {
                displayMessage(solution, message, MessageDialog.WARNING);
                return true;
            }

            private void displayMessage(final Solution solution, final String message, final int dialogImageType) {
                final Display disp = getDisplay();
                disp.asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        Shell shell = disp.getActiveShell();
                        if (shell == null) {
                            shell = AdtPlugin.getShell();
                        }
                        if (shell == null) {
                            return;
                        }

                        String customLabel = null;
                        switch (solution) {
                        case OPEN_ANDROID_PREFS:
                            customLabel = "Open Preferences";
                            break;
                        case OPEN_P2_UPDATE:
                            customLabel = "Check for Updates";
                            break;
                        case OPEN_SDK_MANAGER:
                            customLabel = "Open SDK Manager";
                            break;
                        }

                        String btnLabels[] = new String[customLabel == null ? 1 : 2];
                        btnLabels[0] = customLabel;
                        btnLabels[btnLabels.length - 1] = IDialogConstants.CLOSE_LABEL;

                        MessageDialog dialog = new MessageDialog(shell, // parent
                                mTitle, null, // dialogTitleImage
                                message, dialogImageType, btnLabels, btnLabels.length - 1);
                        int index = dialog.open();

                        if (customLabel != null && index == 0) {
                            switch (solution) {
                            case OPEN_ANDROID_PREFS:
                                openAndroidPrefs();
                                break;
                            case OPEN_P2_UPDATE:
                                openP2Update();
                                break;
                            case OPEN_SDK_MANAGER:
                                openSdkManager();
                                break;
                            }
                        }
                    }
                });
            }

            private void openSdkManager() {
                // Open the standalone external SDK Manager since we know
                // that ADT on Windows is bound to be locking some SDK folders.
                //
                // Also when this is invoked because SdkManagerAction.run() fails, this
                // test will fail and we'll fallback on using the internal one.
                if (SdkManagerAction.openExternalSdkManager()) {
                    return;
                }

                // Otherwise open the regular SDK Manager bundled within ADT
                if (!SdkManagerAction.openAdtSdkManager()) {
                    // We failed because the SDK location is undefined. In this case
                    // let's open the preferences instead.
                    openAndroidPrefs();
                }
            }

            private void openP2Update() {
                Display disp = getDisplay();
                if (disp == null) {
                    return;
                }
                disp.asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        String cmdId = "org.eclipse.equinox.p2.ui.sdk.update"; //$NON-NLS-1$
                        IWorkbench wb = PlatformUI.getWorkbench();
                        if (wb == null) {
                            return;
                        }

                        ICommandService cs = (ICommandService) wb.getService(ICommandService.class);
                        IHandlerService is = (IHandlerService) wb.getService(IHandlerService.class);
                        if (cs == null || is == null) {
                            return;
                        }

                        Command cmd = cs.getCommand(cmdId);
                        if (cmd != null && cmd.isDefined()) {
                            try {
                                is.executeCommand(cmdId, null/*event*/);
                            } catch (Exception ignore) {
                                AdtPlugin.log(ignore, "Failed to execute command %s", cmdId);
                            }
                        }
                    }
                });
            }

            private void openAndroidPrefs() {
                PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(getDisplay().getActiveShell(),
                        "com.android.ide.eclipse.preferences.main", //$NON-NLS-1$ preferencePageId
                        null, // displayedIds
                        null); // data
                dialog.open();
            }
        });
    }

    /**
     * Internal helper to perform the actual sdk location and id check.
     * <p/>
     * This is useful for callers who want to override what happens when the check
     * fails. Otherwise consider calling {@link #checkSdkLocationAndId()} that will
     * present a modal dialog to the user in case of failure.
     *
     * @param osSdkLocation The sdk directory, an OS path. Can be null.
     * @param errorHandler An checkSdkErrorHandler that can display a warning or an error.
     * @return False if there was an error or the result from the errorHandler invocation.
     */
    public boolean checkSdkLocationAndId(@Nullable String osSdkLocation,
            @NonNull CheckSdkErrorHandler errorHandler) {
        if (osSdkLocation == null || osSdkLocation.trim().length() == 0) {
            return errorHandler.handleError(Solution.OPEN_ANDROID_PREFS,
                    "Location of the Android SDK has not been setup in the preferences.");
        }

        if (!osSdkLocation.endsWith(File.separator)) {
            osSdkLocation = osSdkLocation + File.separator;
        }

        File osSdkFolder = new File(osSdkLocation);
        if (osSdkFolder.isDirectory() == false) {
            return errorHandler.handleError(Solution.OPEN_ANDROID_PREFS,
                    String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
        }

        String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
        File toolsFolder = new File(osTools);
        if (toolsFolder.isDirectory() == false) {
            return errorHandler.handleError(Solution.OPEN_ANDROID_PREFS,
                    String.format(Messages.Could_Not_Find_Folder_In_SDK, SdkConstants.FD_TOOLS, osSdkLocation));
        }

        // first check the min plug-in requirement as its error message is easier to figure
        // out for the user
        if (VersionCheck.checkVersion(osSdkLocation, errorHandler) == false) {
            return false;
        }

        // check that we have both the tools component and the platform-tools component.
        String platformTools = osSdkLocation + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER;
        if (checkFolder(platformTools) == false) {
            return errorHandler.handleWarning(Solution.OPEN_SDK_MANAGER,
                    "SDK Platform Tools component is missing!\n" + "Please use the SDK Manager to install it.");
        }

        String tools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
        if (checkFolder(tools) == false) {
            return errorHandler.handleError(Solution.OPEN_SDK_MANAGER,
                    "SDK Tools component is missing!\n" + "Please use the SDK Manager to install it.");
        }

        // check the path to various tools we use to make sure nothing is missing. This is
        // not meant to be exhaustive.
        String[] filesToCheck = new String[] { osSdkLocation + getOsRelativeAdb(),
                osSdkLocation + getOsRelativeEmulator() };
        for (String file : filesToCheck) {
            if (checkFile(file) == false) {
                return errorHandler.handleError(Solution.OPEN_ANDROID_PREFS,
                        String.format(Messages.Could_Not_Find, file));
            }
        }

        return true;
    }

    /**
     * Checks if a path reference a valid existing file.
     * @param osPath the os path to check.
     * @return true if the file exists and is, in fact, a file.
     */
    private boolean checkFile(String osPath) {
        File file = new File(osPath);
        if (file.isFile() == false) {
            return false;
        }

        return true;
    }

    /**
     * Checks if a path reference a valid existing folder.
     * @param osPath the os path to check.
     * @return true if the folder exists and is, in fact, a folder.
     */
    private boolean checkFolder(String osPath) {
        File file = new File(osPath);
        if (file.isDirectory() == false) {
            return false;
        }

        return true;
    }

    /**
     * Parses the SDK resources.
     */
    private void parseSdkContent(long delay) {
        // Perform the update in a thread (here an Eclipse runtime job)
        // since this should never block the caller (especially the start method)
        Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
            @SuppressWarnings("unchecked")
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {

                    if (mParseSdkContentIsRunning) {
                        return new Status(IStatus.WARNING, PLUGIN_ID,
                                "An Android SDK is already being loaded. Please try again later.");
                    }

                    mParseSdkContentIsRunning = true;

                    SubMonitor progress = SubMonitor.convert(monitor, "Initialize SDK Manager", 100);

                    Sdk sdk = Sdk.loadSdk(AdtPrefs.getPrefs().getOsSdkFolder());

                    if (sdk != null) {
                        ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
                        synchronized (Sdk.getLock()) {
                            mSdkLoadedStatus = LoadStatus.LOADED;

                            progress.setTaskName("Check Projects");

                            for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
                                IProject iProject = javaProject.getProject();
                                if (iProject.isOpen()) {
                                    // project that have been resolved before the sdk was loaded
                                    // will have a ProjectState where the IAndroidTarget is null
                                    // so we load the target now that the SDK is loaded.
                                    sdk.loadTargetAndBuildTools(Sdk.getProjectState(iProject));
                                    list.add(javaProject);
                                }
                            }

                            // done with this list.
                            mPostLoadProjectsToResolve.clear();
                        }

                        // check the projects that need checking.
                        // The method modifies the list (it removes the project that
                        // do not need to be resolved again).
                        AndroidClasspathContainerInitializer.checkProjectsCache(mPostLoadProjectsToCheck);

                        list.addAll(mPostLoadProjectsToCheck);

                        // update the project that needs recompiling.
                        if (list.size() > 0) {
                            IJavaProject[] array = list.toArray(new IJavaProject[list.size()]);
                            ProjectHelper.updateProjects(array);
                        }

                        progress.worked(10);
                    } else {
                        // SDK failed to Load!
                        // Sdk#loadSdk() has already displayed an error.
                        synchronized (Sdk.getLock()) {
                            mSdkLoadedStatus = LoadStatus.FAILED;
                        }
                    }

                    // Notify resource changed listeners
                    progress.setTaskName("Refresh UI");
                    progress.setWorkRemaining(mTargetChangeListeners.size());

                    // Clone the list before iterating, to avoid ConcurrentModification
                    // exceptions
                    final List<ITargetChangeListener> listeners = (List<ITargetChangeListener>) mTargetChangeListeners
                            .clone();
                    final SubMonitor progress2 = progress;
                    AdtPlugin.getDisplay().asyncExec(new Runnable() {
                        @Override
                        public void run() {
                            for (ITargetChangeListener listener : listeners) {
                                try {
                                    listener.onSdkLoaded();
                                } catch (Exception e) {
                                    AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
                                } finally {
                                    progress2.worked(1);
                                }
                            }
                        }
                    });
                } catch (Throwable t) {
                    log(t, "Unknown exception in parseSdkContent."); //$NON-NLS-1$
                    return new Status(IStatus.ERROR, PLUGIN_ID, "parseSdkContent failed", t); //$NON-NLS-1$

                } finally {
                    mParseSdkContentIsRunning = false;
                    if (monitor != null) {
                        monitor.done();
                    }
                }

                return Status.OK_STATUS;
            }
        };
        job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
        job.setRule(ResourcesPlugin.getWorkspace().getRoot());
        if (delay > 0) {
            job.schedule(delay);
        } else {
            job.schedule();
        }
    }

    /** Returns the global android console */
    public MessageConsole getAndroidConsole() {
        return mAndroidConsole;
    }

    // ----- Methods for Editors -------

    public void startEditors() {
        sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID, "/icons/android.png"); //$NON-NLS-1$
        sAndroidLogo = sAndroidLogoDesc.createImage();

        // Add a resource listener to handle compiled resources.
        IWorkspace ws = ResourcesPlugin.getWorkspace();
        mResourceMonitor = GlobalProjectMonitor.startMonitoring(ws);

        if (mResourceMonitor != null) {
            try {
                setupEditors(mResourceMonitor);
                ResourceManager.setup(mResourceMonitor);
                LintDeltaProcessor.startListening(mResourceMonitor);
            } catch (Throwable t) {
                log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
            }
        }
    }

    /**
     * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
     * method saves this plug-in's preference and dialog stores and shuts down
     * its image registry (if they are in use). Subclasses may extend this
     * method, but must send super <b>last</b>. A try-finally statement should
     * be used where necessary to ensure that <code>super.shutdown()</code> is
     * always done.
     *
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
     */
    public void stopEditors() {
        sAndroidLogo.dispose();

        IconFactory.getInstance().dispose();

        LintDeltaProcessor.stopListening(mResourceMonitor);

        // Remove the resource listener that handles compiled resources.
        IWorkspace ws = ResourcesPlugin.getWorkspace();
        GlobalProjectMonitor.stopMonitoring(ws);

        if (mRed != null) {
            mRed.dispose();
            mRed = null;
        }
    }

    /**
     * Returns an Image for the small Android logo.
     *
     * Callers should not dispose it.
     */
    public static Image getAndroidLogo() {
        return sAndroidLogo;
    }

    /**
     * Returns an {@link ImageDescriptor} for the small Android logo.
     *
     * Callers should not dispose it.
     */
    public static ImageDescriptor getAndroidLogoDesc() {
        return sAndroidLogoDesc;
    }

    /**
     * Returns the ResourceMonitor object.
     */
    public GlobalProjectMonitor getResourceMonitor() {
        return mResourceMonitor;
    }

    /**
     * Sets up the editor resource listener.
     * <p>
     * The listener handles:
     * <ul>
     * <li> Discovering newly created files, and ensuring that if they are in an Android
     *      project, they default to the right XML editor.
     * <li> Discovering deleted files, and closing the corresponding editors if necessary.
     *      This is only done for XML files, since other editors such as Java editors handles
     *      it on their own.
     * <ul>
     *
     * This is called by the {@link AdtPlugin} during initialization.
     *
     * @param monitor The main Resource Monitor object.
     */
    public void setupEditors(GlobalProjectMonitor monitor) {
        monitor.addFileListener(new IFileListener() {
            @Override
            public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas, int kind,
                    @Nullable String extension, int flags, boolean isAndroidProject) {
                if (!isAndroidProject) {
                    return;
                }
                if (flags == IResourceDelta.MARKERS || !SdkConstants.EXT_XML.equals(extension)) {
                    // ONLY the markers changed, or not XML file: not relevant to this listener
                    return;
                }

                if (kind == IResourceDelta.REMOVED) {
                    AdtUtils.closeEditors(file, false /*save*/);
                    return;
                }

                // The resources files must have a file path similar to
                //    project/res/.../*.xml
                // There is no support for sub folders, so the segment count must be 4
                if (file.getFullPath().segmentCount() == 4) {
                    // check if we are inside the res folder.
                    String segment = file.getFullPath().segment(1);
                    if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
                        // we are inside a res/ folder, get the ResourceFolderType of the
                        // parent folder.
                        String[] folderSegments = file.getParent().getName().split(SdkConstants.RES_QUALIFIER_SEP);

                        // get the enum for the resource type.
                        ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);

                        if (type != null) {
                            if (kind == IResourceDelta.ADDED) {
                                // A new file {@code /res/type-config/some.xml} was added.
                                // All the /res XML files are handled by the same common editor now.
                                IDE.setDefaultEditor(file, CommonXmlEditor.ID);
                            }
                        } else {
                            // if the res folder is null, this means the name is invalid,
                            // in this case we remove whatever android editors that was set
                            // as the default editor.
                            IEditorDescriptor desc = IDE.getDefaultEditor(file);
                            String editorId = desc.getId();
                            if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) {
                                // reset the default editor.
                                IDE.setDefaultEditor(file, null);
                            }
                        }
                    }
                }
            }
        }, IResourceDelta.ADDED | IResourceDelta.REMOVED);

        monitor.addProjectListener(new IProjectListener() {
            @Override
            public void projectClosed(IProject project) {
                // Close any editors referencing this project
                AdtUtils.closeEditors(project, true /*save*/);
            }

            @Override
            public void projectDeleted(IProject project) {
                // Close any editors referencing this project
                AdtUtils.closeEditors(project, false /*save*/);
            }

            @Override
            public void projectOpenedWithWorkspace(IProject project) {
            }

            @Override
            public void allProjectsOpenedWithWorkspace() {
            }

            @Override
            public void projectOpened(IProject project) {
            }

            @Override
            public void projectRenamed(IProject project, IPath from) {
            }
        });
    }

    /**
     * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
     * a project has its target changed.
     */
    public void addTargetListener(ITargetChangeListener listener) {
        mTargetChangeListeners.add(listener);
    }

    /**
     * Removes an existing {@link ITargetChangeListener}.
     * @see #addTargetListener(ITargetChangeListener)
     */
    public void removeTargetListener(ITargetChangeListener listener) {
        mTargetChangeListeners.remove(listener);
    }

    /**
     * Updates all the {@link ITargetChangeListener}s that a target has changed for a given project.
     * <p/>Only editors related to that project should reload.
     */
    @SuppressWarnings("unchecked")
    public void updateTargetListeners(final IProject project) {
        final List<ITargetChangeListener> listeners = (List<ITargetChangeListener>) mTargetChangeListeners.clone();

        AdtPlugin.getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                for (ITargetChangeListener listener : listeners) {
                    try {
                        listener.onProjectTargetChange(project);
                    } catch (Exception e) {
                        AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
                    }
                }
            }
        });
    }

    /**
     * Updates all the {@link ITargetChangeListener}s that a target data was loaded.
     * <p/>Only editors related to a project using this target should reload.
     */
    @SuppressWarnings("unchecked")
    public void updateTargetListeners(final IAndroidTarget target) {
        final List<ITargetChangeListener> listeners = (List<ITargetChangeListener>) mTargetChangeListeners.clone();

        Display display = AdtPlugin.getDisplay();
        if (display == null || display.isDisposed()) {
            return;
        }
        display.asyncExec(new Runnable() {
            @Override
            public void run() {
                for (ITargetChangeListener listener : listeners) {
                    try {
                        listener.onTargetLoaded(target);
                    } catch (Exception e) {
                        AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
                    }
                }
            }
        });
    }

    public static synchronized OutputStream getOutStream() {
        return sPlugin.mAndroidConsoleStream;
    }

    public static synchronized OutputStream getErrorStream() {
        return sPlugin.mAndroidConsoleErrorStream;
    }

    /**
     * Sets the named persistent property for the given file to the given value
     *
     * @param file the file to associate the property with
     * @param qname the name of the property
     * @param value the new value, or null to clear the property
     */
    public static void setFileProperty(IFile file, QualifiedName qname, String value) {
        try {
            file.setPersistentProperty(qname, value);
        } catch (CoreException e) {
            log(e, "Cannot set property %1$s to %2$s", qname, value);
        }
    }

    /**
     * Gets the named persistent file property from the given file
     *
     * @param file the file to look up properties for
     * @param qname the name of the property to look up
     * @return the property value, or null
     */
    public static String getFileProperty(IFile file, QualifiedName qname) {
        try {
            return file.getPersistentProperty(qname);
        } catch (CoreException e) {
            log(e, "Cannot get property %1$s", qname);
        }

        return null;
    }

    /**
     * Conditionally reparses the content of the SDK if it has changed on-disk
     * and updates opened projects.
     * <p/>
     * The operation is asynchronous and happens in a background eclipse job.
     * <p/>
     * This operation is called in multiple places and should be reasonably
     * cheap and conservative. The goal is to automatically refresh the SDK
     * when it is obvious it has changed so when not sure the code should
     * tend to not reload and avoid reloading too often (which is an expensive
     * operation that has a lot of user impact.)
     */
    public void refreshSdk() {
        // SDK can't have changed if we haven't loaded it yet.
        final Sdk sdk = Sdk.getCurrent();
        if (sdk == null) {
            return;
        }

        Job job = new Job("Check Android SDK") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                // SDK has changed if its location path is different.
                File location = sdk.getSdkFileLocation();
                boolean changed = location == null || !location.isDirectory();

                if (!changed) {
                    assert location != null;
                    File prefLocation = new File(AdtPrefs.getPrefs().getOsSdkFolder());
                    changed = !location.equals(prefLocation);

                    if (changed) {
                        // Basic file path comparison indicates they are not the same.
                        // Let's dig a bit deeper.
                        try {
                            location = location.getCanonicalFile();
                            prefLocation = prefLocation.getCanonicalFile();
                            changed = !location.equals(prefLocation);
                        } catch (IOException ignore) {
                            // There's no real reason for the canonicalization to fail
                            // if the paths map to actual directories. And if they don't
                            // this should have been caught above.
                        }
                    }
                }

                if (!changed) {
                    // Check whether the target directories has potentially changed.
                    changed = sdk.haveTargetsChanged();
                }

                if (changed) {
                    monitor.setTaskName("Reload Android SDK");
                    reparseSdk();
                }

                monitor.done();
                return Status.OK_STATUS;
            }
        };
        job.setRule(ResourcesPlugin.getWorkspace().getRoot());
        job.setPriority(Job.SHORT); // a short background job, not interactive.
        job.schedule();
    }

    /**
     * Reparses the content of the SDK and updates opened projects.
     * The operation is asynchronous and happens in a background eclipse job.
     * <p/>
     * This reloads the SDK all the time. To only perform this when it has potentially
     * changed, call {@link #refreshSdk()} instead.
     */
    public void reparseSdk() {
        // add all the opened Android projects to the list of projects to be updated
        // after the SDK is reloaded
        synchronized (Sdk.getLock()) {
            // get the project to refresh.
            IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null /*filter*/);
            mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects));
        }

        // parse the SDK resources at the new location
        parseSdkContent(0 /*immediately*/);
    }

    /**
     * Prints messages, associated with a project to the specified stream
     * @param stream The stream to write to
     * @param tag The tag associated to the message. Can be null
     * @param objects The objects to print through their toString() method (or directly for
     * {@link String} objects.
     */
    public static synchronized void printToStream(MessageConsoleStream stream, String tag, Object... objects) {
        String dateTag = AndroidPrintStream.getMessageTag(tag);

        for (Object obj : objects) {
            stream.print(dateTag);
            stream.print(" "); //$NON-NLS-1$
            if (obj instanceof String) {
                stream.println((String) obj);
            } else if (obj == null) {
                stream.println("(null)"); //$NON-NLS-1$
            } else {
                stream.println(obj.toString());
            }
        }
    }

    // --------- ILogger methods -----------

    @Override
    public void error(@Nullable Throwable t, @Nullable String format, Object... args) {
        if (t != null) {
            log(t, format, args);
        } else {
            log(IStatus.ERROR, format, args);
        }
    }

    @Override
    public void info(@NonNull String format, Object... args) {
        log(IStatus.INFO, format, args);
    }

    @Override
    public void verbose(@NonNull String format, Object... args) {
        log(IStatus.INFO, format, args);
    }

    @Override
    public void warning(@NonNull String format, Object... args) {
        log(IStatus.WARNING, format, args);
    }

    /**
     * Opens the given URL in a browser tab
     *
     * @param url the URL to open in a browser
     */
    public static void openUrl(URL url) {
        IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
        IWebBrowser browser;
        try {
            browser = support.createBrowser(PLUGIN_ID);
            browser.openURL(url);
        } catch (PartInitException e) {
            log(e, null);
        }
    }

    /**
     * Opens a Java class for the given fully qualified class name
     *
     * @param project the project containing the class
     * @param fqcn the fully qualified class name of the class to be opened
     * @return true if the class was opened, false otherwise
     */
    public static boolean openJavaClass(IProject project, String fqcn) {
        if (fqcn == null) {
            return false;
        }

        // Handle inner classes
        if (fqcn.indexOf('$') != -1) {
            fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$
        }

        try {
            if (project.hasNature(JavaCore.NATURE_ID)) {
                IJavaProject javaProject = JavaCore.create(project);
                IJavaElement result = javaProject.findType(fqcn);
                if (result != null) {
                    return JavaUI.openInEditor(result) != null;
                }
            }
        } catch (Throwable e) {
            log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$
        }

        return false;
    }

    /**
     * For a stack trace entry, specifying a class, method, and optionally
     * fileName and line number, open the corresponding line in the editor.
     *
     * @param fqcn the fully qualified name of the class
     * @param method the method name
     * @param fileName the file name, or null
     * @param lineNumber the line number or -1
     * @return true if the target location could be opened, false otherwise
     */
    public static boolean openStackTraceLine(@Nullable String fqcn, @Nullable String method,
            @Nullable String fileName, int lineNumber) {
        return new SourceRevealer().revealMethod(fqcn + '.' + method, fileName, lineNumber, null);
    }

    /**
     * Opens the given file and shows the given (optional) region in the editor (or
     * if no region is specified, opens the editor tab.)
     *
     * @param file the file to be opened
     * @param region an optional region which if set will be selected and shown to the
     *            user
     * @throws PartInitException if something goes wrong
     */
    public static void openFile(IFile file, IRegion region) throws PartInitException {
        openFile(file, region, true);
    }

    // TODO: Make an openEditor which does the above, and make the above pass false for showEditor

    /**
     * Opens the given file and shows the given (optional) region
     *
     * @param file the file to be opened
     * @param region an optional region which if set will be selected and shown to the
     *            user
     * @param showEditorTab if true, front the editor tab after opening the file
     * @return the editor that was opened, or null if no editor was opened
     * @throws PartInitException if something goes wrong
     */
    public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab) throws PartInitException {
        IWorkbenchPage page = AdtUtils.getActiveWorkbenchPage();
        if (page == null) {
            return null;
        }
        IEditorPart targetEditor = IDE.openEditor(page, file, true);
        if (targetEditor instanceof AndroidXmlEditor) {
            AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor;
            if (region != null) {
                editor.show(region.getOffset(), region.getLength(), showEditorTab);
            } else if (showEditorTab) {
                editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
            }
        } else if (targetEditor instanceof AbstractTextEditor) {
            AbstractTextEditor editor = (AbstractTextEditor) targetEditor;
            if (region != null) {
                editor.setHighlightRange(region.getOffset(), region.getLength(), true /* moveCursor*/);
            }
        }

        return targetEditor;
    }
}