]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.ui/src/org/simantics/ui/workbench/editor/EditorRegistry.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.ui / src / org / simantics / ui / workbench / editor / EditorRegistry.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.ui.workbench.editor;\r
13 \r
14 import java.io.IOException;\r
15 import java.lang.ref.WeakReference;\r
16 import java.net.URL;\r
17 import java.util.ArrayList;\r
18 import java.util.Arrays;\r
19 import java.util.Collection;\r
20 import java.util.HashMap;\r
21 import java.util.HashSet;\r
22 import java.util.List;\r
23 import java.util.Map;\r
24 import java.util.Set;\r
25 import java.util.WeakHashMap;\r
26 \r
27 import org.eclipse.core.commands.contexts.ContextManagerEvent;\r
28 import org.eclipse.core.commands.contexts.IContextManagerListener;\r
29 import org.eclipse.core.runtime.CoreException;\r
30 import org.eclipse.core.runtime.FileLocator;\r
31 import org.eclipse.core.runtime.IConfigurationElement;\r
32 import org.eclipse.core.runtime.IExtension;\r
33 import org.eclipse.core.runtime.IExtensionPoint;\r
34 import org.eclipse.core.runtime.IStatus;\r
35 import org.eclipse.core.runtime.MultiStatus;\r
36 import org.eclipse.core.runtime.Platform;\r
37 import org.eclipse.core.runtime.Status;\r
38 import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;\r
39 import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;\r
40 import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;\r
41 import org.eclipse.core.runtime.dynamichelpers.IFilter;\r
42 import org.eclipse.jface.resource.ImageDescriptor;\r
43 import org.eclipse.ui.IEditorDescriptor;\r
44 import org.eclipse.ui.IWorkbench;\r
45 import org.eclipse.ui.PlatformUI;\r
46 import org.eclipse.ui.contexts.IContextService;\r
47 import org.osgi.framework.Bundle;\r
48 import org.simantics.databoard.Bindings;\r
49 import org.simantics.db.ReadGraph;\r
50 import org.simantics.db.Resource;\r
51 import org.simantics.db.common.request.PossibleIndexRoot;\r
52 import org.simantics.db.common.utils.Logger;\r
53 import org.simantics.db.common.utils.NameUtils;\r
54 import org.simantics.db.exception.DatabaseException;\r
55 import org.simantics.db.layer0.adapter.Instances;\r
56 import org.simantics.modeling.ModelingResources;\r
57 import org.simantics.scl.reflection.OntologyVersions;\r
58 import org.simantics.ui.internal.Activator;\r
59 import org.simantics.ui.utils.ResourceAdaptionUtils;\r
60 import org.simantics.utils.datastructures.MapList;\r
61 import org.simantics.utils.ui.ExceptionUtils;\r
62 import org.simantics.utils.ui.action.IPriorityAction;\r
63 \r
64 \r
65 /**\r
66  * @author Tuukka Lehtonen\r
67  */\r
68 public final class EditorRegistry implements IExtensionChangeHandler, IEditorRegistry {\r
69 \r
70     /**\r
71      * The maximum amount of entries to cache\r
72      * {@link #getAdaptersFor(ReadGraph, Resource)} results for. Context activation\r
73      * changes invalidate this cache.\r
74      */\r
75     private static final int    MAX_CACHE_SIZE       = 50;\r
76 \r
77 \r
78     private final static String NAMESPACE            = Activator.PLUGIN_ID;\r
79 \r
80     private final static String EP_NAME              = "resourceEditorAdapter";\r
81 \r
82 \r
83     private final static String EL_NAME_ADAPTER      = "adapter";\r
84 \r
85     private final static String EL_NAME_ADAPTERCLASS = "adapterClass";\r
86 \r
87     private final static String EL_NAME_GROUP        = "group";\r
88 \r
89     private final static String EL_NAME_IN_CONTEXT   = "inContext";\r
90 \r
91 \r
92     private static final String ATTR_CLASS           = "class";\r
93 \r
94     private static final String ATTR_IMAGE           = "image";\r
95 \r
96     private static final String ATTR_LABEL           = "label";\r
97 \r
98     private static final String ATTR_TYPE_URIS       = "type_uris";\r
99 \r
100     private static final String ATTR_EDITOR_ID       = "editorId";\r
101 \r
102     private static final String ATTR_PRIORITY        = "priority";\r
103 \r
104     private static final String ATTR_GROUP_ID        = "groupId";\r
105 \r
106     private static final String ATTR_ID              = "id";\r
107 \r
108 \r
109     private static class Group {\r
110         public final String id;\r
111         public final List<EditorAdapterDescriptor> adapters;\r
112 \r
113         Group(String id) {\r
114             this.id = id;\r
115             this.adapters = new ArrayList<EditorAdapterDescriptor>();\r
116         }\r
117         Group(Group g) {\r
118             this.id = g.id;\r
119             this.adapters = new ArrayList<EditorAdapterDescriptor>(g.adapters);\r
120         }\r
121         void add(EditorAdapterDescriptor desc) {\r
122             adapters.add(desc);\r
123         }\r
124         void remove(EditorAdapterDescriptor desc) {\r
125             adapters.remove(desc);\r
126         }\r
127     }\r
128 \r
129     private static final EditorAdapter[]         EMPTY_ADAPTER_ARRAY  = new EditorAdapter[0];\r
130 \r
131     private static EditorRegistry                INSTANCE;\r
132 \r
133     private ExtensionTracker                             tracker;\r
134 \r
135     private EditorAdapterDescriptorImpl[]            extensions           = new EditorAdapterDescriptorImpl[0];\r
136 \r
137     private Map<String, EditorAdapterDescriptorImpl> idToExtension        = new HashMap<String, EditorAdapterDescriptorImpl>();\r
138 \r
139     private Map<String, Group>                           groupMap             = new HashMap<String, Group>();\r
140 \r
141     private WeakHashMap<Object, EditorAdapter[]> adapterCache         = new WeakHashMap<Object, EditorAdapter[]>(\r
142             MAX_CACHE_SIZE);\r
143 \r
144     private CircularBuffer<Object>                       cacheQueue           = new CircularBuffer<Object>(\r
145             MAX_CACHE_SIZE);\r
146 \r
147     /**\r
148      * A set of all the context id's that are currently referenced by all the\r
149      * loaded resourceEditorAdapter extensions.\r
150      */\r
151     private Set<String>                                  referencedContextIds = new HashSet<String>();\r
152 \r
153     /**\r
154      * The current set of active contexts.\r
155      */\r
156     private Set<String>                                  activeContextIds = new HashSet<String>();\r
157 \r
158     /**\r
159      * Used to store all input -> editor mappings. In the Eclipse IDE, this\r
160      * information is stored as persistent properties in each IFile represented\r
161      * by eclipse Resource's. This implementation stores all the mappings in\r
162      * this single map.\r
163      * \r
164      * Maybe in the future it would be possible to store these mapping in the\r
165      * graph in a way that allows us not to publish those changes to the outside\r
166      * world.\r
167      */\r
168     private final EditorMappingImpl                                editorMapping        = new EditorMappingImpl();\r
169 \r
170     public synchronized static IEditorRegistry getInstance() {\r
171         if (INSTANCE == null) {\r
172             INSTANCE = new EditorRegistry();\r
173         }\r
174         return INSTANCE;\r
175     }\r
176 \r
177     public static synchronized void dispose() {\r
178         if (INSTANCE != null) {\r
179             INSTANCE.close();\r
180             INSTANCE = null;\r
181         }\r
182     }\r
183 \r
184     private EditorRegistry() {\r
185         tracker = new ExtensionTracker();\r
186 \r
187         // Cache defined actions\r
188         IExtensionPoint expt = Platform.getExtensionRegistry().getExtensionPoint(NAMESPACE, EP_NAME);\r
189         loadExtensions(expt.getConfigurationElements());\r
190 \r
191         // Start tracking for new and removed extensions\r
192         IFilter filter = ExtensionTracker.createExtensionPointFilter(expt);\r
193         tracker.registerHandler(this, filter);\r
194 \r
195         hookListeners();\r
196     }\r
197 \r
198     private void close() {\r
199         unhookListeners();\r
200 \r
201         tracker.close();\r
202         tracker = null;\r
203 \r
204         editorMapping.clear();\r
205 \r
206         extensions = null;\r
207         idToExtension = null;\r
208         groupMap = null;\r
209         adapterCache = null;\r
210         cacheQueue = null;\r
211     }\r
212 \r
213 //    /**\r
214 //     * Must reset {@link #getAdaptersFor(ReadGraph, Resource)} query caches when\r
215 //     * perspectives are changed because EditorAdapters may return\r
216 //     * different results in different perspectives.\r
217 //     */\r
218 //    private IPerspectiveListener perspectiveListener = new PerspectiveAdapter() {\r
219 //        public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {\r
220 //            clearCache();\r
221 //        }\r
222 //    };\r
223 \r
224     private final IContextManagerListener contextListener = new IContextManagerListener() {\r
225         @Override\r
226         public void contextManagerChanged(ContextManagerEvent event) {\r
227             if (event.isActiveContextsChanged()) {\r
228                 //System.out.println("EVENT: " + event.isActiveContextsChanged() + ", " + event.isContextChanged() + ", " + event.isContextDefined() + ", " + Arrays.toString(event.getPreviouslyActiveContextIds().toArray()));\r
229 \r
230                 Collection<?> active = event.getContextManager().getActiveContextIds();\r
231                 Collection<?> previouslyActive = event.getPreviouslyActiveContextIds();\r
232 \r
233                 Collection<String> added = new HashSet<String>(4);\r
234                 Collection<String> removed = new HashSet<String>(4);\r
235 \r
236                 for (Object o : active) {\r
237                     if (!previouslyActive.contains(o))\r
238                         added.add((String) o);\r
239                 }\r
240                 for (Object o : previouslyActive) {\r
241                     if (!active.contains(o))\r
242                         removed.add((String) o);\r
243                 }\r
244 \r
245                 //System.out.println("ADDED: " + Arrays.toString(added.toArray()) + ", REMOVED: " + Arrays.toString(removed.toArray()));\r
246                 contextChange(added, removed);\r
247             }\r
248         }\r
249     };\r
250 \r
251     private boolean containsAny(Collection<String> c, Collection<String> anyOf) {\r
252         if (anyOf.isEmpty())\r
253             return false;\r
254         for (String s : anyOf)\r
255             if (c.contains(s))\r
256                 return true;\r
257         return false;\r
258     }\r
259 \r
260     private void contextChange(Collection<String> added, Collection<String> removed) {\r
261         // Update active context id set\r
262         if (!added.isEmpty())\r
263             activeContextIds.addAll(added);\r
264         if (!removed.isEmpty())\r
265             activeContextIds.remove(removed);\r
266 \r
267         // Clear caches if necessary\r
268         if (containsAny(referencedContextIds, added) || containsAny(referencedContextIds, removed)) {\r
269             clearCache();\r
270         }\r
271     }\r
272 \r
273     @SuppressWarnings("unchecked")\r
274     private void hookListeners() {\r
275         IWorkbench wb = PlatformUI.getWorkbench();\r
276         IContextService contextService = (IContextService) wb.getService(IContextService.class);\r
277         contextService.addContextManagerListener(contextListener);\r
278         activeContextIds = new HashSet<String>(contextService.getActiveContextIds());\r
279     }\r
280 \r
281     private void unhookListeners() {\r
282         IWorkbench wb = PlatformUI.getWorkbench();\r
283         IContextService contextService = (IContextService) wb.getService(IContextService.class);\r
284         if (contextService != null) {\r
285             contextService.removeContextManagerListener(contextListener);\r
286         }\r
287     }\r
288 \r
289     private String[] parseInContexts(IConfigurationElement parent) {\r
290         List<String> contexts = null;\r
291         for (IConfigurationElement el : parent.getChildren(EL_NAME_IN_CONTEXT)) {\r
292             String id = el.getAttribute(ATTR_ID);\r
293             if (id != null) {\r
294                 if (contexts == null)\r
295                     contexts = new ArrayList<String>(4);\r
296                 contexts.add(id);\r
297             }\r
298         }\r
299         return contexts != null ? contexts.toArray(new String[contexts.size()]) : null;\r
300     }\r
301 \r
302     private synchronized void loadExtensions(IConfigurationElement[] elements) {\r
303         org.eclipse.ui.IEditorRegistry editorRegistry = PlatformUI.getWorkbench().getEditorRegistry();\r
304 \r
305         Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));\r
306         Map<String, Group> newGroups = new HashMap<String, Group>();\r
307         Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);\r
308 \r
309         for (IConfigurationElement el : elements) {\r
310             String name = el.getName();\r
311             try {\r
312                 String id = el.getAttribute(ATTR_ID);\r
313                 String groupId = el.getAttribute(ATTR_GROUP_ID);\r
314                 EditorAdapter adapter = null;\r
315 \r
316                 String priority = el.getAttribute(ATTR_PRIORITY);\r
317                 int pri = IPriorityAction.NORMAL;\r
318                 try {\r
319                     if (priority != null && !priority.trim().isEmpty())\r
320                         pri = Integer.parseInt(priority);\r
321                 } catch (NumberFormatException e) {\r
322                     ExceptionUtils.logError("Non-integer priority value '" + priority + "' for '" + name + "' extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", e);\r
323                 }\r
324 \r
325                 String[] inContexts = null;\r
326 \r
327                 if (EL_NAME_GROUP.equals(name)) {\r
328                     if (id == null || id.isEmpty()) {\r
329                         ExceptionUtils.logWarning("A group extension contributed by " + el.getDeclaringExtension().getContributor().getName() + " does not define a required id.", null);\r
330                     } else {\r
331                         if (!newGroups.containsKey(id)) {\r
332                             newGroups.put(id, new Group(id));\r
333                         }\r
334                     }\r
335                     continue;\r
336                 } else if (EL_NAME_ADAPTER.equals(name)) {\r
337                     String editorId = el.getAttribute(ATTR_EDITOR_ID);\r
338                     IEditorDescriptor editorDesc = editorRegistry.findEditor(editorId);\r
339                     if (editorDesc == null) {\r
340                         ExceptionUtils.logError("Non-existent editorId '" + editorId + "' in extension contributed by '" + el.getDeclaringExtension().getContributor().getName() + "'", null);\r
341                         continue;\r
342                     }\r
343 \r
344                     String type_uris = OntologyVersions.getInstance().currentVersion(el.getAttribute(ATTR_TYPE_URIS));\r
345                     String[] typeUris = type_uris != null ? type_uris.split(",") : new String[0];\r
346 \r
347                     String label = el.getAttribute(ATTR_LABEL);\r
348                     String image = el.getAttribute(ATTR_IMAGE);\r
349                     ImageDescriptor imageDesc = null;\r
350                     if (label == null)\r
351                         label = editorDesc.getLabel();\r
352                     if (image != null) {\r
353                         try {\r
354                             URL resolved = FileLocator.resolve(new URL(image));\r
355                             imageDesc = ImageDescriptor.createFromURL(resolved);\r
356                         } catch (IOException e) {\r
357                             // Try fallback method\r
358                             Bundle bundle = Platform.getBundle(el.getDeclaringExtension().getContributor().getName());\r
359                             imageDesc = ImageDescriptor.createFromURL(bundle.getEntry(image));\r
360                         }\r
361                     } else {\r
362                         imageDesc = editorDesc.getImageDescriptor();\r
363                     }\r
364 \r
365                     SimpleEditorAdapter _adapter = new SimpleEditorAdapter(label, imageDesc, editorId, (String[]) null, typeUris);\r
366                     _adapter.setPriority(pri);\r
367 \r
368                     adapter = _adapter;\r
369 \r
370                     inContexts = parseInContexts(el);\r
371                 } else if (EL_NAME_ADAPTERCLASS.equals(name)) {\r
372                     adapter = (EditorAdapter) el.createExecutableExtension(ATTR_CLASS);\r
373                     if (adapter instanceof Prioritized) {\r
374                         ((Prioritized) adapter).setPriority(pri);\r
375                     }\r
376                     inContexts = parseInContexts(el);\r
377                 }\r
378 \r
379                 if (adapter != null) {\r
380                     EditorAdapterDescriptorImpl ext = new EditorAdapterDescriptorImpl(id, groupId, adapter, inContexts);\r
381                     //System.out.println("Adding editor adapter extension from " +  el.getContributor().getName() + ": " + ext.getId() + ", " + ext.getAdapter());\r
382 \r
383                     // Start tracking the new extension object, its removal will be notified of\r
384                     // with removeExtension(extension, Object[]).\r
385                     tracker.registerObject(el.getDeclaringExtension(), ext, IExtensionTracker.REF_STRONG);\r
386 \r
387                     if (id != null && !id.isEmpty()) {\r
388                         idToExtension.put(id, ext);\r
389                     }\r
390                     if (inContexts != null)\r
391                         for (String ctx : inContexts)\r
392                             newReferencedContextIds.add(ctx);\r
393 \r
394                     newExtensions.add(ext);\r
395                 }\r
396             } catch (CoreException e) {\r
397                 ExceptionUtils.logError("Failed to initialize resourceEditorAdapter extension \"" + name + "\": "\r
398                         + e.getMessage(), e);\r
399             }\r
400         }\r
401 \r
402         for (EditorAdapterDescriptorImpl desc : idToExtension.values()) {\r
403             if (desc.getGroupId() != null) {\r
404                 Group g = newGroups.get(desc.getGroupId());\r
405                 if (g != null) {\r
406                     g.add(desc);\r
407                 }\r
408             }\r
409         }\r
410 \r
411         clearCache();\r
412         this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);\r
413         this.groupMap = newGroups;\r
414         this.referencedContextIds = newReferencedContextIds;\r
415     }\r
416 \r
417 \r
418     @Override\r
419     public void addExtension(IExtensionTracker tracker, IExtension extension) {\r
420         loadExtensions(extension.getConfigurationElements());\r
421     }\r
422 \r
423     @Override\r
424     public synchronized void removeExtension(IExtension extension, Object[] objects) {\r
425         Set<EditorAdapterDescriptorImpl> newExtensions = new HashSet<EditorAdapterDescriptorImpl>(Arrays.asList(extensions));\r
426         Map<String, EditorAdapterDescriptorImpl> idMap = new HashMap<String, EditorAdapterDescriptorImpl>(idToExtension);\r
427         Set<String> removedContextReferences = new HashSet<String>();\r
428 \r
429         Map<String, Group> newGroups = new HashMap<String, Group>();\r
430         for (Group g : groupMap.values()) {\r
431             newGroups.put(g.id, new Group(g));\r
432         }\r
433 \r
434         for (Object o : objects) {\r
435             EditorAdapterDescriptor ext = (EditorAdapterDescriptor) o;\r
436 \r
437             tracker.unregisterObject(extension, o);\r
438             newExtensions.remove(ext);\r
439             idMap.remove(ext.getId());\r
440 \r
441             if (ext.getGroupId() != null) {\r
442                 Group g = newGroups.get(ext.getGroupId());\r
443                 if (g != null) {\r
444                     g.remove(ext);\r
445                 }\r
446             }\r
447             for (String ctx : ext.getInContexts())\r
448                 removedContextReferences.add(ctx);\r
449         }\r
450 \r
451         // Go through the remaining editor adapters and\r
452         // check whether they still reference any of the removed\r
453         // context ids. Ids that are still referenced will not be\r
454         // removed from <code>referencedContextIds</code>\r
455         for (EditorAdapterDescriptorImpl desc : newExtensions) {\r
456             for (String ctx : desc.getInContexts()) {\r
457                 removedContextReferences.remove(ctx);\r
458             }\r
459         }\r
460         Set<String> newReferencedContextIds = new HashSet<String>(referencedContextIds);\r
461         newReferencedContextIds.removeAll(removedContextReferences);\r
462 \r
463         // Atomic assignment\r
464         this.extensions = newExtensions.toArray(new EditorAdapterDescriptorImpl[newExtensions.size()]);\r
465         this.idToExtension = idMap;\r
466         this.groupMap = newGroups;\r
467         this.referencedContextIds = newReferencedContextIds;\r
468     }\r
469 \r
470     @Override\r
471     public EditorAdapterDescriptor getExtensionById(String id) {\r
472         return idToExtension.get(id);\r
473     }\r
474 \r
475     @Override\r
476     public EditorAdapter getAdapterById(String id) {\r
477         EditorAdapterDescriptor ext = idToExtension.get(id);\r
478         return ext == null ? null : ext.getAdapter();\r
479     }\r
480 \r
481     private void clearCache() {\r
482         synchronized (adapterCache) {\r
483             adapterCache.clear();\r
484             cacheQueue.clear();\r
485         }\r
486     }\r
487 \r
488     @Override\r
489     public EditorAdapterDescriptor[] getEditorAdapters() {\r
490         return extensions;\r
491     }\r
492 \r
493     @Override\r
494     public EditorAdapter[] getAdaptersFor(ReadGraph g, final Object r) throws DatabaseException {\r
495         \r
496         EditorAdapter[] result;\r
497         synchronized (adapterCache) {\r
498             result = adapterCache.get(r);\r
499             if (result != null)\r
500                 return result;\r
501         }\r
502 \r
503         MultiStatus status = null;\r
504 \r
505         final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();\r
506         for (EditorAdapterDescriptor a : extensions) {\r
507             try {\r
508                 // Filter out adapters that are not active in the current context configuration.\r
509                 if (!a.isActive(activeContextIds))\r
510                     continue;\r
511                 // Filter out adapters that just can't handle the input.\r
512                 if (!a.getAdapter().canHandle(g, r))\r
513                     continue;\r
514 \r
515                 // NOTE: Group is null if there is no group.\r
516                 l.add(a.getGroupId(), a.getAdapter());\r
517             } catch (RuntimeException e) {\r
518                 if (status == null)\r
519                     status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);\r
520                 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
521             }\r
522         }\r
523         \r
524         \r
525         Resource res = ResourceAdaptionUtils.toSingleResource(r);\r
526         if (res != null) {\r
527                 ModelingResources MOD = ModelingResources.getInstance(g);\r
528             Resource indexRoot = g.syncRequest(new PossibleIndexRoot(res));\r
529             if (indexRoot != null) {\r
530                 Instances query = g.adapt(MOD.EditorContribution, Instances.class);\r
531                 for(Resource contribution : query.find(g, indexRoot)) {\r
532 \r
533                     try {\r
534 \r
535                         String id = g.getRelatedValue(contribution, MOD.EditorContribution_editorId, Bindings.STRING);\r
536                         String label = NameUtils.getSafeLabel(g, contribution);\r
537 \r
538                         Resource imageResource = g.getPossibleObject(contribution, MOD.EditorContribution_HasImage);\r
539                         ImageDescriptor image = imageResource == null ? null : g.adapt(imageResource, ImageDescriptor.class);\r
540 \r
541                         Integer priority = g.getRelatedValue(contribution, MOD.EditorContribution_priority, Bindings.INTEGER);\r
542                         EditorAdapterDescriptor a = new GraphEditorAdapterDescriptor(id, label, image, contribution, priority);\r
543 \r
544                         // Filter out adapters that are not active in the current context configuration.\r
545                         if (!a.isActive(activeContextIds))\r
546                             continue;\r
547                         // Filter out adapters that just can't handle the input.\r
548                         if (!a.getAdapter().canHandle(g, r))\r
549                             continue;\r
550 \r
551                         l.add(a.getGroupId(), a.getAdapter());\r
552 \r
553                     } catch (DatabaseException e) {\r
554                         Logger.defaultLogError(e);\r
555                     }\r
556                 }\r
557             }\r
558         }\r
559         \r
560         result = gatherAdapterResult(l);\r
561         updateCache(r, result);\r
562 \r
563         if (status != null && !status.isOK())\r
564             Activator.getDefault().getLog().log(status);\r
565 \r
566         return result;\r
567     }\r
568 \r
569     private EditorAdapter[] gatherAdapterResult(MapList<String, EditorAdapter> map) {\r
570         final List<EditorAdapter> result = new ArrayList<EditorAdapter>(8);\r
571         for (String group : map.getKeys()) {\r
572             List<EditorAdapter> grp = map.getValues(group);\r
573             if (group == null) {\r
574                 if (grp != null)\r
575                     result.addAll(grp);\r
576             } else {\r
577                 EditorAdapter highestPriorityAdapter = null;\r
578                 for (EditorAdapter adapter : grp) {\r
579                     if (highestPriorityAdapter == null || adapter.getPriority() > highestPriorityAdapter.getPriority()) {\r
580                         highestPriorityAdapter = adapter;\r
581                     }\r
582                 }\r
583                 result.add(highestPriorityAdapter);\r
584             }\r
585         }\r
586         return result.toArray(EMPTY_ADAPTER_ARRAY);\r
587     }\r
588 \r
589     @Override\r
590     public EditorMapping getMappings() {\r
591         return editorMapping;\r
592     }\r
593 \r
594     private void updateCache(Object r, EditorAdapter[] result) {\r
595         synchronized (adapterCache) {\r
596             adapterCache.put(r, result);\r
597             Object removed = cacheQueue.write(r);\r
598             if (removed != null) {\r
599                 adapterCache.remove(removed);\r
600             }\r
601         }\r
602     }\r
603 \r
604 \r
605     private static class CircularBuffer<T> {\r
606         private final WeakReference<?>[] buffer;\r
607 \r
608         private int head;\r
609         private int tail;\r
610         private boolean full;\r
611         private final int size;\r
612 \r
613         CircularBuffer(int size) {\r
614             if (size == 0)\r
615                 throw new IllegalArgumentException("zero size not allowed");\r
616 \r
617             this.buffer = new WeakReference[size];\r
618             this.size = size;\r
619             clear();\r
620         }\r
621 \r
622         public void clear() {\r
623             this.head = this.tail = 0;\r
624             this.full = false;\r
625             Arrays.fill(buffer, null);\r
626         }\r
627 \r
628         /**\r
629          * @param id an ID, other than 0L\r
630          * @return 0L if the buffer was not yet full, otherwise\r
631          * @throws IllegalArgumentException for 0L id\r
632          */\r
633         @SuppressWarnings("unchecked")\r
634         T write(T id) {\r
635             if (id == null)\r
636                 throw new IllegalArgumentException("null resource id");\r
637 \r
638             if (full) {\r
639                 WeakReference<?> prev = buffer[head];\r
640                 buffer[head++] = new WeakReference<T>(id);\r
641                 head %= size;\r
642                 tail = head;\r
643                 return (T) prev.get();\r
644             } else {\r
645                 buffer[head++] = new WeakReference<T>(id);\r
646                 head %= size;\r
647                 if (head == tail) {\r
648                     full = true;\r
649                 }\r
650             }\r
651             // Nothing was yet overwritten\r
652             return null;\r
653         }\r
654 \r
655         @Override\r
656         public String toString() {\r
657             return Arrays.toString(buffer);\r
658         }\r
659     }\r
660 \r
661 \r
662     @Override\r
663     public EditorAdapter[] getDefaultAdaptersFor(ReadGraph g, Object r) throws DatabaseException {\r
664         EditorAdapter[] results;\r
665 \r
666         MultiStatus status = null;\r
667 \r
668         final MapList<String, EditorAdapter> l = new MapList<String, EditorAdapter>();\r
669         for (EditorAdapterDescriptor a : extensions) {\r
670             try {\r
671 \r
672                 // NOTE: Group is null if there is no group.\r
673                 l.add(a.getGroupId(), a.getAdapter());\r
674             } catch (RuntimeException e) {\r
675                 if (status == null)\r
676                     status = new MultiStatus(Activator.PLUGIN_ID, 0, "Unexpected errors occured in EditorAdapters:" , null);\r
677                 status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
678             }\r
679         }\r
680         results = gatherAdapterResult(l);\r
681 \r
682         if (status != null && !status.isOK())\r
683             Activator.getDefault().getLog().log(status);\r
684 \r
685         // If no default editor is found, get all that can handle\r
686         if (results.length > 0)\r
687             return results;\r
688         else\r
689             return getAdaptersFor(g, r);\r
690     }\r
691 \r
692 }\r