/*******************************************************************************
* Copyright 2006, CHISEL Group, University of Victoria, Victoria, BC, Canada
* and IBM Corporation.
* 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:
* The Chisel Group, University of Victoria
*******************************************************************************/
package net.sourceforge.tagsea;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
import net.sourceforge.tagsea.core.ITagChangeListener;
import net.sourceforge.tagsea.core.ITagSEAOperationStateListener;
import net.sourceforge.tagsea.core.ITagsModel;
import net.sourceforge.tagsea.core.IWaypoint;
import net.sourceforge.tagsea.core.IWaypointChangeListener;
import net.sourceforge.tagsea.core.IWaypointsModel;
import net.sourceforge.tagsea.core.TagSEAOperation;
import net.sourceforge.tagsea.core.WaypointMatch;
import net.sourceforge.tagsea.core.ui.BaseWaypointUI;
import net.sourceforge.tagsea.core.ui.ITagSEAImageConstants;
import net.sourceforge.tagsea.core.ui.ITagSEAUI;
import net.sourceforge.tagsea.core.ui.IWaypointUIExtension;
import net.sourceforge.tagsea.core.ui.internal.TagSEAUI;
import net.sourceforge.tagsea.core.ui.internal.TagSEAUIEvent;
import net.sourceforge.tagsea.model.internal.TagSEAChangeSupport;
import net.sourceforge.tagsea.model.internal.TagsModel;
import net.sourceforge.tagsea.model.internal.WaypointsModel;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle
*/
public class TagSEAPlugin extends AbstractUIPlugin {
// The plug-in ID
public static final String PLUGIN_ID = "net.sourceforge.tagsea"; //$NON-NLS-1$
// The shared instance
private static TagSEAPlugin plugin;
private ITagSEAUI ui;
private Map<String, AbstractWaypointDelegate> delegateMap;
private Map<String, IWaypointUIExtension> uiMap;
private Map<String, WaypointInterface> interfaceMap;
// the paths
private static final String PATH_RENAME = "icons/rename.gif"; //$NON-NLS-1$
private static final String PATH_SYNCHRONIZE = "icons/refresh.gif"; //$NON-NLS-1$
private static final String PATH_LINKWITH = "icons/link.gif"; //$NON-NLS-1$
private static final String PATH_ADD = "icons/add.gif"; //$NON-NLS-1$
private static final String PATH_WAYPOINT = "icons/waypoint.gif"; //$NON-NLS-1$
private static final String PATH_TAG = "icons/tag.gif"; //$NON-NLS-1$
private static final String PATH_ROUTE = "icons/route.gif"; //$NON-NLS-1$
private static final String PATH_UP_ARROW = "icons/up.gif"; //$NON-NLS-1$
private static final String PATH_UP_ARROW_DISABLED = "icons/up_disabled.gif"; //$NON-NLS-1$
private static final String PATH_DOWN_ARROW = "icons/down.gif"; //$NON-NLS-1$
private static final String PATH_DOWN_ARROW_DISABLED = "icons/down_disabled.gif"; //$NON-NLS-1$
private static final String PATH_FILTER = "icons/filter.gif"; //$NON-NLS-1$
private static final String PATH_DELETE_UNUSED = "icons/delete_unused.gif"; //$NON-NLS-1$
private static final String PATH_FILTER_WAYPOINT = "icons/filtertowaypoint.gif"; //$NON-NLS-1$
/**
* ID for waypoint markers in the workspace. Any marker that is a subtype of this marker
* will automatically cause decorations to occurr in the platform ui.
*/
public static final String MARKER_ID = PLUGIN_ID + ".waypointmarker"; //$NON-NLS-1$
private static final String PATH_WAYPOINT_OVERLAY = "icons/waypointoverlay.gif";
/**
* The constructor
*/
public TagSEAPlugin() {
plugin = this;
delegateMap = new TreeMap<String, AbstractWaypointDelegate>();
uiMap = new TreeMap<String, IWaypointUIExtension>();
interfaceMap = new TreeMap<String, WaypointInterface>();
ui = new TagSEAUI();
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
super.start(context);
loadTypes();
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
((TagSEAUI)ui).stop();
plugin = null;
super.stop(context);
}
/**
* Returns the shared instance
*
* @return the shared instance
*/
public static TagSEAPlugin getDefault() {
return plugin;
}
/**
* Returns the waypoint delegate for the given waypoint type. If the given type has not
* been contributed, then null is returned. The waypoint type is defined as the plugin id
* of the defining plugin, followed by a '.' and the simple type name given by the plugin
* extension point.
* @param waypointType
* @return the waypoint delegate, or null if none exists.
*/
public AbstractWaypointDelegate getWaypointDelegate(String waypointType) {
return delegateMap.get(waypointType);
}
/**
* Returns the interface or delegate for the given waypoint type. If the given type
* has not been contributed via the <code>net.sourceforge.tagsea.waypoint</code> extension,
* then null is returned.
*
* This method is prefered to calling getWaypointDelegate(String)
* @param waypointType the type to find.
* @return the waypoint type, or null if none exists.
*/
public IWaypointType getWaypointType(String waypointType) {
IWaypointType type = delegateMap.get(waypointType);
if (type == null) {
type = interfaceMap.get(waypointType);
}
return type;
}
/**
* Returns all of the delegates registered with the platform.
* @return all of the delegates registered with the platform.
*/
public AbstractWaypointDelegate[] getWaypointDelegates() {
Collection<AbstractWaypointDelegate> delegates = delegateMap.values();
return delegates.toArray(new AbstractWaypointDelegate[delegates.size()]);
}
/**
* Returns all interfaces declared in the <code>net.sourceforge.tagsea.waypoint</code>
* extension.
* @return all interfaces declared in the <code>net.sourceforge.tagsea.waypoint</code>
* extension.
*/
public WaypointInterface[] getWaypointInterfaces() {
Collection<WaypointInterface> interfaces = interfaceMap.values();
return interfaces.toArray(new WaypointInterface[interfaces.size()]);
}
/**
* Returns the waypoint ui extension declared by the plugin that defined the given waypoint type.
* If none was declared, then an instance of BaseWaypointUI is returned.
* @param waypointType the waypoint type.
* @return the waypoint ui extension.
*/
public IWaypointUIExtension getWaypointUI(String waypointType) {
return uiMap.get(waypointType);
}
/**
* Returns a list of waypoint types known to this plugin.
* @return
*/
public String[] getWaypointTypes() {
ArrayList<String> types = new ArrayList<String>();
types.addAll(interfaceMap.keySet());
types.addAll(delegateMap.keySet());
return types.toArray(new String[types.size()]);
}
private void loadTypes() {
IConfigurationElement[] elements =
Platform.getExtensionRegistry().getConfigurationElementsFor("net.sourceforge.tagsea.waypoint"); //$NON-NLS-1$
for (IConfigurationElement e : elements) {
if ("interface".equals(e.getName())) { //$NON-NLS-1$
loadInterface(e);
} else if ("waypoint".equals(e.getName())) { //$NON-NLS-1$
loadWaypoint(e);
}
}
}
/**
* @param e
*/
private void loadInterface(IConfigurationElement e) {
WaypointInterface wpinterface = new WaypointInterface();
if (wpinterface.configure(e)) {
if (getWaypointType(wpinterface.getType()) == null) {
interfaceMap.put(wpinterface.getType(), wpinterface);
} else {
//already defined: post an error.
Status status = new Status(Status.ERROR, PLUGIN_ID, Status.ERROR, Messages.getString("TagSEAPlugin.error.duplicateWaypoint") + wpinterface.getType(), null); //$NON-NLS-1$
getLog().log(status);
}
} else {
Status status = new Status(Status.ERROR, PLUGIN_ID, Status.ERROR, Messages.getString("TagSEAPlugin.error.waypointConfiguration"), null); //$NON-NLS-1$
getLog().log(status);
}
}
/**
* @param e
*/
private void loadWaypoint(IConfigurationElement e) {
try {
String type =
e.getContributor().getName() + "." + e.getAttribute("type"); //$NON-NLS-1$ //$NON-NLS-2$
AbstractWaypointDelegate delegate =
(AbstractWaypointDelegate) e.createExecutableExtension("class"); //$NON-NLS-1$
String ui = e.getAttribute("ui"); //$NON-NLS-1$
IWaypointUIExtension uiExtension = new BaseWaypointUI();
if (ui != null) {
uiExtension = (IWaypointUIExtension) e.createExecutableExtension("ui"); //$NON-NLS-1$
}
uiMap.put(type, uiExtension);
if (uiExtension instanceof BaseWaypointUI) {
((BaseWaypointUI)uiExtension).setType(type);
}
if (delegate.configure(e)) {
if (getWaypointType(type) == null)
delegateMap.put(type, delegate);
else {
getLog().log(new Status(Status.ERROR, PLUGIN_ID, Status.ERROR, Messages.getString("TagSEAPlugin.error.duplicateWaypoint") + type, null)); //$NON-NLS-1$
}
}
} catch (CoreException ex) {
log(ex);
}
}
public void log(Exception e) {
if (e instanceof CoreException) {
getLog().log(((CoreException)e).getStatus());
} else {
String message = e.getMessage();
if (message == null) {
message = "";
}
Status status = new Status(
Status.ERROR,
PLUGIN_ID,
Status.ERROR,
message,
e
);
getLog().log(status);
}
}
/**
* Returns a reference to the model in which all the tags are stored in memory.
* @return a reference to the model in which all the tags are stored in memory.
*/
public static ITagsModel getTagsModel() {
return TagsModel.INSTANCE;
}
/**
* Gets a refernce to the model in which all the waypoints are stored in memory.
* @return a refernce to the model in which all the waypoints are stored in memory.
*/
public static IWaypointsModel getWaypointsModel() {
return WaypointsModel.INSTANCE;
}
/**
* Adds the given listener to the list of waypoint listeners, if it has not already been added.
* @param listener the listener to add.
*/
public static void addWaypointChangeListener(IWaypointChangeListener listener) {
TagSEAChangeSupport.INSTANCE.addWaypointChangeListener(listener);
}
/**
* Add the given listener to the list of waypoint listeners for the given type only. The listener
* will be notified only of changes to that particular type.
* @param listener the listener to add.
* @param type the type to listen to.
*/
public static void addWaypointChangeListener(IWaypointChangeListener listener, String type) {
TagSEAChangeSupport.INSTANCE.addWaypointChangeListener(listener, type);
}
/**
* Removes the given listener from the list of waypoint listeners, if it has previously been added.
* @param listener the listener to remove.
*/
public static void removeWaypointChangeListener(IWaypointChangeListener listener) {
TagSEAChangeSupport.INSTANCE.removeWaypointChangeListener(listener);
}
/**
* Adds the given listener to the list of tag listeners, if it has not already been added.
* @param listener the listener to add.
*/
public static void addTagChangeListener(ITagChangeListener listener) {
TagSEAChangeSupport.INSTANCE.addTagChangeListener(listener);
}
/**
* Runs the given operation this thread will block if wait is set to true. Normally, that will
* be the preferred way of calling this method. Setting wait to false is useful for long running
* operations that begin in the UI thread. Note: all other operations on the model will block
* until this operation is complete. Even single calls to the model to change fine-grained elements
* will block if another operation is currently running.
*
* Views that set their progress service to show busy for the TagSEAPlugin.getDefault() object
* will show busy indication while TagSEAOperations are running.
* @param op the operation to run
* @param wait suspend this thread until the operation is complete.
*/
public static void run(TagSEAOperation op, boolean wait) {
TagSEAChangeSupport.INSTANCE.run(op, wait);
}
/**
* Runs the given operation in the current thread. This differs from {@link #run(TagSEAOperation, boolean)}
* in that {@link #run(TagSEAOperation, boolean)} will always spawn a separate job to run the code found
* in the given operation. This method will run the code within this thread, using the progress monitor
* provided. This method is still thread-safe in that it will suspend the current thread until other
* running TagSEAOperations have been completed, or any current model changes have finished. It does
* not guarantee ordering of TagSEAOperations either. If several {@link TagSEAOperation} have been
* scheduled using {@link #run(TagSEAOperation, boolean)}, this method may insert itself between
* operations waiting to run, depending on scheduling. Changes to the TagSEA model will not
* be posted until after the operation (and possible nested operations) has completed.
* @param op the operation to run.
* @param monitor the monitor to use while running the operation.
* @return the status of the changes
*/
public static IStatus syncRun(TagSEAOperation op, IProgressMonitor monitor) {
return TagSEAChangeSupport.INSTANCE.syncRun(op, monitor);
}
/**
* Returns true if there is a concurrent running TagSEAOperation in this thread or another
* thread due to a {@link #run(TagSEAOperation, boolean)} or {@link #syncRun(TagSEAOperation, IProgressMonitor)}
* call. Note that two consecutive calls to this method may return opposite values unless it is called
* from within a currently running TagSEAOperation. This is meant to be a hint only.
* @return true if there is a concurrent running TagSEAOperation in this or another thread.
*/
public static boolean isBlocked() {
return TagSEAChangeSupport.INSTANCE.isBlocked();
}
/**
* Adds the given {@link ITagSEAOperationStateListener} to the list of listeners in the platform.
* Has no effect if the listener has already been registered.
* @param listener the listener to register.
*/
public static void addOperationStateListener(ITagSEAOperationStateListener listener) {
TagSEAChangeSupport.INSTANCE.addOperationStateListener(listener);
}
/**
* Removes the given {@link ITagSEAOperationStateListener} from the list of listeners in the
* platform. Has no effect if the listener has not been registered.
* @param listener the listener to deregister
*/
public static void removeOperationStateListener(ITagSEAOperationStateListener listener) {
TagSEAChangeSupport.INSTANCE.removeOperationStateListener(listener);
}
/**
* Removes the given listener from the list of tag listeners, if it has previously been added.
* @param listener the listener to remove.
*/
public static void removeTagChangeListener(ITagChangeListener listener) {
TagSEAChangeSupport.INSTANCE.removeTagChangeListener(listener);
}
/**
* Navigates to the given waypoint.
* @param waypoint the waypoin to navigate to.
*/
public void navigate(IWaypoint waypoint) {
AbstractWaypointDelegate delegate = getWaypointDelegate(waypoint.getType());
if (delegate != null) {
((TagSEAUI)getUI()).getUIEventModel().fireEvent(TagSEAUIEvent.createNavigationEvent(waypoint));
delegate.navigate(waypoint);
}
}
/**
* 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);
}
@Override
protected void initializeImageRegistry(ImageRegistry registry) {
super.initializeImageRegistry(registry);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_RENAME, PATH_RENAME);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_SYNCHRONIZE, PATH_SYNCHRONIZE);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_LINKWITH, PATH_LINKWITH);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_ADD, PATH_ADD);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_WAYPOINT, PATH_WAYPOINT);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_TAG, PATH_TAG);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_ROUTE, PATH_ROUTE);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_FILTER, PATH_FILTER);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_DELETE_UNUSED, PATH_DELETE_UNUSED);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_UP_ARROW, PATH_UP_ARROW);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_UP_ARROW_DISABLED, PATH_UP_ARROW_DISABLED);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_DOWN_ARROW, PATH_DOWN_ARROW);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_DOWN_ARROW_DISABLED, PATH_DOWN_ARROW_DISABLED);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_FILTER_WAYPOINT, PATH_FILTER_WAYPOINT);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_WAYPOINT_OVERLAY, PATH_WAYPOINT_OVERLAY);
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_TAG_HIERARCHY, "icons/taghierarchy.gif");
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_WAYPOINT_WARNING, "icons/waypointwarning.gif");
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_WAYPOINT_ERROR, "icons/waypointerror.gif");
loadImageDescriptorIntoRegistry(registry, ITagSEAImageConstants.IMG_WAYPOINT_FIX, "icons/waypointfix.gif");
// load the shared workbench images
final ISharedImages workbenchImages = PlatformUI.getWorkbench().getSharedImages();
registry.put(ITagSEAImageConstants.IMG_DELETE, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE));
registry.put(ITagSEAImageConstants.IMG_DELETE_DISABLED, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_DELETE_DISABLED));
registry.put(ITagSEAImageConstants.IMG_BACK, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_BACK));
registry.put(ITagSEAImageConstants.IMG_BACK_DISABLED, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_BACK_DISABLED));
registry.put(ITagSEAImageConstants.IMG_FORWARD, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD));
registry.put(ITagSEAImageConstants.IMG_FORWARD_DISABLED, workbenchImages.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD_DISABLED));
}
/**
* Loads an image descriptor into the registry if it doesn't already exist.
* @param reg the image registry
* @param key the key for the image in the registry
* @param path the path to the image (relative to the plugin directory (e.g. /icons/icon.gif)
*/
private void loadImageDescriptorIntoRegistry(ImageRegistry reg, String key, String path) {
ImageDescriptor descriptor = reg.getDescriptor(key);
if (descriptor == null) {
descriptor = getImageDescriptor(path);
if(descriptor != null) {
reg.put(key, descriptor);
}
}
}
/**
* Returns a convenient access point for ui elements in TagSEA.
* @return a convenient access point for ui elements in TagSEA.
*/
public ITagSEAUI getUI() {
return ui;
}
/**
* @param string
* @param e
*/
public void log(String string, Exception e) {
Status status = new Status(
Status.ERROR,
PLUGIN_ID,
Status.ERROR,
string,
e
);
getLog().log(status);
}
/**
* Checks for equality between two waypoints. Waypoint equality is determined first
* by checking the equality of the waypoint types, then by querying the defining
* plugin for the waypoint type. This method does not replace the equals() method
* of a waypoint. It may be used by UI elements to update selections or check for
* duplicates, but should be used sparingly when a normal equals() would do better.
* @param wp0 the first waypoint.
* @param wp1 the second waypoint.
* @return whether or not the waypoint's defining plugin determines the two waypoints
* to be equal.
*/
public boolean waypointsEqual(IWaypoint wp0, IWaypoint wp1) {
if (!wp0.getType().equals(wp1.getType())) return false;
return getWaypointDelegate(wp1.getType()).waypointsEqual(wp0, wp1);
}
/**
* Attempts to find waypoints of the given type that match the given attributes and tag names. The
* returned matches are ordered by certainty.
* @param type the waypoint type to check.
* @param attributes the attributes to search for.
* @param tagNames the tag names to match.
* @return the matches.
*/
public WaypointMatch[] locate(String type, Map<String, Object> attributes, SortedSet<String> tagNames) {
AbstractWaypointDelegate delegate = getWaypointDelegate(type);
if (delegate != null) {
return delegate.getLocator().findMatches(attributes, tagNames);
}
return new WaypointMatch[0];
}
}