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