| /******************************************************************************* |
| * Copyright (c) 2004, 2012 Tasktop Technologies 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: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.context.core; |
| |
| import java.io.File; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.IExtensionRegistry; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Plugin; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.mylyn.commons.core.ExtensionPointReader; |
| import org.eclipse.mylyn.commons.core.StatusHandler; |
| import org.eclipse.mylyn.context.core.AbstractContextContributor; |
| import org.eclipse.mylyn.context.core.AbstractContextStructureBridge; |
| import org.eclipse.mylyn.context.core.ContextCore; |
| import org.eclipse.mylyn.context.core.IContextContributor; |
| import org.eclipse.mylyn.context.core.IInteractionContextScaling; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * Activator for the Context Core plug-in. |
| * |
| * @author Mik Kersten |
| * @since 3.0 |
| */ |
| public class ContextCorePlugin extends Plugin { |
| |
| public static final String ID_PLUGIN = "org.eclipse.mylyn.context.core"; //$NON-NLS-1$ |
| |
| private final Map<String, AbstractContextStructureBridge> bridges = new ConcurrentHashMap<String, AbstractContextStructureBridge>(); |
| |
| private final Map<String, Set<String>> childContentTypeMap = new ConcurrentHashMap<String, Set<String>>(); |
| |
| private List<IContextContributor> contextContributor = new CopyOnWriteArrayList<IContextContributor>(); |
| |
| // specifies that one content type should shadow another |
| // the <value> content type shadows the <key> content typee |
| private final Map<String, String> contentTypeToShadowMap = new ConcurrentHashMap<String, String>(); |
| |
| private AbstractContextStructureBridge defaultBridge = null; |
| |
| private static ContextCorePlugin INSTANCE; |
| |
| private InteractionContextManager contextManager; |
| |
| private static LocalContextStore contextStore; |
| |
| private final Map<String, Set<AbstractRelationProvider>> relationProviders = new HashMap<String, Set<AbstractRelationProvider>>(); |
| |
| private final InteractionContextScaling commonContextScaling = new InteractionContextScaling(); |
| |
| private boolean contextContributorInitialized = false; |
| |
| private static final AbstractContextStructureBridge DEFAULT_BRIDGE = new AbstractContextStructureBridge() { |
| |
| @Override |
| public String getContentType() { |
| return null; |
| } |
| |
| @Override |
| public String getHandleIdentifier(Object object) { |
| return null; |
| } |
| |
| @Override |
| public Object getObjectForHandle(String handle) { |
| return null; |
| } |
| |
| @Override |
| public String getParentHandle(String handle) { |
| return null; |
| } |
| |
| @Override |
| public String getLabel(Object object) { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public boolean canBeLandmark(String handle) { |
| return false; |
| } |
| |
| @Override |
| public boolean acceptsObject(Object object) { |
| return false; |
| } |
| |
| @Override |
| public boolean canFilter(Object element) { |
| return true; |
| } |
| |
| @Override |
| public boolean isDocument(String handle) { |
| return false; |
| } |
| |
| @Override |
| public String getContentType(String elementHandle) { |
| return getContentType(); |
| } |
| |
| @Override |
| public String getHandleForOffsetInObject(Object resource, int offset) { |
| return null; |
| } |
| |
| @Override |
| public List<String> getChildHandles(String handle) { |
| return Collections.emptyList(); |
| } |
| }; |
| |
| private static final String EXTENSION_ELEMENT_CONTRIBUTOR = "contextContributor"; //$NON-NLS-1$ |
| |
| private static final String EXTENSION_ID_CONTRIBUTOR = "contributor"; //$NON-NLS-1$ |
| |
| public ContextCorePlugin() { |
| INSTANCE = this; |
| } |
| |
| @Override |
| public void start(BundleContext context) throws Exception { |
| super.start(context); |
| contextStore = new LocalContextStore(commonContextScaling); |
| File storeFile = getDefaultContextLocation().toFile(); |
| if (!storeFile.exists()) { |
| storeFile.mkdirs(); |
| } |
| contextStore.setContextDirectory(storeFile); |
| contextManager = new InteractionContextManager(contextStore); |
| } |
| |
| @Override |
| public void stop(BundleContext context) throws Exception { |
| try { |
| super.stop(context); |
| INSTANCE = null; |
| for (AbstractRelationProvider provider : getRelationProviders()) { |
| provider.stopAllRunningJobs(); |
| } |
| } catch (Exception e) { |
| StatusHandler.log(new Status(IStatus.ERROR, ContextCorePlugin.ID_PLUGIN, "Mylyn Core stop failed", e)); //$NON-NLS-1$ |
| } |
| } |
| |
| public IPath getDefaultContextLocation() { |
| IPath stateLocation = Platform.getStateLocation(getBundle()); |
| IPath cacheFile = stateLocation.append("contexts"); //$NON-NLS-1$ |
| return cacheFile; |
| } |
| |
| /** |
| * Shadows override existing shadows if present. |
| */ |
| private void addShadowsContent(String baseContentType, String shadowedByContentType) { |
| contentTypeToShadowMap.put(baseContentType, shadowedByContentType); |
| } |
| |
| private void addRelationProvider(String contentType, AbstractRelationProvider provider) { |
| Set<AbstractRelationProvider> providers = relationProviders.get(contentType); |
| if (providers == null) { |
| providers = new HashSet<AbstractRelationProvider>(); |
| relationProviders.put(contentType, providers); |
| } |
| providers.add(provider); |
| // TODO: need facility for removing |
| ContextCore.getContextManager().addListener(provider); |
| } |
| |
| /** |
| * @return all relation providers |
| */ |
| public Set<AbstractRelationProvider> getRelationProviders() { |
| Set<AbstractRelationProvider> allProviders = new HashSet<AbstractRelationProvider>(); |
| for (Set<AbstractRelationProvider> providers : relationProviders.values()) { |
| allProviders.addAll(providers); |
| } |
| return allProviders; |
| } |
| |
| public Set<AbstractRelationProvider> getRelationProviders(String contentType) { |
| return relationProviders.get(contentType); |
| } |
| |
| public static ContextCorePlugin getDefault() { |
| return INSTANCE; |
| } |
| |
| public static InteractionContextManager getContextManager() { |
| return INSTANCE.contextManager; |
| } |
| |
| public Map<String, AbstractContextStructureBridge> getStructureBridges() { |
| BridgesExtensionPointReader.initExtensions(); |
| return bridges; |
| } |
| |
| public List<IContextContributor> getContextContributor() { |
| initContextContributor(); |
| return contextContributor; |
| } |
| |
| void initContextContributor() { |
| if (!contextContributorInitialized) { |
| ExtensionPointReader<IContextContributor> extensionPointReader = new ExtensionPointReader<IContextContributor>( |
| ContextCorePlugin.ID_PLUGIN, ContextCorePlugin.EXTENSION_ID_CONTRIBUTOR, |
| ContextCorePlugin.EXTENSION_ELEMENT_CONTRIBUTOR, IContextContributor.class); |
| extensionPointReader.read(); |
| contextContributor = extensionPointReader.getItems(); |
| for (IContextContributor contributor : contextContributor) { |
| ContextCorePlugin.getContextManager().addListener(contributor); |
| } |
| contextContributorInitialized = true; |
| } |
| } |
| |
| public void addContextContributor(AbstractContextContributor contributor) { |
| contextContributor.add(contributor); |
| ContextCorePlugin.getContextManager().addListener(contributor); |
| } |
| |
| public void removeContextContributor(AbstractContextContributor contributor) { |
| contextContributor.remove(contributor); |
| ContextCorePlugin.getContextManager().removeListener(contributor); |
| } |
| |
| /** |
| * Finds the shadowed content for the passed in base content |
| * |
| * @param baseContent |
| * @return the shadowed content type or if null there is none |
| */ |
| private String getShadowedContentType(String baseContent) { |
| return contentTypeToShadowMap.get(baseContent); |
| } |
| |
| public AbstractContextStructureBridge getStructureBridge(String contentType) { |
| BridgesExtensionPointReader.initExtensions(); |
| if (contentType != null) { |
| // find the content type that shadows this one |
| // if one exists. |
| String shadowsContentType = getShadowedContentType(contentType); |
| if (shadowsContentType != null) { |
| AbstractContextStructureBridge bridge = bridges.get(shadowsContentType); |
| if (bridge != null) { |
| return bridge; |
| } |
| } |
| |
| // no shadowing of content, look at original content type |
| AbstractContextStructureBridge bridge = bridges.get(contentType); |
| if (bridge != null) { |
| return bridge; |
| } |
| } |
| return (defaultBridge == null) ? DEFAULT_BRIDGE : defaultBridge; |
| } |
| |
| public Set<String> getContentTypes() { |
| BridgesExtensionPointReader.initExtensions(); |
| return bridges.keySet(); |
| } |
| |
| /** |
| * TODO: cache this to improve performance? |
| */ |
| public AbstractContextStructureBridge getStructureBridge(Object object) { |
| BridgesExtensionPointReader.initExtensions(); |
| |
| for (Map.Entry<String, AbstractContextStructureBridge> entry : bridges.entrySet()) { |
| // check to see if there is shadowing of content types going on. |
| String shadowsContentType = getShadowedContentType(entry.getKey()); |
| if (shadowsContentType != null) { |
| AbstractContextStructureBridge structureBridge = bridges.get(shadowsContentType); |
| if (structureBridge.acceptsObject(object)) { |
| return structureBridge; |
| } |
| } |
| |
| // no shadowing...look at actual content type |
| AbstractContextStructureBridge bridge = entry.getValue(); |
| if (bridge != null && bridge.acceptsObject(object)) { |
| return bridge; |
| } |
| } |
| |
| // use the default if not found |
| return (defaultBridge != null && defaultBridge.acceptsObject(object)) ? defaultBridge : DEFAULT_BRIDGE; |
| } |
| |
| /** |
| * Recommended bridge registration is via extension point, but bridges can also be added at runtime. Note that only |
| * one bridge per content type is supported. Overriding content types is not supported. |
| */ |
| public synchronized void addStructureBridge(AbstractContextStructureBridge bridge) { |
| if (bridge.getContentType().equals(ContextCore.CONTENT_TYPE_RESOURCE)) { |
| defaultBridge = bridge; |
| } else { |
| bridges.put(bridge.getContentType(), bridge); |
| } |
| if (bridge.getParentContentType() != null) { |
| Set<String> childContentTypes = childContentTypeMap.get(bridge.getParentContentType()); |
| if (childContentTypes == null) { |
| // CopyOnWriteArrayList handles concurrent access to the content types |
| childContentTypes = new CopyOnWriteArraySet<String>(); |
| } |
| |
| childContentTypes.add(bridge.getContentType()); |
| childContentTypeMap.put(bridge.getParentContentType(), childContentTypes); |
| } |
| } |
| |
| public static LocalContextStore getContextStore() { |
| // if (!contextStoreRead) { |
| // contextStoreRead = true; |
| // ContextStoreExtensionReader.initExtensions(); |
| // if (contextStore != null) { |
| // contextStore.init(); |
| // } else { |
| // StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.PLUGIN_ID, "No context store specified")); |
| // } |
| // } |
| return contextStore; |
| } |
| |
| // public void setContextStore(AbstractContextStore contextStore) { |
| // ContextCorePlugin.contextStore = contextStore; |
| // } |
| |
| // static class ContextStoreExtensionReader { |
| // |
| // private static final String ELEMENT_CONTEXT_STORE = "contextStore"; |
| // |
| // private static boolean extensionsRead = false; |
| // |
| // public static void initExtensions() { |
| // if (!extensionsRead) { |
| // IExtensionRegistry registry = Platform.getExtensionRegistry(); |
| // IExtensionPoint extensionPoint = registry.getExtensionPoint(BridgesExtensionPointReader.EXTENSION_ID_CONTEXT); |
| // IExtension[] extensions = extensionPoint.getExtensions(); |
| // for (IExtension extension : extensions) { |
| // IConfigurationElement[] elements = extension.getConfigurationElements(); |
| // for (IConfigurationElement element : elements) { |
| // if (element.getName().compareTo(ELEMENT_CONTEXT_STORE) == 0) { |
| // readStore(element); |
| // } |
| // } |
| // } |
| // extensionsRead = true; |
| // } |
| // } |
| // |
| // private static void readStore(IConfigurationElement element) { |
| // // Currently disabled |
| // try { |
| // Object object = element.createExecutableExtension(BridgesExtensionPointReader.ATTR_CLASS); |
| // if (!(object instanceof AbstractContextStore)) { |
| // StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.PLUGIN_ID, |
| // "Could not load bridge: " + object.getClass().getCanonicalName() + " must implement " |
| // + AbstractContextStructureBridge.class.getCanonicalName())); |
| // return; |
| // } else { |
| // ContextCorePlugin.contextStore = (AbstractContextStore) object; |
| // } |
| // } catch (CoreException e) { |
| // StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.PLUGIN_ID, |
| // "Could not load bridge extension", e)); |
| // } |
| // } |
| // } |
| |
| static class BridgesExtensionPointReader { |
| |
| private static final String EXTENSION_ID_CONTEXT = "org.eclipse.mylyn.context.core.bridges"; //$NON-NLS-1$ |
| |
| private static final String EXTENSION_ID_INTERNAL_CONTEXT = "org.eclipse.mylyn.context.core.internalBridges"; //$NON-NLS-1$ |
| |
| private static final String EXTENSION_ID_RELATION_PROVIDERS = "org.eclipse.mylyn.context.core.relationProviders"; //$NON-NLS-1$ |
| |
| private static final String ELEMENT_STRUCTURE_BRIDGE = "structureBridge"; //$NON-NLS-1$ |
| |
| private static final String ELEMENT_RELATION_PROVIDER = "provider"; //$NON-NLS-1$ |
| |
| private static final String ELEMENT_SHADOW = "shadow"; //$NON-NLS-1$ |
| |
| private static final String ATTR_CLASS = "class"; //$NON-NLS-1$ |
| |
| private static final String ATTR_CONTENT_TYPE = "contentType"; //$NON-NLS-1$ |
| |
| private static final String ATTR_PARENT_CONTENT_TYPE = "parentContentType"; //$NON-NLS-1$ |
| |
| private static final String ATTR_BASE_CONTENT = "baseContent"; //$NON-NLS-1$ |
| |
| private static final String ATTR_SHADOWED_BY_CONTENT = "shadowedByContent"; //$NON-NLS-1$ |
| |
| private static boolean extensionsRead = false; |
| |
| public static void initExtensions() { |
| if (!extensionsRead) { |
| IExtensionRegistry registry = Platform.getExtensionRegistry(); |
| |
| IExtensionPoint extensionPoint = registry.getExtensionPoint(BridgesExtensionPointReader.EXTENSION_ID_CONTEXT); |
| IExtension[] extensions = extensionPoint.getExtensions(); |
| for (IExtension extension : extensions) { |
| IConfigurationElement[] elements = extension.getConfigurationElements(); |
| for (IConfigurationElement element : elements) { |
| if (element.getName().compareTo(BridgesExtensionPointReader.ELEMENT_STRUCTURE_BRIDGE) == 0) { |
| readBridge(element); |
| } |
| } |
| } |
| |
| // internal bridges |
| extensionPoint = registry.getExtensionPoint(BridgesExtensionPointReader.EXTENSION_ID_INTERNAL_CONTEXT); |
| extensions = extensionPoint.getExtensions(); |
| for (IExtension extension : extensions) { |
| IConfigurationElement[] elements = extension.getConfigurationElements(); |
| for (IConfigurationElement element : elements) { |
| if (element.getName().compareTo(BridgesExtensionPointReader.ELEMENT_SHADOW) == 0) { |
| readInternalBridge(element); |
| } |
| } |
| } |
| |
| extensionPoint = registry.getExtensionPoint(BridgesExtensionPointReader.EXTENSION_ID_RELATION_PROVIDERS); |
| extensions = extensionPoint.getExtensions(); |
| for (IExtension extension : extensions) { |
| IConfigurationElement[] elements = extension.getConfigurationElements(); |
| for (IConfigurationElement element : elements) { |
| if (element.getName().compareTo(BridgesExtensionPointReader.ELEMENT_RELATION_PROVIDER) == 0) { |
| readRelationProvider(element); |
| } |
| } |
| } |
| extensionsRead = true; |
| } |
| } |
| |
| private static void readBridge(IConfigurationElement element) { |
| try { |
| Object object = element.createExecutableExtension(BridgesExtensionPointReader.ATTR_CLASS); |
| if (!(object instanceof AbstractContextStructureBridge)) { |
| StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, |
| "Could not load bridge: " + object.getClass().getCanonicalName() + " must implement " //$NON-NLS-1$ //$NON-NLS-2$ |
| + AbstractContextStructureBridge.class.getCanonicalName())); |
| return; |
| } |
| |
| AbstractContextStructureBridge bridge = (AbstractContextStructureBridge) object; |
| if (element.getAttribute(BridgesExtensionPointReader.ATTR_PARENT_CONTENT_TYPE) != null) { |
| String parentContentType = element.getAttribute(BridgesExtensionPointReader.ATTR_PARENT_CONTENT_TYPE); |
| if (parentContentType != null) { |
| bridge.setParentContentType(parentContentType); |
| } |
| } |
| ContextCorePlugin.getDefault().addStructureBridge(bridge); |
| } catch (Throwable e) { |
| StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, |
| "Could not load bridge extension", e)); //$NON-NLS-1$ |
| } |
| } |
| |
| private static void readInternalBridge(IConfigurationElement element) { |
| String baseContent = element.getAttribute(ATTR_BASE_CONTENT); |
| String shadowedByContent = element.getAttribute(ATTR_SHADOWED_BY_CONTENT); |
| |
| if (baseContent == null || shadowedByContent == null) { |
| StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, |
| "Ignoring bridge shadowing because of invalid extension point " //$NON-NLS-1$ |
| + BridgesExtensionPointReader.ELEMENT_STRUCTURE_BRIDGE, new Exception())); |
| } |
| ContextCorePlugin.getDefault().addShadowsContent(baseContent, shadowedByContent); |
| } |
| |
| private static void readRelationProvider(IConfigurationElement element) { |
| try { |
| String contentType = element.getAttribute(BridgesExtensionPointReader.ATTR_CONTENT_TYPE); |
| AbstractRelationProvider relationProvider = (AbstractRelationProvider) element.createExecutableExtension(BridgesExtensionPointReader.ATTR_CLASS); |
| if (contentType != null) { |
| ContextCorePlugin.getDefault().addRelationProvider(contentType, relationProvider); |
| } |
| } catch (Throwable e) { |
| StatusHandler.log(new Status(IStatus.WARNING, ContextCorePlugin.ID_PLUGIN, |
| "Could not load relation provider", e)); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| public Set<String> getChildContentTypes(String contentType) { |
| Set<String> contentTypes = childContentTypeMap.get(contentType); |
| if (contentTypes != null) { |
| return contentTypes; |
| } else { |
| return Collections.emptySet(); |
| } |
| } |
| |
| public IInteractionContextScaling getCommonContextScaling() { |
| return commonContextScaling; |
| } |
| } |