blob: f7ef41fe3c3a3255e58f92f0451d7d47689c4f6f [file] [log] [blame]
/*
* 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;
}
}