-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.ui.workbench.editor;\r
-\r
-import java.io.IOException;\r
-import java.lang.ref.WeakReference;\r
-import java.net.URL;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.WeakHashMap;\r
-\r
-import org.eclipse.core.commands.contexts.ContextManagerEvent;\r
-import org.eclipse.core.commands.contexts.IContextManagerListener;\r
-import org.eclipse.core.runtime.CoreException;\r
-import org.eclipse.core.runtime.FileLocator;\r
-import org.eclipse.core.runtime.IConfigurationElement;\r
-import org.eclipse.core.runtime.IExtension;\r
-import org.eclipse.core.runtime.IExtensionPoint;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.MultiStatus;\r
-import org.eclipse.core.runtime.Platform;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;\r
-import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;\r
-import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;\r
-import org.eclipse.core.runtime.dynamichelpers.IFilter;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.ui.IEditorDescriptor;\r
-import org.eclipse.ui.IWorkbench;\r
-import org.eclipse.ui.PlatformUI;\r
-import org.eclipse.ui.contexts.IContextService;\r
-import org.osgi.framework.Bundle;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.common.request.PossibleIndexRoot;\r
-import org.simantics.db.common.utils.Logger;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.adapter.Instances;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.scl.reflection.OntologyVersions;\r
-import org.simantics.ui.internal.Activator;\r
-import org.simantics.ui.utils.ResourceAdaptionUtils;\r
-import org.simantics.utils.datastructures.MapList;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-import org.simantics.utils.ui.action.IPriorityAction;\r
-\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public final class EditorRegistry implements IExtensionChangeHandler, IEditorRegistry {\r
-\r
- /**\r
- * The maximum amount of entries to cache\r
- * {@link #getAdaptersFor(ReadGraph, Resource)} results for. Context activation\r
- * changes invalidate this cache.\r
- */\r
- private static final int MAX_CACHE_SIZE = 50;\r
-\r
-\r
- private final static String NAMESPACE = Activator.PLUGIN_ID;\r
-\r
- private final static String EP_NAME = "resourceEditorAdapter";\r
-\r
-\r
- private final static String EL_NAME_ADAPTER = "adapter";\r
-\r
- private final static String EL_NAME_ADAPTERCLASS = "adapterClass";\r
-\r
- private final static String EL_NAME_GROUP = "group";\r
-\r
- private final static String EL_NAME_IN_CONTEXT = "inContext";\r
-\r
-\r
- private static final String ATTR_CLASS = "class";\r
-\r
- private static final String ATTR_IMAGE = "image";\r
-\r
- private static final String ATTR_LABEL = "label";\r
-\r
- private static final String ATTR_TYPE_URIS = "type_uris";\r
-\r
- private static final String ATTR_EDITOR_ID = "editorId";\r
-\r
- private static final String ATTR_PRIORITY = "priority";\r
-\r
- private static final String ATTR_GROUP_ID = "groupId";\r
-\r
- private static final String ATTR_ID = "id";\r
-\r
-\r
- private static class Group {\r
- public final String id;\r
- public final List<EditorAdapterDescriptor> adapters;\r
-\r
- Group(String id) {\r
- this.id = id;\r
- this.adapters = new ArrayList<EditorAdapterDescriptor>();\r
- }\r
- Group(Group g) {\r
- this.id = g.id;\r
- this.adapters = new ArrayList<EditorAdapterDescriptor>(g.adapters);\r
- }\r
- void add(EditorAdapterDescriptor desc) {\r
- adapters.add(desc);\r
- }\r
- void remove(EditorAdapterDescriptor desc) {\r
- adapters.remove(desc);\r
- }\r
- }\r
-\r
- private static final EditorAdapter[] EMPTY_ADAPTER_ARRAY = new EditorAdapter[0];\r
-\r
- private static EditorRegistry INSTANCE;\r
-\r
- private ExtensionTracker tracker;\r
-\r
- private EditorAdapterDescriptorImpl[] extensions = new EditorAdapterDescriptorImpl[0];\r
-\r
- private Map<String, EditorAdapterDescriptorImpl> idToExtension = new HashMap<String, EditorAdapterDescriptorImpl>();\r
-\r
- private Map<String, Group> groupMap = new HashMap<String, Group>();\r
-\r
- private WeakHashMap<Object, EditorAdapter[]> adapterCache = new WeakHashMap<Object, EditorAdapter[]>(\r
- MAX_CACHE_SIZE);\r
-\r
- private CircularBuffer<Object> cacheQueue = new CircularBuffer<Object>(\r
- MAX_CACHE_SIZE);\r
-\r
- /**\r
- * A set of all the context id's that are currently referenced by all the\r
- * loaded resourceEditorAdapter extensions.\r
- */\r
- private Set<String> referencedContextIds = new HashSet<String>();\r
-\r
- /**\r
- * The current set of active contexts.\r
- */\r
- private Set<String> activeContextIds = new HashSet<String>();\r
-\r
- /**\r
- * Used to store all input -> editor mappings. In the Eclipse IDE, this\r
- * information is stored as persistent properties in each IFile represented\r
- * by eclipse Resource's. This implementation stores all the mappings in\r
- * this single map.\r
- * \r
- * Maybe in the future it would be possible to store these mapping in the\r
- * graph in a way that allows us not to publish those changes to the outside\r
- * world.\r
- */\r
- private final EditorMappingImpl editorMapping = new EditorMappingImpl();\r
-\r
- public synchronized static IEditorRegistry getInstance() {\r
- if (INSTANCE == null) {\r
- INSTANCE = new EditorRegistry();\r
- }\r
- return INSTANCE;\r
- }\r
-\r
- public static synchronized void dispose() {\r
- if (INSTANCE != null) {\r
- INSTANCE.close();\r
- INSTANCE = null;\r
- }\r
- }\r
-\r
- private EditorRegistry() {\r
- tracker = new ExtensionTracker();\r
-\r
- // Cache defined actions\r
- IExtensionPoint expt = Platform.getExtensionRegistry().getExtensionPoint(NAMESPACE, EP_NAME);\r
- loadExtensions(expt.getConfigurationElements());\r
-\r
- // Start tracking for new and removed extensions\r
- IFilter filter = ExtensionTracker.createExtensionPointFilter(expt);\r
- tracker.registerHandler(this, filter);\r
-\r
- hookListeners();\r
- }\r
-\r
- private void close() {\r
- unhookListeners();\r
-\r
- tracker.close();\r
- tracker = null;\r
-\r
- editorMapping.clear();\r
-\r
- extensions = null;\r
- idToExtension = null;\r
- groupMap = null;\r
- adapterCache = null;\r
- cacheQueue = null;\r
- }\r
-\r
-// /**\r
-// * Must reset {@link #getAdaptersFor(ReadGraph, Resource)} query caches when\r
-// * perspectives are changed because EditorAdapters may return\r
-// * different results in different perspectives.\r
-// */\r
-// private IPerspectiveListener perspectiveListener = new PerspectiveAdapter() {\r
-// public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {\r
-// clearCache();\r
-// }\r
-// };\r
-\r
- private final IContextManagerListener contextListener = new IContextManagerListener() {\r
- @Override\r
- public void contextManagerChanged(ContextManagerEvent event) {\r
- if (event.isActiveContextsChanged()) {\r
- //System.out.println("EVENT: " + event.isActiveContextsChanged() + ", " + event.isContextChanged() + ", " + event.isContextDefined() + ", " + Arrays.toString(event.getPreviouslyActiveContextIds().toArray()));\r
-\r
- Collection<?> active = event.getContextManager().getActiveContextIds();\r
- Collection<?> previouslyActive = event.getPreviouslyActiveContextIds();\r
-\r
- Collection<String> added = new HashSet<String>(4);\r
- Collection<String> removed = new HashSet<String>(4);\r
-\r
- for (Object o : active) {\r
- if (!previouslyActive.contains(o))\r
- added.add((String) o);\r
- }\r
- for (Object o : previouslyActive) {\r
- if (!active.contains(o))\r
- removed.add((String) o);\r
- }\r
-\r
- //System.out.println("ADDED: " + Arrays.toString(added.toArray()) + ", REMOVED: " + Arrays.toString(removed.toArray()));\r
- contextChange(added, removed);\r
- }\r
- }\r
- };\r
-\r
- private boolean containsAny(Collection<String> c, Collection<String> anyOf) {\r
- if (anyOf.isEmpty())\r
- return false;\r
- for (String s : anyOf)\r
- if (c.contains(s))\r
- return true;\r
- return false;\r
- }\r
-\r
- private void contextChange(Collection<String> added, Collection<String> removed) {\r
- // Update active context id set\r
- if (!added.isEmpty())\r
- activeContextIds.addAll(added);\r
- if (!removed.isEmpty())\r
- activeContextIds.remove(removed);\r
-\r
- // Clear caches if necessary\r
- if (containsAny(referencedContextIds, added) || containsAny(referencedContextIds, removed)) {\r
- clearCache();\r
- }\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- private void hookListeners() {\r
- IWorkbench wb = PlatformUI.getWorkbench();\r
- IContextService contextService = (IContextService) wb.getService(IContextService.class);\r
- contextService.addContextManagerListener(contextListener);\r
- activeContextIds = new HashSet<String>(contextService.getActiveContextIds());\r
- }\r
-\r
- private void unhookListeners() {\r
- IWorkbench wb = PlatformUI.getWorkbench();\r
- IContextService contextService = (IContextService) wb.getService(IContextService.class);\r
- if (contextService != null) {\r
- contextService.removeContextManagerListener(contextListener);\r
- }\r
- }\r
-\r
- private String[] parseInContexts(IConfigurationElement parent) {\r
- List<String> contexts = null;\r
- for (IConfigurationElement el : parent.getChildren(EL_NAME_IN_CONTEXT)) {\r
- String id = el.getAttribute(ATTR_ID);\r
- if (id != null) {\r
- if (contexts == null)\r
- contexts = new ArrayList<String>(4);\r
- contexts.add(id);\r
- }\r
- }\r
- return contexts != null ? contexts.toArray(new String[contexts.size()]) : null;\r
- }\r
-\r
- private synchronized void loadExtensions(IConfigurationElement[] elements) {\r
- org.eclipse.ui.IEditorRegistry editorRegistry = PlatformUI.getWorkbench().getEditorRegistry();\r
-\r
- Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));\r
- Map<String, Group> newGroups = new HashMap<String, Group>();\r
- Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);\r
-\r
- for (IConfigurationElement el : elements) {\r
- String name = el.getName();\r
- try {\r
- String id = el.getAttribute(ATTR_ID);\r
- String groupId = el.getAttribute(ATTR_GROUP_ID);\r
- EditorAdapter adapter = null;\r
-\r
- String priority = el.getAttribute(ATTR_PRIORITY);\r
- int pri = IPriorityAction.NORMAL;\r
- try {\r
- if (priority != null && !priority.trim().isEmpty())\r
- pri = Integer.parseInt(priority);\r
- } catch (NumberFormatException e) {\r
- ExceptionUtils.logError("Non-integer priority value '" + priority + "' for '" + name + "' extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", e);\r
- }\r
-\r
- String[] inContexts = null;\r
-\r
- if (EL_NAME_GROUP.equals(name)) {\r
- if (id == null || id.isEmpty()) {\r
- ExceptionUtils.logWarning("A group extension contributed by " + el.getDeclaringExtension().getContributor().getName() + " does not define a required id.", null);\r
- } else {\r
- if (!newGroups.containsKey(id)) {\r
- newGroups.put(id, new Group(id));\r
- }\r
- }\r
- continue;\r
- } else if (EL_NAME_ADAPTER.equals(name)) {\r
- String editorId = el.getAttribute(ATTR_EDITOR_ID);\r
- IEditorDescriptor editorDesc = editorRegistry.findEditor(editorId);\r
- if (editorDesc == null) {\r
- ExceptionUtils.logError("Non-existent editorId '" + editorId + "' in extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", null);\r
- continue;\r
- }\r
-\r
- String type_uris = OntologyVersions.getInstance().currentVersion(el.getAttribute(ATTR_TYPE_URIS));\r
- String[] typeUris = type_uris != null ? type_uris.split(",") : new String[0];\r
-\r
- String label = el.getAttribute(ATTR_LABEL);\r
- String image = el.getAttribute(ATTR_IMAGE);\r
- ImageDescriptor imageDesc = null;\r
- if (label == null)\r
- label = editorDesc.getLabel();\r
- if (image != null) {\r
- try {\r
- URL resolved = FileLocator.resolve(new URL(image));\r
- imageDesc = ImageDescriptor.createFromURL(resolved);\r
- } catch (IOException e) {\r
- // Try fallback method\r
- Bundle bundle = Platform.getBundle(el.getDeclaringExtension().getContributor().getName());\r
- imageDesc = ImageDescriptor.createFromURL(bundle.getEntry(image));\r
- }\r
- } else {\r
- imageDesc = editorDesc.getImageDescriptor();\r
- }\r
-\r
- SimpleEditorAdapter _adapter = new SimpleEditorAdapter(label, imageDesc, editorId, (String[]) null, typeUris);\r
- _adapter.setPriority(pri);\r
-\r
- adapter = _adapter;\r
-\r
- inContexts = parseInContexts(el);\r
- } else if (EL_NAME_ADAPTERCLASS.equals(name)) {\r
- adapter = (EditorAdapter) el.createExecutableExtension(ATTR_CLASS);\r
- if (adapter instanceof Prioritized) {\r
- ((Prioritized) adapter).setPriority(pri);\r
- }\r
- inContexts = parseInContexts(el);\r
- }\r
-\r
- if (adapter != null) {\r
- EditorAdapterDescriptorImpl ext = new EditorAdapterDescriptorImpl(id, groupId, adapter, inContexts);\r
- //System.out.println("Adding editor adapter extension from " + el.getContributor().getName() + ": " + ext.getId() + ", " + ext.getAdapter());\r
-\r
- // Start tracking the new extension object, its removal will be notified of\r
- // with removeExtension(extension, Object[]).\r
- tracker.registerObject(el.getDeclaringExtension(), ext, IExtensionTracker.REF_STRONG);\r
-\r
- if (id != null && !id.isEmpty()) {\r
- idToExtension.put(id, ext);\r
- }\r
- if (inContexts != null)\r
- for (String ctx : inContexts)\r
- newReferencedContextIds.add(ctx);\r
-\r
- newExtensions.add(ext);\r
- }\r
- } catch (CoreException e) {\r
- ExceptionUtils.logError("Failed to initialize resourceEditorAdapter extension \"" + name + "\": "\r
- + e.getMessage(), e);\r
- }\r
- }\r
-\r
- for (EditorAdapterDescriptorImpl desc : idToExtension.values()) {\r
- if (desc.getGroupId() != null) {\r
- Group g = newGroups.get(desc.getGroupId());\r
- if (g != null) {\r
- g.add(desc);\r
- }\r
- }\r
- }\r
-\r
- clearCache();\r
- this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);\r
- this.groupMap = newGroups;\r
- this.referencedContextIds = newReferencedContextIds;\r
- }\r
-\r
-\r
- @Override\r
- public void addExtension(IExtensionTracker tracker, IExtension extension) {\r
- loadExtensions(extension.getConfigurationElements());\r
- }\r
-\r
- @Override\r
- public synchronized void removeExtension(IExtension extension, Object[] objects) {\r
- Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));\r
- Map<String, EditorAdapterDescriptorImpl> idMap = new HashMap<String, EditorAdapterDescriptorImpl>(idToExtension);\r
- Set<String> removedContextReferences = new HashSet<String>();\r
-\r
- Map<String, Group> newGroups = new HashMap<String, Group>();\r
- for (Group g : groupMap.values()) {\r
- newGroups.put(g.id, new Group(g));\r
- }\r
-\r
- for (Object o : objects) {\r
- EditorAdapterDescriptor ext = (EditorAdapterDescriptor) o;\r
-\r
- tracker.unregisterObject(extension, o);\r
- newExtensions.remove(ext);\r
- idMap.remove(ext.getId());\r
-\r
- if (ext.getGroupId() != null) {\r
- Group g = newGroups.get(ext.getGroupId());\r
- if (g != null) {\r
- g.remove(ext);\r
- }\r
- }\r
- for (String ctx : ext.getInContexts())\r
- removedContextReferences.add(ctx);\r
- }\r
-\r
- // Go through the remaining editor adapters and\r
- // check whether they still reference any of the removed\r
- // context ids. Ids that are still referenced will not be\r
- // removed from <code>referencedContextIds</code>\r
- for (EditorAdapterDescriptorImpl desc : newExtensions) {\r
- for (String ctx : desc.getInContexts()) {\r
- removedContextReferences.remove(ctx);\r
- }\r
- }\r
- Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);\r
- newReferencedContextIds.removeAll(removedContextReferences);\r
-\r
- // Atomic assignment\r
- this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);\r
- this.idToExtension = idMap;\r
- this.groupMap = newGroups;\r
- this.referencedContextIds = newReferencedContextIds;\r
- }\r
-\r
- @Override\r
- public EditorAdapterDescriptor getExtensionById(String id) {\r
- return idToExtension.get(id);\r
- }\r
-\r
- @Override\r
- public EditorAdapter getAdapterById(String id) {\r
- EditorAdapterDescriptor ext = idToExtension.get(id);\r
- return ext == null ? null : ext.getAdapter();\r
- }\r
-\r
- private void clearCache() {\r
- synchronized (adapterCache) {\r
- adapterCache.clear();\r
- cacheQueue.clear();\r
- }\r
- }\r
-\r
- @Override\r
- public EditorAdapterDescriptor[] getEditorAdapters() {\r
- return extensions;\r
- }\r
-\r
- @Override\r
- public EditorAdapter[] getAdaptersFor(ReadGraph g, final Object r) throws DatabaseException {\r
- \r
- EditorAdapter[] result;\r
- synchronized (adapterCache) {\r
- result = adapterCache.get(r);\r
- if (result != null)\r
- return result;\r
- }\r
-\r
- MultiStatus status = null;\r
-\r
- final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();\r
- for (EditorAdapterDescriptor a : extensions) {\r
- try {\r
- // Filter out adapters that are not active in the current context configuration.\r
- if (!a.isActive(activeContextIds))\r
- continue;\r
- // Filter out adapters that just can't handle the input.\r
- if (!a.getAdapter().canHandle(g, r))\r
- continue;\r
-\r
- // NOTE: Group is null if there is no group.\r
- l.add(a.getGroupId(), a.getAdapter());\r
- } catch (RuntimeException e) {\r
- if (status == null)\r
- status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);\r
- status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
- }\r
- }\r
- \r
- \r
- Resource res = ResourceAdaptionUtils.toSingleResource(r);\r
- if (res != null) {\r
- ModelingResources MOD = ModelingResources.getInstance(g);\r
- Resource indexRoot = g.syncRequest(new PossibleIndexRoot(res));\r
- if (indexRoot != null) {\r
- Instances query = g.adapt(MOD.EditorContribution, Instances.class);\r
- for(Resource contribution : query.find(g, indexRoot)) {\r
-\r
- try {\r
-\r
- String id = g.getRelatedValue(contribution, MOD.EditorContribution_editorId, Bindings.STRING);\r
- String label = NameUtils.getSafeLabel(g, contribution);\r
-\r
- Resource imageResource = g.getPossibleObject(contribution, MOD.EditorContribution_HasImage);\r
- ImageDescriptor image = imageResource == null ? null : g.adapt(imageResource, ImageDescriptor.class);\r
-\r
- Integer priority = g.getRelatedValue(contribution, MOD.EditorContribution_priority, Bindings.INTEGER);\r
- EditorAdapterDescriptor a = new GraphEditorAdapterDescriptor(id, label, image, contribution, priority);\r
-\r
- // Filter out adapters that are not active in the current context configuration.\r
- if (!a.isActive(activeContextIds))\r
- continue;\r
- // Filter out adapters that just can't handle the input.\r
- if (!a.getAdapter().canHandle(g, r))\r
- continue;\r
-\r
- l.add(a.getGroupId(), a.getAdapter());\r
-\r
- } catch (DatabaseException e) {\r
- Logger.defaultLogError(e);\r
- }\r
- }\r
- }\r
- }\r
- \r
- result = gatherAdapterResult(l);\r
- updateCache(r, result);\r
-\r
- if (status != null && !status.isOK())\r
- Activator.getDefault().getLog().log(status);\r
-\r
- return result;\r
- }\r
-\r
- private EditorAdapter[] gatherAdapterResult(MapList<String, EditorAdapter> map) {\r
- final List<EditorAdapter> result = new ArrayList<EditorAdapter>(8);\r
- for (String group : map.getKeys()) {\r
- List<EditorAdapter> grp = map.getValues(group);\r
- if (group == null) {\r
- if (grp != null)\r
- result.addAll(grp);\r
- } else {\r
- EditorAdapter highestPriorityAdapter = null;\r
- for (EditorAdapter adapter : grp) {\r
- if (highestPriorityAdapter == null || adapter.getPriority() > highestPriorityAdapter.getPriority()) {\r
- highestPriorityAdapter = adapter;\r
- }\r
- }\r
- result.add(highestPriorityAdapter);\r
- }\r
- }\r
- return result.toArray(EMPTY_ADAPTER_ARRAY);\r
- }\r
-\r
- @Override\r
- public EditorMapping getMappings() {\r
- return editorMapping;\r
- }\r
-\r
- private void updateCache(Object r, EditorAdapter[] result) {\r
- synchronized (adapterCache) {\r
- adapterCache.put(r, result);\r
- Object removed = cacheQueue.write(r);\r
- if (removed != null) {\r
- adapterCache.remove(removed);\r
- }\r
- }\r
- }\r
-\r
-\r
- private static class CircularBuffer<T> {\r
- private final WeakReference<?>[] buffer;\r
-\r
- private int head;\r
- private int tail;\r
- private boolean full;\r
- private final int size;\r
-\r
- CircularBuffer(int size) {\r
- if (size == 0)\r
- throw new IllegalArgumentException("zero size not allowed");\r
-\r
- this.buffer = new WeakReference[size];\r
- this.size = size;\r
- clear();\r
- }\r
-\r
- public void clear() {\r
- this.head = this.tail = 0;\r
- this.full = false;\r
- Arrays.fill(buffer, null);\r
- }\r
-\r
- /**\r
- * @param id an ID, other than 0L\r
- * @return 0L if the buffer was not yet full, otherwise\r
- * @throws IllegalArgumentException for 0L id\r
- */\r
- @SuppressWarnings("unchecked")\r
- T write(T id) {\r
- if (id == null)\r
- throw new IllegalArgumentException("null resource id");\r
-\r
- if (full) {\r
- WeakReference<?> prev = buffer[head];\r
- buffer[head++] = new WeakReference<T>(id);\r
- head %= size;\r
- tail = head;\r
- return (T) prev.get();\r
- } else {\r
- buffer[head++] = new WeakReference<T>(id);\r
- head %= size;\r
- if (head == tail) {\r
- full = true;\r
- }\r
- }\r
- // Nothing was yet overwritten\r
- return null;\r
- }\r
-\r
- @Override\r
- public String toString() {\r
- return Arrays.toString(buffer);\r
- }\r
- }\r
-\r
-\r
- @Override\r
- public EditorAdapter[] getDefaultAdaptersFor(ReadGraph g, Object r) throws DatabaseException {\r
- EditorAdapter[] results;\r
-\r
- MultiStatus status = null;\r
-\r
- final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();\r
- for (EditorAdapterDescriptor a : extensions) {\r
- try {\r
-\r
- // NOTE: Group is null if there is no group.\r
- l.add(a.getGroupId(), a.getAdapter());\r
- } catch (RuntimeException e) {\r
- if (status == null)\r
- status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);\r
- status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
- }\r
- }\r
- results = gatherAdapterResult(l);\r
-\r
- if (status != null && !status.isOK())\r
- Activator.getDefault().getLog().log(status);\r
-\r
- // If no default editor is found, get all that can handle\r
- if (results.length > 0)\r
- return results;\r
- else\r
- return getAdaptersFor(g, r);\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * 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:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.ui.workbench.editor;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import org.eclipse.core.commands.contexts.ContextManagerEvent;
+import org.eclipse.core.commands.contexts.IContextManagerListener;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
+import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
+import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
+import org.eclipse.core.runtime.dynamichelpers.IFilter;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.IEditorDescriptor;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.contexts.IContextService;
+import org.osgi.framework.Bundle;
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.request.PossibleIndexRoot;
+import org.simantics.db.common.utils.Logger;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.adapter.Instances;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scl.reflection.OntologyVersions;
+import org.simantics.ui.internal.Activator;
+import org.simantics.ui.utils.ResourceAdaptionUtils;
+import org.simantics.utils.datastructures.MapList;
+import org.simantics.utils.ui.ExceptionUtils;
+import org.simantics.utils.ui.action.IPriorityAction;
+
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public final class EditorRegistry implements IExtensionChangeHandler, IEditorRegistry {
+
+ /**
+ * The maximum amount of entries to cache
+ * {@link #getAdaptersFor(ReadGraph, Resource)} results for. Context activation
+ * changes invalidate this cache.
+ */
+ private static final int MAX_CACHE_SIZE = 50;
+
+
+ private final static String NAMESPACE = Activator.PLUGIN_ID;
+
+ private final static String EP_NAME = "resourceEditorAdapter";
+
+
+ private final static String EL_NAME_ADAPTER = "adapter";
+
+ private final static String EL_NAME_ADAPTERCLASS = "adapterClass";
+
+ private final static String EL_NAME_GROUP = "group";
+
+ private final static String EL_NAME_IN_CONTEXT = "inContext";
+
+
+ private static final String ATTR_CLASS = "class";
+
+ private static final String ATTR_IMAGE = "image";
+
+ private static final String ATTR_LABEL = "label";
+
+ private static final String ATTR_TYPE_URIS = "type_uris";
+
+ private static final String ATTR_EDITOR_ID = "editorId";
+
+ private static final String ATTR_PRIORITY = "priority";
+
+ private static final String ATTR_GROUP_ID = "groupId";
+
+ private static final String ATTR_ID = "id";
+
+ private static final Comparator<EditorAdapter> ADAPTER_COMPARATOR = (o1, o2) -> -(o1.getPriority() - o2.getPriority());
+
+ private static class Group {
+ public final String id;
+ public final List<EditorAdapterDescriptor> adapters;
+
+ Group(String id) {
+ this.id = id;
+ this.adapters = new ArrayList<EditorAdapterDescriptor>();
+ }
+ Group(Group g) {
+ this.id = g.id;
+ this.adapters = new ArrayList<EditorAdapterDescriptor>(g.adapters);
+ }
+ void add(EditorAdapterDescriptor desc) {
+ adapters.add(desc);
+ }
+ void remove(EditorAdapterDescriptor desc) {
+ adapters.remove(desc);
+ }
+ }
+
+ private static final EditorAdapter[] EMPTY_ADAPTER_ARRAY = new EditorAdapter[0];
+
+ private static EditorRegistry INSTANCE;
+
+ private ExtensionTracker tracker;
+
+ private EditorAdapterDescriptorImpl[] extensions = new EditorAdapterDescriptorImpl[0];
+
+ private Map<String, EditorAdapterDescriptorImpl> idToExtension = new HashMap<String, EditorAdapterDescriptorImpl>();
+
+ private Map<String, Group> groupMap = new HashMap<String, Group>();
+
+ private WeakHashMap<Object, EditorAdapter[]> adapterCache = new WeakHashMap<Object, EditorAdapter[]>(
+ MAX_CACHE_SIZE);
+
+ private CircularBuffer<Object> cacheQueue = new CircularBuffer<Object>(
+ MAX_CACHE_SIZE);
+
+ /**
+ * A set of all the context id's that are currently referenced by all the
+ * loaded resourceEditorAdapter extensions.
+ */
+ private Set<String> referencedContextIds = new HashSet<String>();
+
+ /**
+ * The current set of active contexts.
+ */
+ private Set<String> activeContextIds = new HashSet<String>();
+
+ /**
+ * Used to store all input -> editor mappings. In the Eclipse IDE, this
+ * information is stored as persistent properties in each IFile represented
+ * by eclipse Resource's. This implementation stores all the mappings in
+ * this single map.
+ *
+ * Maybe in the future it would be possible to store these mapping in the
+ * graph in a way that allows us not to publish those changes to the outside
+ * world.
+ */
+ private final EditorMappingImpl editorMapping = new EditorMappingImpl();
+
+ public synchronized static IEditorRegistry getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new EditorRegistry();
+ }
+ return INSTANCE;
+ }
+
+ public static synchronized void dispose() {
+ if (INSTANCE != null) {
+ INSTANCE.close();
+ INSTANCE = null;
+ }
+ }
+
+ private EditorRegistry() {
+ tracker = new ExtensionTracker();
+
+ // Cache defined actions
+ IExtensionPoint expt = Platform.getExtensionRegistry().getExtensionPoint(NAMESPACE, EP_NAME);
+ loadExtensions(expt.getConfigurationElements());
+
+ // Start tracking for new and removed extensions
+ IFilter filter = ExtensionTracker.createExtensionPointFilter(expt);
+ tracker.registerHandler(this, filter);
+
+ hookListeners();
+ }
+
+ private void close() {
+ unhookListeners();
+
+ tracker.close();
+ tracker = null;
+
+ editorMapping.clear();
+
+ extensions = null;
+ idToExtension = null;
+ groupMap = null;
+ adapterCache = null;
+ cacheQueue = null;
+ }
+
+// /**
+// * Must reset {@link #getAdaptersFor(ReadGraph, Resource)} query caches when
+// * perspectives are changed because EditorAdapters may return
+// * different results in different perspectives.
+// */
+// private IPerspectiveListener perspectiveListener = new PerspectiveAdapter() {
+// public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {
+// clearCache();
+// }
+// };
+
+ private final IContextManagerListener contextListener = new IContextManagerListener() {
+ @Override
+ public void contextManagerChanged(ContextManagerEvent event) {
+ if (event.isActiveContextsChanged()) {
+ //System.out.println("EVENT: " + event.isActiveContextsChanged() + ", " + event.isContextChanged() + ", " + event.isContextDefined() + ", " + Arrays.toString(event.getPreviouslyActiveContextIds().toArray()));
+
+ Collection<?> active = event.getContextManager().getActiveContextIds();
+ Collection<?> previouslyActive = event.getPreviouslyActiveContextIds();
+
+ Collection<String> added = new HashSet<String>(4);
+ Collection<String> removed = new HashSet<String>(4);
+
+ for (Object o : active) {
+ if (!previouslyActive.contains(o))
+ added.add((String) o);
+ }
+ for (Object o : previouslyActive) {
+ if (!active.contains(o))
+ removed.add((String) o);
+ }
+
+ //System.out.println("ADDED: " + Arrays.toString(added.toArray()) + ", REMOVED: " + Arrays.toString(removed.toArray()));
+ contextChange(added, removed);
+ }
+ }
+ };
+
+ private boolean containsAny(Collection<String> c, Collection<String> anyOf) {
+ if (anyOf.isEmpty())
+ return false;
+ for (String s : anyOf)
+ if (c.contains(s))
+ return true;
+ return false;
+ }
+
+ private void contextChange(Collection<String> added, Collection<String> removed) {
+ // Update active context id set
+ if (!added.isEmpty())
+ activeContextIds.addAll(added);
+ if (!removed.isEmpty())
+ activeContextIds.remove(removed);
+
+ // Clear caches if necessary
+ if (containsAny(referencedContextIds, added) || containsAny(referencedContextIds, removed)) {
+ clearCache();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void hookListeners() {
+ IWorkbench wb = PlatformUI.getWorkbench();
+ IContextService contextService = (IContextService) wb.getService(IContextService.class);
+ contextService.addContextManagerListener(contextListener);
+ activeContextIds = new HashSet<String>(contextService.getActiveContextIds());
+ }
+
+ private void unhookListeners() {
+ IWorkbench wb = PlatformUI.getWorkbench();
+ IContextService contextService = (IContextService) wb.getService(IContextService.class);
+ if (contextService != null) {
+ contextService.removeContextManagerListener(contextListener);
+ }
+ }
+
+ private String[] parseInContexts(IConfigurationElement parent) {
+ List<String> contexts = null;
+ for (IConfigurationElement el : parent.getChildren(EL_NAME_IN_CONTEXT)) {
+ String id = el.getAttribute(ATTR_ID);
+ if (id != null) {
+ if (contexts == null)
+ contexts = new ArrayList<String>(4);
+ contexts.add(id);
+ }
+ }
+ return contexts != null ? contexts.toArray(new String[contexts.size()]) : null;
+ }
+
+ private synchronized void loadExtensions(IConfigurationElement[] elements) {
+ org.eclipse.ui.IEditorRegistry editorRegistry = PlatformUI.getWorkbench().getEditorRegistry();
+
+ Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));
+ Map<String, Group> newGroups = new HashMap<String, Group>();
+ Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);
+
+ for (IConfigurationElement el : elements) {
+ String name = el.getName();
+ try {
+ String id = el.getAttribute(ATTR_ID);
+ String groupId = el.getAttribute(ATTR_GROUP_ID);
+ EditorAdapter adapter = null;
+
+ String priority = el.getAttribute(ATTR_PRIORITY);
+ int pri = IPriorityAction.NORMAL;
+ try {
+ if (priority != null && !priority.trim().isEmpty())
+ pri = Integer.parseInt(priority);
+ } catch (NumberFormatException e) {
+ ExceptionUtils.logError("Non-integer priority value '" + priority + "' for '" + name + "' extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", e);
+ }
+
+ String[] inContexts = null;
+
+ if (EL_NAME_GROUP.equals(name)) {
+ if (id == null || id.isEmpty()) {
+ ExceptionUtils.logWarning("A group extension contributed by " + el.getDeclaringExtension().getContributor().getName() + " does not define a required id.", null);
+ } else {
+ if (!newGroups.containsKey(id)) {
+ newGroups.put(id, new Group(id));
+ }
+ }
+ continue;
+ } else if (EL_NAME_ADAPTER.equals(name)) {
+ String editorId = el.getAttribute(ATTR_EDITOR_ID);
+ IEditorDescriptor editorDesc = editorRegistry.findEditor(editorId);
+ if (editorDesc == null) {
+ ExceptionUtils.logError("Non-existent editorId '" + editorId + "' in extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", null);
+ continue;
+ }
+
+ String type_uris = OntologyVersions.getInstance().currentVersion(el.getAttribute(ATTR_TYPE_URIS));
+ String[] typeUris = type_uris != null ? type_uris.split(",") : new String[0];
+
+ String label = el.getAttribute(ATTR_LABEL);
+ String image = el.getAttribute(ATTR_IMAGE);
+ ImageDescriptor imageDesc = null;
+ if (label == null)
+ label = editorDesc.getLabel();
+ if (image != null) {
+ try {
+ URL resolved = FileLocator.resolve(new URL(image));
+ imageDesc = ImageDescriptor.createFromURL(resolved);
+ } catch (IOException e) {
+ // Try fallback method
+ Bundle bundle = Platform.getBundle(el.getDeclaringExtension().getContributor().getName());
+ imageDesc = ImageDescriptor.createFromURL(bundle.getEntry(image));
+ }
+ } else {
+ imageDesc = editorDesc.getImageDescriptor();
+ }
+
+ SimpleEditorAdapter _adapter = new SimpleEditorAdapter(label, imageDesc, editorId, (String[]) null, typeUris);
+ _adapter.setPriority(pri);
+
+ adapter = _adapter;
+
+ inContexts = parseInContexts(el);
+ } else if (EL_NAME_ADAPTERCLASS.equals(name)) {
+ adapter = (EditorAdapter) el.createExecutableExtension(ATTR_CLASS);
+ if (adapter instanceof Prioritized) {
+ ((Prioritized) adapter).setPriority(pri);
+ }
+ inContexts = parseInContexts(el);
+ }
+
+ if (adapter != null) {
+ EditorAdapterDescriptorImpl ext = new EditorAdapterDescriptorImpl(id, groupId, adapter, inContexts);
+ //System.out.println("Adding editor adapter extension from " + el.getContributor().getName() + ": " + ext.getId() + ", " + ext.getAdapter());
+
+ // Start tracking the new extension object, its removal will be notified of
+ // with removeExtension(extension, Object[]).
+ tracker.registerObject(el.getDeclaringExtension(), ext, IExtensionTracker.REF_STRONG);
+
+ if (id != null && !id.isEmpty()) {
+ idToExtension.put(id, ext);
+ }
+ if (inContexts != null)
+ for (String ctx : inContexts)
+ newReferencedContextIds.add(ctx);
+
+ newExtensions.add(ext);
+ }
+ } catch (CoreException e) {
+ ExceptionUtils.logError("Failed to initialize resourceEditorAdapter extension \"" + name + "\": "
+ + e.getMessage(), e);
+ }
+ }
+
+ for (EditorAdapterDescriptorImpl desc : idToExtension.values()) {
+ if (desc.getGroupId() != null) {
+ Group g = newGroups.get(desc.getGroupId());
+ if (g != null) {
+ g.add(desc);
+ }
+ }
+ }
+
+ clearCache();
+ this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);
+ this.groupMap = newGroups;
+ this.referencedContextIds = newReferencedContextIds;
+ }
+
+
+ @Override
+ public void addExtension(IExtensionTracker tracker, IExtension extension) {
+ loadExtensions(extension.getConfigurationElements());
+ }
+
+ @Override
+ public synchronized void removeExtension(IExtension extension, Object[] objects) {
+ Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));
+ Map<String, EditorAdapterDescriptorImpl> idMap = new HashMap<String, EditorAdapterDescriptorImpl>(idToExtension);
+ Set<String> removedContextReferences = new HashSet<String>();
+
+ Map<String, Group> newGroups = new HashMap<String, Group>();
+ for (Group g : groupMap.values()) {
+ newGroups.put(g.id, new Group(g));
+ }
+
+ for (Object o : objects) {
+ EditorAdapterDescriptor ext = (EditorAdapterDescriptor) o;
+
+ tracker.unregisterObject(extension, o);
+ newExtensions.remove(ext);
+ idMap.remove(ext.getId());
+
+ if (ext.getGroupId() != null) {
+ Group g = newGroups.get(ext.getGroupId());
+ if (g != null) {
+ g.remove(ext);
+ }
+ }
+ for (String ctx : ext.getInContexts())
+ removedContextReferences.add(ctx);
+ }
+
+ // Go through the remaining editor adapters and
+ // check whether they still reference any of the removed
+ // context ids. Ids that are still referenced will not be
+ // removed from <code>referencedContextIds</code>
+ for (EditorAdapterDescriptorImpl desc : newExtensions) {
+ for (String ctx : desc.getInContexts()) {
+ removedContextReferences.remove(ctx);
+ }
+ }
+ Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);
+ newReferencedContextIds.removeAll(removedContextReferences);
+
+ // Atomic assignment
+ this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);
+ this.idToExtension = idMap;
+ this.groupMap = newGroups;
+ this.referencedContextIds = newReferencedContextIds;
+ }
+
+ @Override
+ public EditorAdapterDescriptor getExtensionById(String id) {
+ return idToExtension.get(id);
+ }
+
+ @Override
+ public EditorAdapter getAdapterById(String id) {
+ EditorAdapterDescriptor ext = idToExtension.get(id);
+ return ext == null ? null : ext.getAdapter();
+ }
+
+ private void clearCache() {
+ synchronized (adapterCache) {
+ adapterCache.clear();
+ cacheQueue.clear();
+ }
+ }
+
+ @Override
+ public EditorAdapterDescriptor[] getEditorAdapters() {
+ return extensions;
+ }
+
+ @Override
+ public EditorAdapter[] getAdaptersFor(ReadGraph g, final Object r) throws DatabaseException {
+
+ EditorAdapter[] result;
+ synchronized (adapterCache) {
+ result = adapterCache.get(r);
+ if (result != null)
+ return result;
+ }
+
+ MultiStatus status = null;
+
+ final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();
+ for (EditorAdapterDescriptor a : extensions) {
+ try {
+ // Filter out adapters that are not active in the current context configuration.
+ if (!a.isActive(activeContextIds))
+ continue;
+ // Filter out adapters that just can't handle the input.
+ if (!a.getAdapter().canHandle(g, r))
+ continue;
+
+ // NOTE: Group is null if there is no group.
+ l.add(a.getGroupId(), a.getAdapter());
+ } catch (RuntimeException e) {
+ if (status == null)
+ status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);
+ status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
+ }
+ }
+
+
+ Resource res = ResourceAdaptionUtils.toSingleResource(r);
+ if (res != null) {
+ ModelingResources MOD = ModelingResources.getInstance(g);
+ Resource indexRoot = g.syncRequest(new PossibleIndexRoot(res));
+ if (indexRoot != null) {
+ Instances query = g.adapt(MOD.EditorContribution, Instances.class);
+ for(Resource contribution : query.find(g, indexRoot)) {
+
+ try {
+
+ String id = g.getRelatedValue(contribution, MOD.EditorContribution_editorId, Bindings.STRING);
+ String label = NameUtils.getSafeLabel(g, contribution);
+
+ Resource imageResource = g.getPossibleObject(contribution, MOD.EditorContribution_HasImage);
+ ImageDescriptor image = imageResource == null ? null : g.adapt(imageResource, ImageDescriptor.class);
+
+ Integer priority = g.getRelatedValue(contribution, MOD.EditorContribution_priority, Bindings.INTEGER);
+ EditorAdapterDescriptor a = new GraphEditorAdapterDescriptor(id, label, image, contribution, priority);
+
+ // Filter out adapters that are not active in the current context configuration.
+ if (!a.isActive(activeContextIds))
+ continue;
+ // Filter out adapters that just can't handle the input.
+ if (!a.getAdapter().canHandle(g, r))
+ continue;
+
+ l.add(a.getGroupId(), a.getAdapter());
+
+ } catch (DatabaseException e) {
+ Logger.defaultLogError(e);
+ }
+ }
+ }
+ }
+
+ result = gatherAdapterResult(l);
+
+ Arrays.sort(result, ADAPTER_COMPARATOR);
+
+ updateCache(r, result);
+
+ if (status != null && !status.isOK())
+ Activator.getDefault().getLog().log(status);
+
+ return result;
+ }
+
+ private EditorAdapter[] gatherAdapterResult(MapList<String, EditorAdapter> map) {
+ final List<EditorAdapter> result = new ArrayList<EditorAdapter>(8);
+ for (String group : map.getKeys()) {
+ List<EditorAdapter> grp = map.getValues(group);
+ if (group == null) {
+ if (grp != null)
+ result.addAll(grp);
+ } else {
+ EditorAdapter highestPriorityAdapter = null;
+ for (EditorAdapter adapter : grp) {
+ if (highestPriorityAdapter == null || adapter.getPriority() > highestPriorityAdapter.getPriority()) {
+ highestPriorityAdapter = adapter;
+ }
+ }
+ result.add(highestPriorityAdapter);
+ }
+ }
+ return result.toArray(EMPTY_ADAPTER_ARRAY);
+ }
+
+ @Override
+ public EditorMapping getMappings() {
+ return editorMapping;
+ }
+
+ private void updateCache(Object r, EditorAdapter[] result) {
+ synchronized (adapterCache) {
+ adapterCache.put(r, result);
+ Object removed = cacheQueue.write(r);
+ if (removed != null) {
+ adapterCache.remove(removed);
+ }
+ }
+ }
+
+
+ private static class CircularBuffer<T> {
+ private final WeakReference<?>[] buffer;
+
+ private int head;
+ private int tail;
+ private boolean full;
+ private final int size;
+
+ CircularBuffer(int size) {
+ if (size == 0)
+ throw new IllegalArgumentException("zero size not allowed");
+
+ this.buffer = new WeakReference[size];
+ this.size = size;
+ clear();
+ }
+
+ public void clear() {
+ this.head = this.tail = 0;
+ this.full = false;
+ Arrays.fill(buffer, null);
+ }
+
+ /**
+ * @param id an ID, other than 0L
+ * @return 0L if the buffer was not yet full, otherwise
+ * @throws IllegalArgumentException for 0L id
+ */
+ @SuppressWarnings("unchecked")
+ T write(T id) {
+ if (id == null)
+ throw new IllegalArgumentException("null resource id");
+
+ if (full) {
+ WeakReference<?> prev = buffer[head];
+ buffer[head++] = new WeakReference<T>(id);
+ head %= size;
+ tail = head;
+ return (T) prev.get();
+ } else {
+ buffer[head++] = new WeakReference<T>(id);
+ head %= size;
+ if (head == tail) {
+ full = true;
+ }
+ }
+ // Nothing was yet overwritten
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(buffer);
+ }
+ }
+
+
+ @Override
+ public EditorAdapter[] getDefaultAdaptersFor(ReadGraph g, Object r) throws DatabaseException {
+ EditorAdapter[] results;
+
+ MultiStatus status = null;
+
+ final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();
+ for (EditorAdapterDescriptor a : extensions) {
+ try {
+
+ // NOTE: Group is null if there is no group.
+ l.add(a.getGroupId(), a.getAdapter());
+ } catch (RuntimeException e) {
+ if (status == null)
+ status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);
+ status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
+ }
+ }
+ results = gatherAdapterResult(l);
+
+ if (status != null && !status.isOK())
+ Activator.getDefault().getLog().log(status);
+
+ // If no default editor is found, get all that can handle
+ if (results.length > 0)
+ return results;
+ else
+ return getAdaptersFor(g, r);
+ }
+
+}