blob: 7f8566e1deefacfbed1570e5700be84b277bc9a3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IPluginDescriptor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WWinPluginAction;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
/**
* Abstract base class for plug-ins that integrate with the Eclipse platform UI.
* <p>
* Subclasses obtain the following capabilities:
* </p>
* <p>
* Preferences
* <ul>
* <li> The platform core runtime contains general support for plug-in
* preferences (<code>org.eclipse.core.runtime.Preferences</code>).
* This class provides appropriate conversion to the older JFace preference
* API (<code>org.eclipse.jface.preference.IPreferenceStore</code>).</li>
* <li> The method <code>getPreferenceStore</code> returns the JFace preference
* store (cf. <code>Plugin.getPluginPreferences</code> which returns
* a core runtime preferences object.</li>
* <li> Subclasses may reimplement <code>initializeDefaultPreferences</code>
* to set up any default values for preferences using JFace API. In this
* case, <code>initializeDefaultPluginPreferences</code> should not be
* overridden.</li>
* <li> Subclasses may reimplement
* <code>initializeDefaultPluginPreferences</code> to set up any default
* values for preferences using core runtime API. In this
* case, <code>initializeDefaultPreferences</code> should not be
* overridden.</li>
* <li> Preferences are also saved automatically on plug-in shutdown.
* However, saving preferences immediately after changing them is
* strongly recommended, since that ensures that preference settings
* are not lost even in the event of a platform crash.</li>
* </ul>
* Dialogs
* <ul>
* <li> The dialog store is read the first time <code>getDialogSettings</code>
* is called.</li>
* <li> The dialog store allows the plug-in to "record" important choices made
* by the user in a wizard or dialog, so that the next time the
* wizard/dialog is used the widgets can be defaulted to better values. A
* wizard could also use it to record the last 5 values a user entered into
* an editable combo - to show "recent values". </li>
* <li> The dialog store is found in the file whose name is given by the
* constant <code>FN_DIALOG_STORE</code>. A dialog store file is first
* looked for in the plug-in's read/write state area; if not found there,
* the plug-in's install directory is checked.
* This allows a plug-in to ship with a read-only copy of a dialog store
* file containing initial values for certain settings.</li>
* <li> Plug-in code can call <code>saveDialogSettings</code> to cause settings to
* be saved in the plug-in's read/write state area. A plug-in may opt to do
* this each time a wizard or dialog is closed to ensure the latest
* information is always safe on disk. </li>
* <li> Dialog settings are also saved automatically on plug-in shutdown.</li>
* </ul>
* Images
* <ul>
* <li> A typical UI plug-in will have some images that are used very frequently
* and so need to be cached and shared. The plug-in's image registry
* provides a central place for a plug-in to store its common images.
* Images managed by the registry are created lazily as needed, and will be
* automatically disposed of when the plug-in shuts down. Note that the
* number of registry images should be kept to a minimum since many OSs
* have severe limits on the number of images that can be in memory at once.
* </ul>
* <p>
* For easy access to your plug-in object, use the singleton pattern. Declare a
* static variable in your plug-in class for the singleton. Store the first
* (and only) instance of the plug-in class in the singleton when it is created.
* Then access the singleton when needed through a static <code>getDefault</code>
* method.
* </p>
* <p>
* See the description on {@link Plugin}.
* </p>
*/
public abstract class AbstractUIPlugin extends Plugin {
/**
* The name of the dialog settings file (value
* <code>"dialog_settings.xml"</code>).
*/
private static final String FN_DIALOG_SETTINGS = "dialog_settings.xml"; //$NON-NLS-1$
/**
* Storage for dialog and wizard data; <code>null</code> if not yet
* initialized.
*/
private IDialogSettings dialogSettings = null;
/**
* Storage for preferences.
*/
private ScopedPreferenceStore preferenceStore;
/**
* The registry for all graphic images; <code>null</code> if not yet
* initialized.
*/
private ImageRegistry imageRegistry = null;
/**
* The bundle listener used for kicking off refreshPluginActions().
*
* @since 3.0.1
*/
private BundleListener bundleListener;
/**
* Creates an abstract UI plug-in runtime object for the given plug-in
* descriptor.
* <p>
* Note that instances of plug-in runtime classes are automatically created
* by the platform in the course of plug-in activation.
* <p>
*
* @param descriptor the plug-in descriptor
* @see Plugin#Plugin(org.eclipse.core.runtime.IPluginDescriptor descriptor)
* @deprecated
* In Eclipse 3.0 this constructor has been replaced by
* {@link #AbstractUIPlugin()}. Implementations of
* <code>MyPlugin(IPluginDescriptor descriptor)</code> should be changed to
* <code>MyPlugin()</code> and call <code>super()</code> instead of
* <code>super(descriptor)</code>.
* The <code>MyPlugin(IPluginDescriptor descriptor)</code> constructor is
* called only for plug-ins which explicitly require the
* org.eclipse.core.runtime.compatibility plug-in (or, as in this case,
* subclasses which might).
*/
public AbstractUIPlugin(IPluginDescriptor descriptor) {
super(descriptor);
}
/**
* Creates an abstract UI plug-in runtime object.
* <p>
* Plug-in runtime classes are <code>BundleActivators</code> and so must
* have an default constructor. This method is called by the runtime when
* the associated bundle is being activated.
* <p>
* For more details, see <code>Plugin</code>'s default constructor.
*
* @see Plugin#Plugin()
* @since 3.0
*/
public AbstractUIPlugin() {
super();
}
/**
* Returns a new image registry for this plugin-in. The registry will be
* used to manage images which are frequently used by the plugin-in.
* <p>
* The default implementation of this method creates an empty registry.
* Subclasses may override this method if needed.
* </p>
*
* @return ImageRegistry the resulting registry.
* @see #getImageRegistry
*/
protected ImageRegistry createImageRegistry() {
//If we are in the UI Thread use that
if(Display.getCurrent() != null) {
return new ImageRegistry(Display.getCurrent());
}
if(PlatformUI.isWorkbenchRunning()) {
return new ImageRegistry(PlatformUI.getWorkbench().getDisplay());
}
//Invalid thread access if it is not the UI Thread
//and the workbench is not created.
throw new SWTError(SWT.ERROR_THREAD_INVALID_ACCESS);
}
/**
* Returns the dialog settings for this UI plug-in.
* The dialog settings is used to hold persistent state data for the various
* wizards and dialogs of this plug-in in the context of a workbench.
* <p>
* If an error occurs reading the dialog store, an empty one is quietly created
* and returned.
* </p>
* <p>
* Subclasses may override this method but are not expected to.
* </p>
*
* @return the dialog settings
*/
public IDialogSettings getDialogSettings() {
if (dialogSettings == null) {
loadDialogSettings();
}
return dialogSettings;
}
/**
* Returns the image registry for this UI plug-in.
* <p>
* The image registry contains the images used by this plug-in that are very
* frequently used and so need to be globally shared within the plug-in. Since
* many OSs have a severe limit on the number of images that can be in memory at
* any given time, a plug-in should only keep a small number of images in their
* registry.
* <p>
* Subclasses should reimplement <code>initializeImageRegistry</code> if they have
* custom graphic images to load.
* </p>
* <p>
* Subclasses may override this method but are not expected to.
* </p>
*
* @return the image registry
*/
public ImageRegistry getImageRegistry() {
if (imageRegistry == null) {
imageRegistry = createImageRegistry();
initializeImageRegistry(imageRegistry);
}
return imageRegistry;
}
/**
* Returns the preference store for this UI plug-in.
* This preference store is used to hold persistent settings for this plug-in in
* the context of a workbench. Some of these settings will be user controlled,
* whereas others may be internal setting that are never exposed to the user.
* <p>
* If an error occurs reading the preference store, an empty preference store is
* quietly created, initialized with defaults, and returned.
* </p>
* <p>
* <strong>NOTE:</strong> As of Eclipse 3.1 this method is
* no longer referring to the core runtime compatibility layer and so
* plug-ins relying on Plugin#initializeDefaultPreferences
* will have to access the compatibility layer themselves.
* </p>
*
* @return the preference store
*/
public IPreferenceStore getPreferenceStore() {
// Create the preference store lazily.
if (preferenceStore == null) {
preferenceStore = new ScopedPreferenceStore(new InstanceScope(),getBundle().getSymbolicName());
}
return preferenceStore;
}
/**
* Returns the Platform UI workbench.
* <p>
* This method exists as a convenience for plugin implementors. The
* workbench can also be accessed by invoking <code>PlatformUI.getWorkbench()</code>.
* </p>
* @return IWorkbench the workbench for this plug-in
*/
public IWorkbench getWorkbench() {
return PlatformUI.getWorkbench();
}
/**
* Initializes a preference store with default preference values
* for this plug-in.
* <p>
* This method is called after the preference store is initially loaded
* (default values are never stored in preference stores).
* </p>
* <p>
* The default implementation of this method does nothing.
* Subclasses should reimplement this method if the plug-in has any preferences.
* </p>
* <p>
* A subclass may reimplement this method to set default values for the
* preference store using JFace API. This is the older way of initializing
* default values. If this method is reimplemented, do not override
* <code>initializeDefaultPluginPreferences()</code>.
* </p>
*
* @param store the preference store to fill
*
* @deprecated this is only called if the runtime compatibility layer is
* present. See {@link #initializeDefaultPluginPreferences}.
*/
protected void initializeDefaultPreferences(IPreferenceStore store) {
// spec'ed to do nothing
}
/**
* The <code>AbstractUIPlugin</code> implementation of this
* <code>Plugin</code> method forwards to
* <code>initializeDefaultPreferences(IPreferenceStore)</code>.
* <p>
* A subclass may reimplement this method to set default values for the core
* runtime preference store in the standard way. This is the recommended way
* to do this. The older
* <code>initializeDefaultPreferences(IPreferenceStore)</code> method
* serves a similar purpose. If this method is reimplemented, do not send
* super, and do not override
* <code>initializeDefaultPreferences(IPreferenceStore)</code>.
* </p>
*
* @deprecated this is only called if the runtime compatibility layer is
* present. See the deprecated comment in
* {@link Plugin#initializeDefaultPluginPreferences}.
*
* @see #initializeDefaultPreferences
* @since 2.0
*/
protected void initializeDefaultPluginPreferences() {
// N.B. by the time this method is called, the plug-in has a
// core runtime preference store (no default values)
// call loadPreferenceStore (only) for backwards compatibility with Eclipse 1.0
loadPreferenceStore();
// call initializeDefaultPreferences (only) for backwards compatibility
// with Eclipse 1.0
initializeDefaultPreferences(getPreferenceStore());
}
/**
* Initializes an image registry with images which are frequently used by the
* plugin.
* <p>
* The image registry contains the images used by this plug-in that are very
* frequently used and so need to be globally shared within the plug-in. Since
* many OSs have a severe limit on the number of images that can be in memory
* at any given time, each plug-in should only keep a small number of images in
* its registry.
* </p><p>
* Implementors should create a JFace image descriptor for each frequently used
* image. The descriptors describe how to create/find the image should it be needed.
* The image described by the descriptor is not actually allocated until someone
* retrieves it.
* </p><p>
* Subclasses may override this method to fill the image registry.
* </p>
* @param reg the registry to initialize
*
* @see #getImageRegistry
*/
protected void initializeImageRegistry(ImageRegistry reg) {
// spec'ed to do nothing
}
/**
* Loads the dialog settings for this plug-in.
* The default implementation first looks for a standard named file in the
* plug-in's read/write state area; if no such file exists, the plug-in's
* install directory is checked to see if one was installed with some default
* settings; if no file is found in either place, a new empty dialog settings
* is created. If a problem occurs, an empty settings is silently used.
* <p>
* This framework method may be overridden, although this is typically
* unnecessary.
* </p>
*/
protected void loadDialogSettings() {
dialogSettings = new DialogSettings("Workbench"); //$NON-NLS-1$
// bug 69387: The instance area should not be created (in the call to
// #getStateLocation) if -data @none or -data @noDefault was used
IPath dataLocation = getStateLocationOrNull();
if (dataLocation != null) {
// try r/w state area in the local file system
String readWritePath = dataLocation.append(FN_DIALOG_SETTINGS)
.toOSString();
File settingsFile = new File(readWritePath);
if (settingsFile.exists()) {
try {
dialogSettings.load(readWritePath);
} catch (IOException e) {
// load failed so ensure we have an empty settings
dialogSettings = new DialogSettings("Workbench"); //$NON-NLS-1$
}
return;
}
}
// otherwise look for bundle specific dialog settings
URL dsURL = BundleUtility.find(getBundle(), FN_DIALOG_SETTINGS);
if (dsURL == null) {
return;
}
InputStream is = null;
try {
is = dsURL.openStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(is, "utf-8")); //$NON-NLS-1$
dialogSettings.load(reader);
} catch (IOException e) {
// load failed so ensure we have an empty settings
dialogSettings = new DialogSettings("Workbench"); //$NON-NLS-1$
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
// do nothing
}
}
}
/**
* Loads the preference store for this plug-in.
* The default implementation looks for a standard named file in the
* plug-in's read/write state area. If no file is found or a problem
* occurs, a new empty preference store is silently created.
* <p>
* This framework method may be overridden, although this is typically
* unnecessary.
* </p>
*
* @deprecated As of Eclipse 2.0, a basic preference store exists for all
* plug-ins. This method now exists only for backwards compatibility.
* It is called as the plug-in's preference store is being initialized.
* The plug-ins preferences are loaded from the file regardless of what
* this method does.
*/
protected void loadPreferenceStore() {
// do nothing by default
}
/**
* Refreshes the actions for the plugin.
* This method is called from <code>startup</code>.
* <p>
* This framework method may be overridden, although this is typically
* unnecessary.
* </p>
*/
protected void refreshPluginActions() {
// If the workbench is not started yet, or is no longer running, do nothing.
if (!PlatformUI.isWorkbenchRunning()) {
return;
}
// startup() is not guaranteed to be called in the UI thread,
// but refreshPluginActions must run in the UI thread,
// so use asyncExec. See bug 6623 for more details.
Display.getDefault().asyncExec(new Runnable() {
public void run() {
WWinPluginAction.refreshActionList();
}
});
}
/**
* Saves this plug-in's dialog settings.
* Any problems which arise are silently ignored.
*/
protected void saveDialogSettings() {
if (dialogSettings == null) {
return;
}
try {
IPath path = getStateLocationOrNull();
if(path == null) {
return;
}
String readWritePath = path
.append(FN_DIALOG_SETTINGS).toOSString();
dialogSettings.save(readWritePath);
} catch (IOException e) {
// spec'ed to ignore problems
} catch (IllegalStateException e) {
// spec'ed to ignore problems
}
}
/**
* Saves this plug-in's preference store.
* Any problems which arise are silently ignored.
*
* @see Plugin#savePluginPreferences()
* @deprecated As of Eclipse 2.0, preferences exist for all plug-ins. The
* equivalent of this method is <code>Plugin.savePluginPreferences</code>.
* This method now calls <code>savePluginPreferences</code>, and exists only for
* backwards compatibility.
*/
protected void savePreferenceStore() {
savePluginPreferences();
}
/**
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
* method does nothing. Subclasses may extend this method, but must send
* super first.
* <p>
* WARNING: Plug-ins may not be started in the UI thread.
* The <code>startup()</code> method should not assume that its code runs in
* the UI thread, otherwise SWT thread exceptions may occur on startup.'
* @deprecated
* In Eclipse 3.0, <code>startup</code> has been replaced by {@link Plugin#start(BundleContext context)}.
* Implementations of <code>startup</code> should be changed to extend
* <code>start(BundleContext context)</code> and call <code>super.start(context)</code>
* instead of <code>super.startup()</code>. Like <code>super.startup()</code>,
* <code>super.stop(context)</code> must be called as the very first thing.
* The <code>startup</code> method is called only for plug-ins which explicitly require the
* org.eclipse.core.runtime.compatibility plug-in; in contrast,
* the <code>start</code> method is always called.
*/
public void startup() throws CoreException {
// this method no longer does anything
// the code that used to be here in 2.1 has moved to start(BundleContext)
super.startup();
}
/**
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
* method does nothing. Subclasses may extend this method, but must send
* super first.
* @deprecated
* In Eclipse 3.0, <code>shutdown</code> has been replaced by {@link Plugin#stop(BundleContext context)}.
* Implementations of <code>shutdown</code> should be changed to extend
* <code>stop(BundleContext context)</code> and call <code>super.stop(context)</code>
* instead of <code>super.shutdown()</code>. Unlike <code>super.shutdown()</code>,
* <code>super.stop(context)</code> must be called as the very <b>last</b> thing rather
* than as the very first thing. The <code>shutdown</code> method is called
* only for plug-ins which explicitly require the
* org.eclipse.core.runtime.compatibility plug-in;
* in contrast, the <code>stop</code> method is always called.
*/
public void shutdown() throws CoreException {
// this method no longer does anything interesting
// the code that used to be here in 2.1 has moved to stop(BundleContext),
// which is called regardless of whether the plug-in being instantiated
// requires org.eclipse.core.runtime.compatibility
super.shutdown();
}
/**
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
* method refreshes the plug-in actions. Subclasses may extend this method,
* but must send super <b>first</b>.
* {@inheritDoc}
*
* @since 3.0
*/
public void start(BundleContext context) throws Exception {
super.start(context);
final BundleContext fc = context;
// Should only attempt refreshPluginActions() once the bundle
// has been fully started. Otherwise, action delegates
// can be created while in the process of creating
// a triggering action delegate (if UI events are processed during startup).
// Also, if the start throws an exception, the bundle will be shut down.
// We don't want to have created any delegates if this happens.
// See bug 63324 for more details.
bundleListener = new BundleListener() {
public void bundleChanged(BundleEvent event) {
if (event.getBundle() == getBundle()) {
if (event.getType() == BundleEvent.STARTED) {
// We're getting notified that the bundle has been started.
// Make sure it's still active. It may have been shut down between
// the time this event was queued and now.
if (getBundle().getState() == Bundle.ACTIVE) {
refreshPluginActions();
}
fc.removeBundleListener(this);
}
}
}
};
context.addBundleListener(bundleListener);
// bundleListener is removed in stop(BundleContext)
}
/**
* The <code>AbstractUIPlugin</code> implementation of this {@link Plugin}
* 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.stop()</code> is
* always done.
* {@inheritDoc}
*
* @since 3.0
*/
public void stop(BundleContext context) throws Exception {
try {
if (bundleListener != null) {
context.removeBundleListener(bundleListener);
}
saveDialogSettings();
savePreferenceStore();
preferenceStore = null;
if (imageRegistry != null)
imageRegistry.dispose();
imageRegistry = null;
} finally {
super.stop(context);
}
}
/**
* Creates and returns a new image descriptor for an image file located
* within the specified plug-in.
* <p>
* This is a convenience method that simply locates the image file in within
* the plug-in. It will now query the ISharedImages registry first. The path
* is relative to the root of the plug-in, and takes into account files
* coming from plug-in fragments. The path may include $arg$ elements.
* However, the path must not have a leading "." or path separator. Clients
* should use a path like "icons/mysample.gif" rather than
* "./icons/mysample.gif" or "/icons/mysample.gif".
* </p>
*
* @param pluginId
* the id of the plug-in containing the image file;
* <code>null</code> is returned if the plug-in does not exist
* @param imageFilePath
* the relative path of the image file, relative to the root of
* the plug-in; the path must be legal
* @return an image descriptor, or <code>null</code> if no image could be
* found
* @since 3.0
*/
public static ImageDescriptor imageDescriptorFromPlugin(String pluginId,
String imageFilePath) {
if (pluginId == null || imageFilePath == null) {
throw new IllegalArgumentException();
}
IWorkbench workbench = PlatformUI.isWorkbenchRunning() ? PlatformUI.getWorkbench() : null;
ImageDescriptor imageDescriptor = workbench == null ? null : workbench
.getSharedImages().getImageDescriptor(imageFilePath);
if (imageDescriptor != null)
return imageDescriptor; // found in the shared images
// if the bundle is not ready then there is no image
Bundle bundle = Platform.getBundle(pluginId);
if (!BundleUtility.isReady(bundle)) {
return null;
}
// look for the image (this will check both the plugin and fragment folders
URL fullPathString = BundleUtility.find(bundle, imageFilePath);
if (fullPathString == null) {
try {
fullPathString = new URL(imageFilePath);
} catch (MalformedURLException e) {
return null;
}
}
return ImageDescriptor.createFromURL(fullPathString);
}
/**
* FOR INTERNAL WORKBENCH USE ONLY.
*
* Returns the path to a location in the file system that can be used
* to persist/restore state between workbench invocations.
* If the location did not exist prior to this call it will be created.
* Returns <code>null</code> if no such location is available.
*
* @return path to a location in the file system where this plug-in can
* persist data between sessions, or <code>null</code> if no such
* location is available.
* @since 3.1
*/
private IPath getStateLocationOrNull() {
try {
return getStateLocation();
} catch (IllegalStateException e) {
// This occurs if -data=@none is explicitly specified, so ignore this silently.
// Is this OK? See bug 85071.
return null;
}
}
}