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