af0fa25a355019468aecd5a89f6a6f950e69833e
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / GraphExplorerImpl2.java
1 /*******************************************************************************
2  * Copyright (c) 2013 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.browsing.ui.swt;
13
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Deque;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.WeakHashMap;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.Semaphore;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicReference;
33 import java.util.function.BiFunction;
34 import java.util.function.Consumer;
35
36 import org.eclipse.core.runtime.Assert;
37 import org.eclipse.core.runtime.IAdaptable;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.MultiStatus;
41 import org.eclipse.core.runtime.Platform;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.jobs.Job;
44 import org.eclipse.jface.layout.GridDataFactory;
45 import org.eclipse.jface.layout.TreeColumnLayout;
46 import org.eclipse.jface.resource.ColorDescriptor;
47 import org.eclipse.jface.resource.DeviceResourceException;
48 import org.eclipse.jface.resource.DeviceResourceManager;
49 import org.eclipse.jface.resource.FontDescriptor;
50 import org.eclipse.jface.resource.ImageDescriptor;
51 import org.eclipse.jface.resource.JFaceResources;
52 import org.eclipse.jface.resource.LocalResourceManager;
53 import org.eclipse.jface.viewers.CellEditor;
54 import org.eclipse.jface.viewers.CellLabelProvider;
55 import org.eclipse.jface.viewers.ColumnViewer;
56 import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
57 import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
58 import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
59 import org.eclipse.jface.viewers.ColumnWeightData;
60 import org.eclipse.jface.viewers.ComboBoxCellEditor;
61 import org.eclipse.jface.viewers.DialogCellEditor;
62 import org.eclipse.jface.viewers.EditingSupport;
63 import org.eclipse.jface.viewers.ICellEditorValidator;
64 import org.eclipse.jface.viewers.IPostSelectionProvider;
65 import org.eclipse.jface.viewers.ISelection;
66 import org.eclipse.jface.viewers.ISelectionChangedListener;
67 import org.eclipse.jface.viewers.ISelectionProvider;
68 import org.eclipse.jface.viewers.ITreeContentProvider;
69 import org.eclipse.jface.viewers.ITreeViewerListener;
70 import org.eclipse.jface.viewers.SelectionChangedEvent;
71 import org.eclipse.jface.viewers.StructuredSelection;
72 import org.eclipse.jface.viewers.TextCellEditor;
73 import org.eclipse.jface.viewers.TreeExpansionEvent;
74 import org.eclipse.jface.viewers.TreeSelection;
75 import org.eclipse.jface.viewers.TreeViewer;
76 import org.eclipse.jface.viewers.TreeViewerColumn;
77 import org.eclipse.jface.viewers.Viewer;
78 import org.eclipse.jface.viewers.ViewerCell;
79 import org.eclipse.swt.SWT;
80 import org.eclipse.swt.events.DisposeEvent;
81 import org.eclipse.swt.events.DisposeListener;
82 import org.eclipse.swt.events.FocusEvent;
83 import org.eclipse.swt.events.FocusListener;
84 import org.eclipse.swt.events.KeyEvent;
85 import org.eclipse.swt.events.KeyListener;
86 import org.eclipse.swt.events.MouseEvent;
87 import org.eclipse.swt.events.MouseListener;
88 import org.eclipse.swt.events.SelectionListener;
89 import org.eclipse.swt.graphics.Color;
90 import org.eclipse.swt.graphics.Font;
91 import org.eclipse.swt.graphics.Image;
92 import org.eclipse.swt.graphics.Point;
93 import org.eclipse.swt.graphics.RGB;
94 import org.eclipse.swt.layout.FillLayout;
95 import org.eclipse.swt.widgets.Composite;
96 import org.eclipse.swt.widgets.Control;
97 import org.eclipse.swt.widgets.Display;
98 import org.eclipse.swt.widgets.Event;
99 import org.eclipse.swt.widgets.Listener;
100 import org.eclipse.swt.widgets.ScrollBar;
101 import org.eclipse.swt.widgets.Tree;
102 import org.eclipse.swt.widgets.TreeColumn;
103 import org.eclipse.swt.widgets.TreeItem;
104 import org.eclipse.ui.PlatformUI;
105 import org.eclipse.ui.contexts.IContextActivation;
106 import org.eclipse.ui.contexts.IContextService;
107 import org.eclipse.ui.services.IServiceLocator;
108 import org.eclipse.ui.swt.IFocusService;
109 import org.simantics.browsing.ui.BuiltinKeys;
110 import org.simantics.browsing.ui.Column;
111 import org.simantics.browsing.ui.Column.Align;
112 import org.simantics.browsing.ui.DataSource;
113 import org.simantics.browsing.ui.ExplorerState;
114 import org.simantics.browsing.ui.GraphExplorer;
115 import org.simantics.browsing.ui.NodeContext;
116 import org.simantics.browsing.ui.NodeContext.CacheKey;
117 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
118 import org.simantics.browsing.ui.NodeContext.QueryKey;
119 import org.simantics.browsing.ui.NodeQueryManager;
120 import org.simantics.browsing.ui.NodeQueryProcessor;
121 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
122 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
123 import org.simantics.browsing.ui.SelectionDataResolver;
124 import org.simantics.browsing.ui.SelectionFilter;
125 import org.simantics.browsing.ui.StatePersistor;
126 import org.simantics.browsing.ui.common.AdaptableHintContext;
127 import org.simantics.browsing.ui.common.ColumnKeys;
128 import org.simantics.browsing.ui.common.ErrorLogger;
129 import org.simantics.browsing.ui.common.NodeContextBuilder;
130 import org.simantics.browsing.ui.common.NodeContextUtil;
131 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
132 import org.simantics.browsing.ui.common.internal.IGECache;
133 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
134 import org.simantics.browsing.ui.common.internal.UIElementReference;
135 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
136 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
137 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
138 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
139 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
140 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
141 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
142 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
143 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
144 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
145 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
146 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
147 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
148 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
149 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
150 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
151 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
152 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
153 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
154 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
155 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
156 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
157 import org.simantics.browsing.ui.content.ImageDecorator;
158 import org.simantics.browsing.ui.content.Imager;
159 import org.simantics.browsing.ui.content.LabelDecorator;
160 import org.simantics.browsing.ui.content.Labeler;
161 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
162 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
163 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
164 import org.simantics.browsing.ui.content.Labeler.Modifier;
165 import org.simantics.browsing.ui.swt.internal.Threads;
166 import org.simantics.db.layer0.SelectionHints;
167 import org.simantics.ui.SimanticsUI;
168 import org.simantics.utils.datastructures.BijectionMap;
169 import org.simantics.utils.datastructures.MapList;
170 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
171 import org.simantics.utils.datastructures.hints.IHintContext;
172 import org.simantics.utils.threads.IThreadWorkQueue;
173 import org.simantics.utils.threads.SWTThread;
174 import org.simantics.utils.threads.ThreadUtils;
175 import org.simantics.utils.ui.AdaptionUtils;
176 import org.simantics.utils.ui.ISelectionUtils;
177 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
178
179 import gnu.trove.map.hash.THashMap;
180 import gnu.trove.map.hash.TObjectIntHashMap;
181
182 /**
183  * TreeView based GraphExplorer
184  * 
185  * 
186  * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
187  */
188 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
189         
190         private static final boolean DEBUG_SELECTION_LISTENERS = false;
191         private static final boolean DEBUG = false;
192         
193         private TreeViewer viewer;
194         
195         private LocalResourceManager localResourceManager;
196         private DeviceResourceManager resourceManager;
197         
198         
199         private IThreadWorkQueue thread;
200         
201         @SuppressWarnings({ "rawtypes" })
202         final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
203         @SuppressWarnings({ "rawtypes" })
204         final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
205         @SuppressWarnings({ "rawtypes" })
206         final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
207         
208         private FontDescriptor originalFont;
209     protected ColorDescriptor originalForeground;
210     protected ColorDescriptor originalBackground;
211     private Color invalidModificationColor;
212
213         private Column[] columns;
214         private Map<String,Integer> columnKeyToIndex;
215         private boolean columnsAreVisible = true;
216
217         
218         private NodeContext rootContext;
219         private TreeNode rootNode;
220         private StatePersistor persistor = null;
221
222         private boolean editable = true;
223         
224         private boolean disposed = false;
225         
226         private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
227     private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
228     private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
229         
230     private int autoExpandLevel = 0;
231     private IServiceLocator serviceLocator;
232     private IContextService contextService = null;
233     private IFocusService focusService = null;
234     private IContextActivation editingContext = null;
235         
236     GeViewerContext explorerContext = new GeViewerContext(this);
237     
238     private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
239     private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();
240     private SelectionDataResolver selectionDataResolver;
241     private SelectionFilter selectionFilter;
242     
243     private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
244     private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
245     
246     private ModificationContext                          modificationContext = null;
247     
248     private boolean filterSelectionEdit = true;
249     
250     private TreeColumnLayout treeColumnLayout;
251     
252     private boolean expand;
253     private boolean verticalBarVisible = false;
254     
255     private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
256
257         @Override
258         public Object[] apply(GraphExplorer explorer, Object[] objects) {
259             Object[] result = new Object[objects.length];
260             for (int i = 0; i < objects.length; i++) {
261                 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
262                 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
263                 result[i] = context;
264             }
265             return result;
266         }
267
268     };
269     
270     static class TransientStateImpl implements TransientExplorerState {
271
272         private Integer activeColumn = null;
273         
274                 @Override
275                 public synchronized Integer getActiveColumn() {
276                         return activeColumn;
277                 }
278                 
279                 public synchronized void setActiveColumn(Integer column) {
280                         activeColumn = column;
281                 }
282         
283     }
284     
285     private TransientStateImpl transientState = new TransientStateImpl();
286     
287         
288         public GraphExplorerImpl2(Composite parent) {
289                 this(parent, SWT.BORDER | SWT.MULTI );
290         }
291
292         public GraphExplorerImpl2(Composite parent, int style) {
293                 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
294                 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
295
296                 this.imageLoaderJob = new ImageLoaderJob(this);
297                 this.imageLoaderJob.setPriority(Job.DECORATE);
298
299                 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
300
301                 this.thread = SWTThread.getThreadAccess(parent);
302
303                 for (int i = 0; i < 10; i++)
304                         explorerContext.activity.push(0);
305
306                 boolean useLayout = true;
307                 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. 
308                 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
309
310                         Composite rootTreeComposite = new Composite(parent, SWT.NONE);
311                         treeColumnLayout = new TreeColumnLayout();
312                         rootTreeComposite.setLayout(treeColumnLayout);
313
314                         viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
315                         
316                         GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
317                         
318                 } else {
319                         viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
320                 }
321                 
322                 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
323                         
324                         @Override
325                         public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
326                                 
327                         }
328                         
329                         @Override
330                         public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
331                                 // cancel editor activation for double click events.
332                                 // TODO: this may not work similarly to GraphExplorerImpl
333                                 if ((event.time - focusGainedAt) < 250L) {
334                                         event.cancel = true;
335                                 }
336                         }
337                         
338                         @Override
339                         public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
340                                 
341                         }
342                         
343                         @Override
344                         public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
345                                 
346                         }
347                 });
348                 
349                 viewer.setUseHashlookup(true);
350                 viewer.setContentProvider(new GeViewerContentProvider());
351                 
352                 
353
354                 originalFont = JFaceResources.getDefaultFontDescriptor();
355
356                 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
357                 
358                 setBasicListeners();
359                 setDefaultProcessors();
360                 
361                 viewer.getTree().addDisposeListener(new DisposeListener() {
362                         
363                         @Override
364                         public void widgetDisposed(DisposeEvent e) {
365                                 doDispose();
366                                 
367                         }
368                 });
369                 
370                 
371                 // Add listener to tree for delayed tree population. 
372                 
373                 Listener listener = new Listener() {
374                         
375                         @Override
376                         public void handleEvent(Event event) {
377                                 
378                                 switch (event.type) {
379                                         case SWT.Activate:
380                                         case SWT.Show:
381                                         case SWT.Paint:
382                                         {
383                                                 visible = true;
384                                                 activate();
385                                                 break;
386                                         }
387                                         case SWT.Deactivate:
388                                         case SWT.Hide:
389                                                 visible = false;
390                                 }
391                         }
392                 };
393                 
394                 viewer.getTree().addListener(SWT.Activate, listener);
395                 viewer.getTree().addListener(SWT.Deactivate, listener);
396                 viewer.getTree().addListener(SWT.Show, listener);
397                 viewer.getTree().addListener(SWT.Hide, listener);
398                 viewer.getTree().addListener(SWT.Paint,listener);
399                 
400                 
401                 viewer.addTreeListener(new ITreeViewerListener() {
402                         
403                         @Override
404                         public void treeExpanded(TreeExpansionEvent event) {
405                                 
406                         }
407                         
408                         @Override
409                         public void treeCollapsed(TreeExpansionEvent event) {
410                                 collapsedNodes.add((TreeNode)event.getElement());
411                         }
412                 });
413                 
414                 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
415         }
416         
417         private long focusGainedAt = 0L;
418         private boolean visible = false;
419         
420         private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
421         
422         protected void setBasicListeners() {
423                 Tree tree = viewer.getTree();
424                 
425                  tree.addFocusListener(new FocusListener() {
426                     @Override
427                     public void focusGained(FocusEvent e) {
428                         focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
429                         for (FocusListener listener : focusListeners)
430                             listener.focusGained(e);
431                     }
432                     @Override
433                     public void focusLost(FocusEvent e) {
434                         for (FocusListener listener : focusListeners)
435                             listener.focusLost(e);
436                     }
437                 });
438                 tree.addMouseListener(new MouseListener() {
439                     @Override
440                     public void mouseDoubleClick(MouseEvent e) {
441                         for (MouseListener listener : mouseListeners) {
442                             listener.mouseDoubleClick(e);
443                         }
444                     }
445                     @Override
446                     public void mouseDown(MouseEvent e) {
447                         for (MouseListener listener : mouseListeners) {
448                             listener.mouseDown(e);
449                         }
450                     }
451                     @Override
452                     public void mouseUp(MouseEvent e) {
453                         for (MouseListener listener : mouseListeners) {
454                             listener.mouseUp(e);
455                         }
456                     }
457                 });
458                 tree.addKeyListener(new KeyListener() {
459                     @Override
460                     public void keyPressed(KeyEvent e) {
461                         for (KeyListener listener : keyListeners) {
462                             listener.keyPressed(e);
463                         }
464                     }
465                     @Override
466                     public void keyReleased(KeyEvent e) {
467                         for (KeyListener listener : keyListeners) {
468                             listener.keyReleased(e);
469                         }
470                     }
471                 });
472                 
473                 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
474                                 
475                                 @Override
476                                 public void selectionChanged(SelectionChangedEvent event) {
477                                         //System.out.println("GraphExplorerImpl2.fireSelection");
478                                         selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
479                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
480                                         selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
481                                 }
482                         });
483                 
484                 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
485                                 
486                                 @Override
487                                 public void selectionChanged(SelectionChangedEvent event) {
488                                         //System.out.println("GraphExplorerImpl2.firePostSelection");
489                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
490                                         selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
491                                         
492                                 }
493                         });
494
495         }
496         
497         private NodeContext pendingRoot;
498         
499         private void activate() {
500                 if (pendingRoot != null && !expand) {
501                         doSetRoot(pendingRoot);
502                         pendingRoot = null;
503                 }
504         }
505         
506     /**
507      * Invoke only from SWT thread to reset the root of the graph explorer tree.
508      * 
509      * @param root
510      */
511     private void doSetRoot(NodeContext root) {
512         Display display = viewer.getTree().getDisplay();
513                 if (display.getThread() != Thread.currentThread()) {
514                         throw new RuntimeException("Invoke from SWT thread only");
515                 }
516 //      System.out.println("doSetRoot " + root);
517         if (isDisposed())
518             return;
519         if (viewer.getTree().isDisposed())
520                 return;
521         if (root.getConstant(BuiltinKeys.INPUT) == null) {
522             ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
523             return;
524         }
525         
526         
527
528         // Empty caches, release queries.
529         if (rootNode != null) {
530                 rootNode.dispose();
531         }       
532         GeViewerContext oldContext = explorerContext;
533         GeViewerContext newContext = new GeViewerContext(this);
534         this.explorerContext = newContext;
535         oldContext.safeDispose();
536
537         // Need to empty these or otherwise they won't be emptied until the
538         // explorer is disposed which would mean that many unwanted references
539         // will be held by this map.
540         clearPrimitiveProcessors();
541
542         this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
543                 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
544
545         explorerContext.getCache().incRef(this.rootContext);
546
547         initializeState();
548         
549         
550         select(rootContext);
551         //refreshColumnSizes();
552         rootNode = new TreeNode(rootContext);
553         if (DEBUG) System.out.println("setRoot " + rootNode);
554       
555         viewer.setInput(rootNode);
556         
557         // Delay content reading.
558         
559         // This is required for cases when GEImpl2 is attached to selection view. Reading content
560         // instantly could stagnate SWT thread under rapid changes in selection. By delaying the 
561         // content reading we give the system a change to dispose the GEImpl2 before the content is read.
562         display.asyncExec(new Runnable() {
563                         
564                         @Override
565                         public void run() {
566                                 if (rootNode != null) {
567                                     rootNode.updateChildren();
568                                 }
569                         }
570                 });
571        
572     }
573     
574     private void initializeState() {
575         if (persistor == null)
576             return;
577
578         ExplorerState state = persistor.deserialize(
579                 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
580                 getRoot());
581
582
583         Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
584         if (processor instanceof DefaultIsExpandedProcessor) {
585             DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
586             for(NodeContext expanded : state.expandedNodes) {
587                 isExpandedProcessor.setExpanded(expanded, true);
588             }
589         }
590     }
591
592     @Override
593     public NodeContext getRoot() {
594         return rootContext;
595     }
596     
597     @Override
598     public IThreadWorkQueue getThread() {
599         return thread;
600     }
601
602     @Override
603     public NodeContext getParentContext(NodeContext context) {
604         if (disposed)
605             throw new IllegalStateException("disposed");
606         if (!thread.currentThreadAccess())
607             throw new IllegalStateException("not in SWT display thread " + thread.getThread());
608
609         List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
610         for (int i = 0; i < nodes.size(); i++) {
611                 if (nodes.get(i).getParent() != null)
612                         return nodes.get(i).getParent().getContext();
613         }
614         return null;
615         
616     }
617     
618     
619     @SuppressWarnings("unchecked")
620     @Override
621     public <T> T getAdapter(Class<T> adapter) {
622         if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
623         else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
624         return null;
625     }
626
627         
628         protected void setDefaultProcessors() {
629                 // Add a simple IMAGER query processor that always returns null.
630                 // With this processor no images will ever be shown.
631                 // setPrimitiveProcessor(new StaticImagerProcessor(null));
632
633                 setProcessor(new DefaultComparableChildrenProcessor());
634                 setProcessor(new DefaultLabelDecoratorsProcessor());
635                 setProcessor(new DefaultImageDecoratorsProcessor());
636                 setProcessor(new DefaultSelectedLabelerProcessor());
637                 setProcessor(new DefaultLabelerFactoriesProcessor());
638                 setProcessor(new DefaultSelectedImagerProcessor());
639                 setProcessor(new DefaultImagerFactoriesProcessor());
640                 setPrimitiveProcessor(new DefaultLabelerProcessor());
641                 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
642                 setPrimitiveProcessor(new DefaultImagerProcessor());
643                 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
644                 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
645                 setPrimitiveProcessor(new NoSelectionRequestProcessor());
646
647                 setProcessor(new DefaultFinalChildrenProcessor(this));
648
649                 setProcessor(new DefaultPrunedChildrenProcessor());
650                 setProcessor(new DefaultSelectedViewpointProcessor());
651                 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
652                 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
653                 setProcessor(new DefaultViewpointContributionsProcessor());
654
655                 setPrimitiveProcessor(new DefaultViewpointProcessor());
656                 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
657                 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
658                 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
659                 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
660         }
661         
662         @Override
663     public Column[] getColumns() {
664         return Arrays.copyOf(columns, columns.length);
665     }
666         
667     @Override
668     public void setColumnsVisible(boolean visible) {
669         columnsAreVisible = visible;
670         if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
671     }
672
673     @Override
674     public void setColumns(final Column[] columns) {
675         setColumns(columns, null);
676     }
677
678     @Override
679     public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
680         assertNotDisposed();
681         checkUniqueColumnKeys(columns);
682
683         Display d = viewer.getTree().getDisplay();
684         if (d.getThread() == Thread.currentThread()) {
685             doSetColumns(columns, callback);
686             viewer.refresh(true);
687          }else
688             d.asyncExec(new Runnable() {
689                 @Override
690                 public void run() {
691                         if (viewer == null)
692                                 return;
693                     if (viewer.getTree().isDisposed())
694                         return;
695                     doSetColumns(columns, callback);
696                     viewer.refresh(true);
697                     viewer.getTree().getParent().layout();
698                 }
699             });
700     }
701     
702     private void checkUniqueColumnKeys(Column[] cols) {
703         Set<String> usedColumnKeys = new HashSet<String>();
704         List<Column> duplicateColumns = new ArrayList<Column>();
705         for (Column c : cols) {
706             if (!usedColumnKeys.add(c.getKey()))
707                 duplicateColumns.add(c);
708         }
709         if (!duplicateColumns.isEmpty()) {
710             throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
711         }
712     }
713     
714     private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
715     private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
716     private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
717     
718     private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
719         // Attempt to keep previous column widths.
720         Map<String, Integer> prevWidths = new HashMap<String, Integer>();
721         
722         for (TreeViewerColumn c : treeViewerColumns) {
723                 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
724                 c.getColumn().dispose();
725         }
726        
727         treeViewerColumns.clear();
728         
729         HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
730         for (int i = 0; i < cols.length; ++i) {
731             keyToIndex.put(cols[i].getKey(), i);
732         }
733
734         this.columns = Arrays.copyOf(cols, cols.length);
735         //this.columns[cols.length] = FILLER_COLUMN;
736         this.columnKeyToIndex = keyToIndex;
737
738         Map<Column, Object> map = new HashMap<Column, Object>();
739
740         // FIXME : temporary workaround for ModelBrowser.
741         viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
742         
743         int columnIndex = 0;
744
745         for (Column column : columns) {
746                 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
747                 treeViewerColumns.add(tvc);
748                 tvc.setLabelProvider(cellLabelProvider);
749                 EditingSupport support = null;
750                 if (editingSupports.size() > columnIndex)
751                         support = editingSupports.get(columnIndex);
752                 else {
753                         support = new GeEditingSupport(viewer, columnIndex);
754                         editingSupports.add(support);
755                 }
756                 
757                 tvc.setEditingSupport(support);
758                 
759             TreeColumn c = tvc.getColumn();
760             map.put(column, c);
761             c.setData(column);
762             c.setText(column.getLabel());
763             c.setToolTipText(column.getTooltip());
764
765             int cw = column.getWidth();
766
767             // Try to keep previous widths
768             Integer w = prevWidths.get(column);
769             if (w != null)
770                 c.setWidth(w);
771             else if (cw != Column.DEFAULT_CONTROL_WIDTH)
772                 c.setWidth(cw);
773             else if (columns.length == 1) {
774                 // FIXME : how to handle single column properly?
775                 c.setWidth(1000);
776             }
777             else {
778                 // Go for some kind of default settings then...
779                 if (ColumnKeys.PROPERTY.equals(column.getKey()))
780                     c.setWidth(150);
781                 else
782                     c.setWidth(50);
783             }
784             if (treeColumnLayout != null) {
785                 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
786             }
787
788 //            if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
789 //                c.addListener(SWT.Resize, resizeListener);
790 //                c.setResizable(true);
791 //            } else {
792 //                //c.setResizable(false);
793 //            }
794             columnIndex++;
795         }
796        
797        
798
799         if(callback != null) callback.accept(map);
800     }
801
802     int toSWT(Align alignment) {
803         switch (alignment) {
804             case LEFT: return SWT.LEFT;
805             case CENTER: return SWT.CENTER;
806             case RIGHT: return SWT.RIGHT;
807             default: throw new Error("unhandled alignment: " + alignment);
808         }
809     }
810
811         @Override
812         public <T> void setProcessor(NodeQueryProcessor<T> processor) {
813                 assertNotDisposed();
814                 if (processor == null)
815                         throw new IllegalArgumentException("null processor");
816
817                 processors.put(processor.getIdentifier(), processor);
818         }
819
820         @Override
821         public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
822                 assertNotDisposed();
823                 if (processor == null)
824                         throw new IllegalArgumentException("null processor");
825
826                 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
827                                 processor.getIdentifier(), processor);
828
829                 if (oldProcessor instanceof ProcessorLifecycle)
830                         ((ProcessorLifecycle) oldProcessor).detached(this);
831                 if (processor instanceof ProcessorLifecycle)
832                         ((ProcessorLifecycle) processor).attached(this);
833         }
834
835         @Override
836         public <T> void setDataSource(DataSource<T> provider) {
837                 assertNotDisposed();
838                 if (provider == null)
839                         throw new IllegalArgumentException("null provider");
840                 dataSources.put(provider.getProvidedClass(), provider);
841         }
842
843         @SuppressWarnings("unchecked")
844         @Override
845         public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
846                 assertNotDisposed();
847                 if (forProvidedClass == null)
848                         throw new IllegalArgumentException("null class");
849                 return dataSources.remove(forProvidedClass);
850         }
851
852         @Override
853         public void setPersistor(StatePersistor persistor) {
854                 this.persistor = persistor;
855         }
856
857         @Override
858         public SelectionDataResolver getSelectionDataResolver() {
859                 return selectionDataResolver;
860         }
861
862         @Override
863         public void setSelectionDataResolver(SelectionDataResolver r) {
864                 this.selectionDataResolver = r;
865         }
866
867         @Override
868         public SelectionFilter getSelectionFilter() {
869                 return selectionFilter;
870         }
871
872         @Override
873         public void setSelectionFilter(SelectionFilter f) {
874                 this.selectionFilter = f;
875                 // TODO: re-filter current selection?
876         }
877         
878     protected ISelection constructSelection(NodeContext... contexts) {
879         if (contexts ==  null)
880             throw new IllegalArgumentException("null contexts");
881         if (contexts.length == 0)
882             return StructuredSelection.EMPTY;
883         if (selectionFilter == null)
884             return new StructuredSelection(transformSelection(contexts));
885         return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
886     }
887     
888     protected Object[] transformSelection(Object[] objects) {
889         return selectionTransformation.apply(this, objects);
890     }
891     
892     protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
893         int len = contexts.length;
894         Object[] objects = new Object[len];
895         for (int i = 0; i < len; ++i)
896             objects[i] = filter.filter(contexts[i]);
897         return objects;
898     }
899
900         @Override
901         public void setSelectionTransformation(
902                         BiFunction<GraphExplorer, Object[], Object[]> f) {
903                 this.selectionTransformation = f;
904         }
905         
906         public ISelection getWidgetSelection() {
907                 return viewer.getSelection();
908         }
909
910         @Override
911         public <T> void addListener(T listener) {
912                 if (listener instanceof FocusListener) {
913                         focusListeners.add((FocusListener) listener);
914                 } else if (listener instanceof MouseListener) {
915                         mouseListeners.add((MouseListener) listener);
916                 } else if (listener instanceof KeyListener) {
917                         keyListeners.add((KeyListener) listener);
918                 }
919         }
920
921         @Override
922         public <T> void removeListener(T listener) {
923                 if (listener instanceof FocusListener) {
924                         focusListeners.remove(listener);
925                 } else if (listener instanceof MouseListener) {
926                         mouseListeners.remove(listener);
927                 } else if (listener instanceof KeyListener) {
928                         keyListeners.remove(listener);
929                 }
930         }
931
932         public void addSelectionListener(SelectionListener listener) {
933                 viewer.getTree().addSelectionListener(listener);
934         }
935
936         public void removeSelectionListener(SelectionListener listener) {
937                 viewer.getTree().removeSelectionListener(listener);
938         }
939
940     private Set<String> uiContexts;
941     
942     @Override
943     public void setUIContexts(Set<String> contexts) {
944         this.uiContexts = contexts;
945     }
946         
947         @Override
948         public void setRoot(final Object root) {
949         if(uiContexts != null && uiContexts.size() == 1)
950                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
951         else
952                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
953         }
954
955         @Override
956         public void setRootContext(final NodeContext context) {
957                 setRootContext0(context);
958         }
959         
960         private void setRoot(NodeContext context) {
961                 if (!visible) {
962                         pendingRoot = context;
963                         Display.getDefault().asyncExec(new Runnable() {
964                                 @Override
965                                 public void run() {
966                                         if (viewer != null && !viewer.getTree().isDisposed())
967                                                 viewer.getTree().redraw();
968                                 }
969                         });
970                         return;
971         }
972                 doSetRoot(context);
973         }
974
975         private void setRootContext0(final NodeContext context) {
976                 Assert.isNotNull(context, "root must not be null");
977                 if (isDisposed() || viewer.getTree().isDisposed())
978                         return;
979                 Display display = viewer.getTree().getDisplay();
980                 if (display.getThread() == Thread.currentThread()) {
981                         setRoot(context);
982                 } else {
983                         display.asyncExec(new Runnable() {
984                                 @Override
985                                 public void run() {
986                                         setRoot(context);
987                                 }
988                         });
989                 }
990         }
991         
992         @Override
993         public void setFocus() {
994                 viewer.getTree().setFocus();
995         }
996         
997         @SuppressWarnings("unchecked")
998         @Override
999         public <T> T getControl() {
1000                 return (T)viewer.getTree();
1001         }
1002         
1003             
1004     @Override
1005     public boolean isDisposed() {
1006         return disposed;
1007     }
1008
1009     protected void assertNotDisposed() {
1010         if (isDisposed())
1011             throw new IllegalStateException("disposed");
1012     }
1013     
1014         @Override
1015         public boolean isEditable() {
1016                 return editable;
1017         }
1018
1019         @Override
1020         public void setEditable(boolean editable) {
1021                 if (!thread.currentThreadAccess())
1022                         throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1023
1024                 this.editable = editable;
1025                 Display display = viewer.getTree().getDisplay();
1026                 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
1027         }
1028         
1029     private void doDispose() {
1030         if (disposed)
1031                 return;
1032         disposed = true;
1033        // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
1034        //       we have to remove all references here to reduce memory consumption.
1035        //       
1036        //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
1037         explorerContext.dispose();
1038         explorerContext = null;
1039         processors.clear();
1040         detachPrimitiveProcessors();
1041         primitiveProcessors.clear();
1042         dataSources.clear();      
1043         pendingItems.clear();
1044         rootContext = null;
1045         mouseListeners.clear();
1046         selectionProvider.clearListeners();
1047         selectionProvider = null;
1048         selectionDataResolver = null;
1049         selectedNodes.clear();
1050         selectedNodes = null;
1051         selectionTransformation = null;
1052         originalFont = null;
1053         localResourceManager.dispose();
1054         localResourceManager = null;
1055         // Must shutdown image loader job before disposing its ResourceManager
1056         imageLoaderJob.dispose();
1057         imageLoaderJob.cancel();
1058         try {
1059             imageLoaderJob.join();
1060             imageLoaderJob = null;
1061         } catch (InterruptedException e) {
1062             ErrorLogger.defaultLogError(e);
1063         }
1064         resourceManager.dispose();
1065         resourceManager = null;
1066         collapsedNodes.clear();
1067         collapsedNodes = null;
1068         if (rootNode != null) {
1069                 rootNode.dispose();
1070                 rootNode = null;        
1071         }                       
1072         contextToNodeMap.clear(); // should be empty at this point.
1073         contextToNodeMap = null;
1074         if (postSelectionProvider != null) {
1075                 postSelectionProvider.dispose();
1076                 postSelectionProvider = null;
1077         }
1078         imageTasks = null;
1079         modificationContext = null;
1080         focusService = null;
1081         contextService = null;
1082         serviceLocator = null;
1083         columns = null;
1084         columnKeyToIndex.clear();
1085         columnKeyToIndex = null;
1086         viewer = null;
1087
1088     }
1089     
1090     @Override
1091     public boolean select(NodeContext context) {
1092
1093         assertNotDisposed();
1094
1095         if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1096             viewer.setSelection(new StructuredSelection());
1097             selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
1098             return true;
1099         }
1100
1101         viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1102         
1103         return false;
1104         
1105     }
1106     
1107     @Override
1108     public boolean selectPath(Collection<NodeContext> contexts) {
1109         
1110         if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1111         if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1112         
1113         return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1114         
1115     }
1116     
1117     private boolean selectPathInternal(NodeContext[] contexts, int position) {
1118
1119         NodeContext head = contexts[position];
1120
1121         if(position == contexts.length-1) {
1122                 return select(head);
1123                 
1124         }
1125
1126         setExpanded(head, true);
1127         if(!waitVisible(contexts[position+1])) return false;
1128         
1129         return selectPathInternal(contexts, position+1);
1130         
1131     }
1132     
1133     private boolean waitVisible(NodeContext context) {
1134         long start = System.nanoTime();
1135         while(!isVisible(context)) {
1136                 Display.getCurrent().readAndDispatch();
1137                 long duration = System.nanoTime() - start;
1138                 if(duration > 10e9) return false;
1139         }
1140         return true;
1141     }
1142     
1143     @Override
1144     public boolean isVisible(NodeContext context) {
1145         if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1146                 return false;
1147         
1148         Object elements[] = viewer.getVisibleExpandedElements();
1149         return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1150         
1151         
1152     }
1153     
1154     @Override
1155     public TransientExplorerState getTransientState() {
1156         if (!thread.currentThreadAccess())
1157             throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1158         return transientState;
1159     }
1160     
1161     @Override
1162     public <T> T query(NodeContext context, CacheKey<T> key) {
1163         return this.explorerContext.cache.get(context, key);
1164     }
1165     
1166     /**
1167      * For setting a more local service locator for the explorer than the global
1168      * workbench service locator. Sometimes required to give this implementation
1169      * access to local workbench services like IFocusService.
1170      * 
1171      * <p>
1172      * Must be invoked during right after construction.
1173      * 
1174      * @param serviceLocator
1175      *            a specific service locator or <code>null</code> to use the
1176      *            workbench global service locator
1177      */
1178     public void setServiceLocator(IServiceLocator serviceLocator) {
1179         if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1180             serviceLocator = PlatformUI.getWorkbench();
1181         this.serviceLocator = serviceLocator;
1182         if (serviceLocator != null) {
1183             this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1184             this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1185         }
1186     }
1187     
1188     private void detachPrimitiveProcessors() {
1189         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1190             if (p instanceof ProcessorLifecycle) {
1191                 ((ProcessorLifecycle) p).detached(this);
1192             }
1193         }
1194     }
1195
1196     private void clearPrimitiveProcessors() {
1197         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1198             if (p instanceof ProcessorLifecycle) {
1199                 ((ProcessorLifecycle) p).clear();
1200             }
1201         }
1202     }
1203     
1204     @Override
1205     public void setExpanded(NodeContext context, boolean expanded) {
1206         viewer.setExpandedState(context, expanded);
1207         
1208     }
1209     
1210     @Override
1211     public void setAutoExpandLevel(int level) {
1212         this.autoExpandLevel = level;
1213         viewer.setAutoExpandLevel(level);
1214     }
1215     
1216     int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
1217     
1218     @Override
1219     public int getMaxChildren() {
1220         return maxChildren;
1221     }
1222     
1223     @Override
1224     public void setMaxChildren(int maxChildren) {
1225         this.maxChildren = maxChildren;
1226         
1227     }
1228     
1229     @Override
1230     public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1231         Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1232         //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1233         if (result != null) {
1234             if (result < 0)
1235                 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1236             return result;
1237         }
1238         return maxChildren;
1239     }
1240     
1241     @Override
1242     public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1243         return explorerContext.getProcessor(key);
1244     }
1245
1246     @Override
1247     public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1248         return explorerContext.getPrimitiveProcessor(key);
1249     }
1250     
1251     private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();
1252     private boolean updating = false;
1253     private int updateCounter = 0;
1254     final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();
1255     
1256     private class UpdateItem {
1257         TreeNode element;
1258         int columnIndex;
1259         
1260         public UpdateItem(TreeNode element) {
1261                 this(element,-1);
1262         }
1263         
1264         public UpdateItem(TreeNode element, int columnIndex) {
1265                 this.element = element;
1266                 this.columnIndex = columnIndex;
1267                 if (element != null && element.isDisposed()) {
1268                         throw new IllegalArgumentException("Node is disposed. " + element);
1269                 }
1270         }
1271         
1272         public void update(TreeViewer viewer) {
1273                 if (element != null) {
1274
1275                                 if (element.isDisposed()) {
1276                                 return;
1277                                 }
1278                         if (((TreeNode)element).updateChildren()) {
1279                                 viewer.refresh(element,true);
1280                         } else {
1281                                 if (columnIndex >= 0) {
1282                                         viewer.update(element, new String[]{columns[columnIndex].getKey()});
1283                                 } else {
1284                                         viewer.refresh(element,true);
1285                                 }
1286                         }
1287                         
1288                         if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
1289                                 expand = true;
1290                                 viewer.setExpandedState(element, true);
1291                                 expand = false;
1292                         }
1293                         } else {
1294                                 if (rootNode.updateChildren())
1295                                         viewer.refresh(rootNode,true);
1296                         }
1297         }
1298         
1299         @Override
1300         public boolean equals(Object obj) {
1301                 if (obj == null)
1302                         return false;
1303                 if (obj.getClass() != getClass())
1304                         return false;
1305                 UpdateItem other = (UpdateItem)obj;
1306                 if (columnIndex != other.columnIndex)
1307                         return false;
1308                 if (element != null)
1309                         return element.equals(other.element);
1310                 return other.element == null;
1311         }
1312         
1313         @Override
1314         public int hashCode() {
1315                 if (element != null)
1316                         return element.hashCode() + columnIndex;
1317                 return 0;
1318         }
1319     }
1320     
1321     private void update(final TreeNode element, final int columnIndex) {
1322         if (DEBUG)System.out.println("update " + element + " " + columnIndex);
1323         if (viewer.getTree().isDisposed())
1324                 return;
1325         synchronized (pendingItems) {
1326                         pendingItems.add(new UpdateItem(element, columnIndex));
1327                         if (updating) return;
1328                         updateCounter++;
1329                         scheduleUpdater();
1330                 }
1331     }
1332
1333     private void update(final TreeNode element) {
1334         if (DEBUG)System.out.println("update " + element);
1335         if (viewer.getTree().isDisposed())
1336                 return;
1337         if (element != null && element.isDisposed())
1338                 return;
1339         synchronized (pendingItems) {
1340                 
1341                         pendingItems.add(new UpdateItem(element));
1342                         if (updating) return;
1343                         updateCounter++;
1344                         scheduleUpdater();
1345                 }
1346     }
1347     
1348     boolean scheduleUpdater() {
1349
1350         if (viewer.getTree().isDisposed())
1351             return false;
1352
1353         if (!pendingItems.isEmpty()) {
1354             
1355             int activity = explorerContext.activityInt;
1356             long delay = 30;
1357             if (activity < 100) {
1358                 //System.out.println("Scheduling update immediately.");
1359             } else if (activity < 1000) {
1360                 //System.out.println("Scheduling update after 500ms.");
1361                 delay = 500;
1362             } else {
1363                 //System.out.println("Scheduling update after 3000ms.");
1364                 delay = 3000;
1365             }
1366
1367             updateCounter = 0;
1368             
1369             //System.out.println("Scheduling UI update after " + delay + " ms.");
1370             uiUpdateScheduler.schedule(new Runnable() {
1371                 @Override
1372                 public void run() {
1373                         
1374                     if (viewer == null || viewer.getTree().isDisposed())
1375                         return;
1376                     
1377                     if (updateCounter > 0) {
1378                         updateCounter = 0;
1379                         uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1380                     } else {
1381                         viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
1382                     }
1383                     
1384                 }
1385             }, delay, TimeUnit.MILLISECONDS);
1386
1387             updating = true;
1388             return true;
1389         }
1390
1391         return false;
1392     }
1393     
1394     @Override
1395     public String startEditing(NodeContext context, String columnKey) {
1396         assertNotDisposed();
1397         if (!thread.currentThreadAccess())
1398             throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1399
1400         if(columnKey.startsWith("#")) {
1401                 columnKey = columnKey.substring(1);
1402         }
1403
1404         Integer columnIndex = columnKeyToIndex.get(columnKey);
1405         if (columnIndex == null)
1406             return "Rename not supported for selection";
1407
1408         viewer.editElement(context, columnIndex);
1409         if(viewer.isCellEditorActive()) return null;
1410         return "Rename not supported for selection";
1411     }
1412
1413     @Override
1414     public String startEditing(String columnKey) {
1415         ISelection selection = postSelectionProvider.getSelection();
1416         if(selection == null) return "Rename not supported for selection";
1417         NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1418         if(context == null) return "Rename not supported for selection";
1419
1420         return startEditing(context, columnKey);
1421
1422     }
1423     
1424     public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1425         assertNotDisposed();
1426         boolean equalsOld = selectionProvider.selectionEquals(selection);
1427         if (equalsOld && !forceControlUpdate) {
1428             // Just set the selection object instance, fire no events nor update
1429             // the viewer selection.
1430             selectionProvider.setSelection(selection);
1431         } else {
1432                 Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1433                 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1434                 for (NodeContext c : coll) {
1435                         List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1436                         if(match.size() > 0)
1437                                 nodes.add(match.get(0));
1438                 }
1439                 final ISelection sel = new StructuredSelection(nodes.toArray());
1440                 if (coll.size() == 0)
1441                         return;
1442             // Schedule viewer and selection update if necessary.
1443             if (viewer.getTree().isDisposed())
1444                 return;
1445             Display d = viewer.getTree().getDisplay();
1446             if (d.getThread() == Thread.currentThread()) {
1447                viewer.setSelection(sel);
1448             } else {
1449                 d.asyncExec(new Runnable() {
1450                     @Override
1451                     public void run() {
1452                         if (viewer.getTree().isDisposed())
1453                             return;
1454                         viewer.setSelection(sel);
1455                     }
1456                 });
1457             }
1458         }
1459     }
1460     
1461     @Override
1462     public void setModificationContext(ModificationContext modificationContext) {
1463         this.modificationContext = modificationContext;
1464         
1465     }
1466     
1467     final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();
1468     
1469     private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
1470         // This is for query debugging only.
1471         
1472         private GraphExplorerImpl2 ge;
1473         int                  queryIndent   = 0;
1474
1475         GECache2             cache         = new GECache2();
1476         AtomicBoolean        propagating   = new AtomicBoolean(false);
1477         Object               propagateList = new Object();
1478         Object               propagate     = new Object();
1479         List<Runnable>       scheduleList  = new ArrayList<Runnable>();
1480         final Deque<Integer> activity      = new LinkedList<Integer>();
1481         int                  activityInt   = 0;
1482         
1483         AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
1484
1485         /**
1486          * Keeps track of nodes that have already been auto-expanded. After
1487          * being inserted into this set, nodes will not be forced to stay in an
1488          * expanded state after that. This makes it possible for the user to
1489          * close auto-expanded nodes.
1490          */
1491         Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();
1492
1493         public GeViewerContext(GraphExplorerImpl2 ge) {
1494                 this.ge = ge;
1495         }
1496         
1497         @Override
1498         protected void doDispose() {
1499                 //saveState();
1500             autoExpanded.clear();
1501         }
1502
1503         @Override
1504         public IGECache getCache() {
1505             return cache;
1506         }
1507
1508         @Override
1509         public int queryIndent() {
1510             return queryIndent;
1511         }
1512
1513         @Override
1514         public int queryIndent(int offset) {
1515             queryIndent += offset;
1516             return queryIndent;
1517         }
1518
1519         @Override
1520         @SuppressWarnings("unchecked")
1521         public <T> NodeQueryProcessor<T> getProcessor(Object o) {
1522                 if (ge == null)
1523                         return null;
1524             return ge.processors.get(o);
1525         }
1526
1527         @Override
1528         @SuppressWarnings("unchecked")
1529         public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
1530             return ge.primitiveProcessors.get(o);
1531         }
1532
1533         @SuppressWarnings("unchecked")
1534         @Override
1535         public <T> DataSource<T> getDataSource(Class<T> clazz) {
1536             return ge.dataSources.get(clazz);
1537         }
1538
1539         @Override
1540         public void update(UIElementReference ref) {
1541                 if (ref instanceof ViewerCellReference) {
1542                     ViewerCellReference tiref = (ViewerCellReference) ref;
1543                     Object element = tiref.getElement();
1544                     int columnIndex = tiref.getColumn();
1545                     // NOTE: must be called regardless of the the item value.
1546                     // A null item is currently used to indicate a tree root update.
1547                     ge.update((TreeNode)element,columnIndex);
1548                 } else if (ref instanceof ViewerRowReference) {
1549                         ViewerRowReference rref = (ViewerRowReference)ref;
1550                         Object element = rref.getElement();
1551                         ge.update((TreeNode)element);
1552                 } else {
1553                         throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
1554                 }
1555         }
1556
1557         @Override
1558         public Object getPropagateLock() {
1559             return propagate;
1560         }
1561
1562         @Override
1563         public Object getPropagateListLock() {
1564             return propagateList;
1565         }
1566
1567         @Override
1568         public boolean isPropagating() {
1569             return propagating.get();
1570         }
1571
1572         @Override
1573         public void setPropagating(boolean b) {
1574             this.propagating.set(b);
1575         }
1576
1577         @Override
1578         public List<Runnable> getScheduleList() {
1579             return scheduleList;
1580         }
1581
1582         @Override
1583         public void setScheduleList(List<Runnable> list) {
1584             this.scheduleList = list;
1585         }
1586
1587         @Override
1588         public Deque<Integer> getActivity() {
1589             return activity;
1590         }
1591
1592         @Override
1593         public void setActivityInt(int i) {
1594             this.activityInt = i;
1595         }
1596
1597         @Override
1598         public int getActivityInt() {
1599             return activityInt;
1600         }
1601
1602         @Override
1603         public void scheduleQueryUpdate(Runnable r) {
1604                 if (ge == null)
1605                         return;
1606             if (ge.isDisposed())
1607                 return;
1608             if (currentQueryUpdater.compareAndSet(null, r)) {
1609                 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
1610             }
1611         }
1612
1613         Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
1614             @Override
1615             public void run() {
1616                 Runnable r = currentQueryUpdater.getAndSet(null);
1617                 if (r != null) {
1618                     r.run();
1619                 }
1620             }
1621         };
1622         
1623         @Override
1624         public void dispose() {
1625                 cache.dispose();
1626                 cache = new DummyCache();
1627                 scheduleList.clear();
1628                 autoExpanded.clear();
1629                 autoExpanded = null;
1630                 ge = null;
1631             
1632         }
1633
1634     }
1635     
1636     
1637    
1638     
1639     private static class GeViewerContentProvider implements ITreeContentProvider {
1640         @Override
1641         public Object[] getElements(Object inputElement) {
1642                 return getChildren(inputElement);
1643         }
1644         
1645         @Override
1646         public Object[] getChildren(Object element) {
1647                 TreeNode node = (TreeNode)element;
1648                 return node.getChildren().toArray();
1649                 
1650         }
1651         
1652         @Override
1653         public Object getParent(Object element) {
1654                 TreeNode node = (TreeNode)element;
1655                 return node.getParent();
1656         }
1657         
1658         @Override
1659         public boolean hasChildren(Object element) {
1660                 return getChildren(element).length > 0;
1661         }
1662         
1663         @Override
1664         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
1665                 
1666         }
1667         
1668         @Override
1669         public void dispose() {
1670                 
1671         }
1672     }
1673     
1674     private class GeViewerLabelProvider extends CellLabelProvider {
1675         private TreeNode node;
1676         
1677         private Labeler labeler;
1678         private Imager imager;
1679         Collection<LabelDecorator> labelDecorators;
1680         Collection<ImageDecorator> imageDecorators;
1681          
1682         Map<String, String> labels;
1683         Map<String, String> runtimeLabels;
1684         @Override
1685         public void update(ViewerCell cell) {
1686                 TreeNode node = (TreeNode)cell.getElement();
1687                 NodeContext ctx = node.getContext();
1688                 int columnIndex = cell.getColumnIndex();
1689                 String columnKey = columns[columnIndex].getKey();
1690                 
1691                 // using columnIndex 0 to refresh data.
1692                 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
1693                 if (node != this.node || columnIndex == 0) { 
1694                         this.node = node;
1695                         GENodeQueryManager manager = node.getManager();
1696                         labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
1697                         imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
1698                         labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
1699                         imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
1700                         
1701                         if (labeler != null) {
1702                         labels = labeler.getLabels();
1703                                 runtimeLabels = labeler.getRuntimeLabels();
1704                 } else {
1705                         labels = null;
1706                         runtimeLabels = null;
1707                 }
1708                 }
1709
1710                 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + "  " + columnIndex);
1711                 
1712             setText(cell,  columnKey);
1713             setImage(cell, columnKey);
1714         }
1715
1716                 void setImage(ViewerCell cell, String columnKey) {
1717                         if (imager != null) {
1718                                 Object descOrImage = null;
1719                                 boolean hasUncachedImages = false;
1720
1721                                 ImageDescriptor desc = imager.getImage(columnKey);
1722                                 if (desc != null) {
1723                                         int index = 0;
1724                                         // Attempt to decorate the label
1725                                         if (!imageDecorators.isEmpty()) {
1726                                                 for (ImageDecorator id : imageDecorators) {
1727                                                         ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
1728                                                         if (ds != null)
1729                                                                 desc = ds;
1730                                                 }
1731                                         }
1732
1733                                         // Try resolving only cached images here and now
1734                                         Object img = localResourceManager.find(desc);
1735                                         if (img == null)
1736                                                 img = resourceManager.find(desc);
1737
1738                                         descOrImage = img != null ? img : desc;
1739                                         hasUncachedImages |= img == null;
1740                                 }
1741
1742                                 if (!hasUncachedImages) {
1743                                         cell.setImage((Image) descOrImage);
1744                                 } else {
1745                                         // Schedule loading to another thread to refrain from
1746                                         // blocking
1747                                         // the UI with database operations.
1748                                         queueImageTask(node, new ImageTask(node, descOrImage));
1749                                 }
1750                         } else {
1751                                 cell.setImage(null);
1752                         }
1753                 }
1754
1755                 private void queueImageTask(TreeNode node, ImageTask task) {
1756                         synchronized (imageTasks) {
1757                                 imageTasks.put(node, task);
1758                         }
1759                         imageLoaderJob.scheduleIfNecessary(100);
1760                 }
1761
1762                 void setText(ViewerCell cell, String key) {
1763                         if (labeler != null) {
1764                                 String s = null;
1765                                 if (runtimeLabels != null)
1766                                         s = runtimeLabels.get(key);
1767                                 if (s == null)
1768                                         s = labels.get(key);
1769                                 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
1770                                 if (s != null) {
1771                                         FontDescriptor font = originalFont;
1772                                         ColorDescriptor bg = originalBackground;
1773                                         ColorDescriptor fg = originalForeground;
1774                                         
1775                                         // Attempt to decorate the label
1776                                         if (!labelDecorators.isEmpty()) {
1777                                                 int index = 0;
1778                                                 for (LabelDecorator ld : labelDecorators) {
1779                                                         String ds = ld.decorateLabel(s, key, index);
1780                                                         if (ds != null)
1781                                                                 s = ds;
1782
1783                                                         FontDescriptor dfont = ld.decorateFont(font, key, index);
1784                                                         if (dfont != null)
1785                                                                 font = dfont;
1786
1787                                                         ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
1788                                                         if (dbg != null)
1789                                                                 bg = dbg;
1790
1791                                                         ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
1792                                                         if (dfg != null)
1793                                                                 fg = dfg;
1794                                                 }
1795                                         }
1796
1797                                         if (font != originalFont) {
1798                                                 // System.out.println("set font: " + index + ": " +
1799                                                 // font);
1800                                                 cell.setFont((Font) localResourceManager.get(font));
1801                                         } else {
1802                                                 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
1803                                         }
1804                                         if (bg != originalBackground)
1805                                                 cell.setBackground((Color) localResourceManager.get(bg));
1806                                         else
1807                                                 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
1808                                         if (fg != originalForeground)
1809                                                 cell.setForeground((Color) localResourceManager.get(fg));
1810                                         else
1811                                                 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
1812
1813                                         cell.setText(s);
1814                                 }
1815                         } else {
1816                                 cell.setText(Labeler.NO_LABEL);
1817                         }
1818                         
1819                 }
1820     }
1821     
1822     
1823     
1824     private class GeEditingSupport extends EditingSupport {
1825         private Object lastElement;
1826         
1827         private Modifier lastModifier;
1828         
1829         private int columnIndex;
1830         public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
1831                         super(viewer);
1832                         this.columnIndex = columnIndex;
1833                 }
1834
1835                 @Override
1836         protected boolean canEdit(Object element) {
1837                          if (filterSelectionEdit && !selectedNodes.contains(element)) {
1838                                  // When item is clicked, canEdit is called before the selection is updated.
1839                                  // This allows filtering edit attempts when the item is selected. 
1840                                  return false;
1841                          }
1842                         lastElement = null; // clear cached element + modifier.
1843                         Modifier modifier = getModifier((TreeNode)element);
1844                         if (modifier == null)
1845                                 return false;
1846                 return true;
1847         }
1848         
1849         @Override
1850         protected CellEditor getCellEditor(Object element) {
1851                 TreeNode node = (TreeNode) element;
1852                 Modifier modifier = getModifier((TreeNode)element);
1853                 NodeContext context = node.getContext();
1854                 if (modifier instanceof DialogModifier) {
1855                 return performDialogEditing(context, (DialogModifier) modifier);
1856             } else if (modifier instanceof CustomModifier) {
1857                 return startCustomEditing(node, (CustomModifier) modifier);
1858             } else if (modifier instanceof EnumerationModifier) {
1859                 return startEnumerationEditing((EnumerationModifier) modifier);
1860             } else {
1861                 return startTextEditing(modifier);
1862             }
1863                 
1864         }
1865         
1866         @Override
1867         protected Object getValue(Object element) {
1868                 Modifier modifier = getModifier((TreeNode)element);
1869                 return modifier.getValue();
1870         }
1871         @Override
1872         protected void setValue(Object element, Object value) {
1873                 Modifier modifier = getModifier((TreeNode)element);
1874                 // CustomModifiers have internal set value mechanism.
1875                 if (!(modifier instanceof CustomModifier))
1876                         modifier.modify((String)value);
1877                 
1878         }
1879         
1880         CellEditor startTextEditing( Modifier modifier) {
1881                 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
1882                 editor.setValidator(new ModifierValidator(modifier));
1883                 return editor;
1884         }
1885         
1886         CellEditor startEnumerationEditing(EnumerationModifier modifier) {
1887                 if (SimanticsUI.isLinuxGTK()) {
1888                         // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
1889                         // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. 
1890                         return new EnumModifierEditor(viewer.getTree(),modifier);
1891                 } else {
1892                         return new EnumModifierEditor2(viewer.getTree(),modifier);
1893                 }
1894         }
1895         
1896         CellEditor performDialogEditing(final NodeContext context, final  DialogModifier modifier) {
1897                 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
1898                                 String res = null;
1899                                 @Override
1900                                 protected Object openDialogBox(Control cellEditorWindow) {
1901
1902                                     final Semaphore sem = new Semaphore(1);
1903                                         Consumer<String> callback = result -> {
1904                                             res = result;
1905                                             sem.release();
1906                                 };
1907                                         String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
1908                                         if (status != null)
1909                                                 return null;
1910                                         try {
1911                                                 sem.acquire();
1912                                         } catch (InterruptedException e) {
1913                                                 e.printStackTrace();
1914                                         }
1915                                         return res;
1916                                 }
1917                                 
1918                                 
1919                         };
1920                         editor.setValidator(new ModifierValidator(modifier));
1921                         return editor;
1922         }
1923         
1924         CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
1925                 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
1926                 return editor;
1927         }
1928         
1929                 private Modifier getModifier(TreeNode element) {
1930                         if (element == lastElement)
1931                                 return lastModifier;
1932                         lastModifier =  GraphExplorerImpl2.this.getModifier(element, columnIndex);
1933                         lastElement = element;
1934                         return lastModifier;
1935                 }
1936         
1937         
1938     }
1939     
1940     private Modifier getModifier(TreeNode element, int columnIndex) {
1941                 GENodeQueryManager manager = element.getManager();
1942                 final NodeContext context = element.getContext();
1943             Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1944             if (labeler == null)
1945                  return null;
1946             Column column = columns[columnIndex];
1947
1948         return labeler.getModifier(modificationContext, column.getKey());
1949
1950         }
1951
1952     static class ImageTask {
1953         TreeNode node;
1954         Object descsOrImage;
1955         public ImageTask(TreeNode node, Object descsOrImage) {
1956             this.node = node;
1957             this.descsOrImage = descsOrImage;
1958         }
1959     }
1960
1961     /**
1962      * The job that is used for off-loading image loading tasks (see
1963      * {@link ImageTask} to a worker thread from the main UI thread.
1964      */
1965     ImageLoaderJob           imageLoaderJob;
1966     
1967    // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();
1968     Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();
1969     
1970     /**
1971      * Invoked in a job worker thread.
1972      * 
1973      * @param monitor
1974      */
1975     @Override
1976     protected IStatus setPendingImages(IProgressMonitor monitor) {
1977         ImageTask[] tasks = null;
1978         synchronized (imageTasks) {
1979             tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
1980             imageTasks.clear();
1981         }
1982
1983         MultiStatus status = null;
1984
1985         // Load missing images
1986         for (ImageTask task : tasks) {
1987             Object desc = task.descsOrImage;
1988                  if (desc instanceof ImageDescriptor) {
1989                         try {
1990                             desc = resourceManager.get((ImageDescriptor) desc);
1991                             task.descsOrImage = desc;
1992                         } catch (DeviceResourceException e) {
1993                             if (status == null)
1994                                 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
1995                             status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
1996                         }
1997                     }
1998             
1999         }
2000
2001         // Perform final UI updates in the UI thread.
2002         final ImageTask[] _tasks = tasks;
2003         thread.asyncExec(new Runnable() {
2004             @Override
2005             public void run() {
2006                 setImages(_tasks);
2007             }
2008         });
2009
2010         return status != null ? status : Status.OK_STATUS;
2011     }
2012     
2013
2014     void setImages(ImageTask[] tasks) {
2015         for (ImageTask task : tasks)
2016             if (task != null)
2017                 setImage(task);
2018     }
2019     
2020     void setImage(ImageTask task) {
2021         if (!task.node.isDisposed())
2022                 update(task.node, 0);
2023     }
2024     
2025         private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2026                 
2027                 private GraphExplorerImpl2 ge;
2028                 
2029                 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
2030                         this.ge = ge;
2031                 }
2032                 
2033                 void dispose() {
2034                         ge = null;
2035                 }
2036                 
2037             @Override
2038             public void setSelection(final ISelection selection) {
2039                 if(ge == null) return;
2040                 ge.setSelection(selection, false);
2041                 
2042             }
2043             
2044
2045             @Override
2046             public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2047                 if(ge == null) return;
2048                 if(ge.isDisposed()) {
2049                     if (DEBUG_SELECTION_LISTENERS)
2050                         System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2051                     return;
2052                 }
2053                 ge.selectionProvider.removeSelectionChangedListener(listener);
2054             }
2055             
2056             @Override
2057             public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2058                 if(ge == null) return;
2059                 if (!ge.thread.currentThreadAccess())
2060                     throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2061                 if(ge.isDisposed()) {
2062                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2063                     return;
2064                 }
2065                 ge.selectionProvider.addPostSelectionChangedListener(listener);
2066             }
2067
2068             @Override
2069             public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2070                 if(ge == null) return;
2071                 if(ge.isDisposed()) {
2072                     if (DEBUG_SELECTION_LISTENERS)
2073                         System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2074                     return;
2075                 }
2076                 ge.selectionProvider.removePostSelectionChangedListener(listener);
2077             }
2078             
2079
2080             @Override
2081             public void addSelectionChangedListener(ISelectionChangedListener listener) {
2082                 if(ge == null) return;
2083                 if (!ge.thread.currentThreadAccess())
2084                     throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2085                 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
2086                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2087                     return;
2088                 }
2089
2090                 ge.selectionProvider.addSelectionChangedListener(listener);
2091             }
2092
2093             
2094             @Override
2095             public ISelection getSelection() {
2096                 if(ge == null) return StructuredSelection.EMPTY;
2097                 if (!ge.thread.currentThreadAccess())
2098                     throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2099                 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
2100                     return StructuredSelection.EMPTY;
2101                 return ge.selectionProvider.getSelection();
2102             }
2103             
2104         }
2105         
2106         static class ModifierValidator implements ICellEditorValidator {
2107                 private Modifier modifier;
2108                 public ModifierValidator(Modifier modifier) {
2109                         this.modifier = modifier;
2110                 }
2111                 
2112                 @Override
2113                 public String isValid(Object value) {
2114                         return modifier.isValid((String)value);
2115                 }
2116         }
2117         
2118         static class UpdateRunner implements Runnable {
2119
2120             final GraphExplorerImpl2 ge;
2121
2122             UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
2123                 this.ge = ge;
2124             }
2125
2126             public void run() {
2127                 try {
2128                         doRun();
2129                 } catch (Throwable t) {
2130                         t.printStackTrace();
2131                 }
2132             }
2133
2134             public void doRun() {
2135                 
2136                 if (ge.isDisposed())
2137                     return;
2138
2139                 HashSet<UpdateItem> items;
2140
2141                 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
2142               
2143                 
2144                 synchronized (ge.pendingItems) {
2145                    items = ge.pendingItems;
2146                    ge.pendingItems = new HashSet<UpdateItem>();
2147                 }
2148                 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2149
2150                 ge.viewer.getTree().setRedraw(false);
2151             for (UpdateItem item : items) {
2152                 item.update(ge.viewer);
2153             }
2154             
2155             // check if vertical scroll bar has become visible and refresh layout.
2156             boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2157             if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2158                 ge.verticalBarVisible = currentlyVerticalBarVisible;
2159                 ge.viewer.getTree().getParent().layout();
2160             }
2161             
2162             ge.viewer.getTree().setRedraw(true);
2163             
2164                 synchronized (ge.pendingItems) {
2165                     if (!ge.scheduleUpdater()) {
2166                         ge.updating = false;
2167                     }
2168                 }
2169                 if (DEBUG) {
2170                         if (!ge.updating) {
2171                                  ge.printTree(ge.rootNode, 0);
2172                         }
2173                 }
2174             }
2175
2176         }
2177         
2178         private class ValidatedTextEditor extends TextCellEditor {
2179                 
2180
2181                 public ValidatedTextEditor(Composite parent) {
2182                         super(parent);
2183                 }
2184
2185                 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
2186                          String value = text.getText();
2187                         if (value == null) {
2188                                         value = "";//$NON-NLS-1$
2189                                 }
2190                         Object typedValue = value;
2191                         boolean oldValidState = isValueValid();
2192                         boolean newValidState = isCorrect(typedValue);
2193                         if (!newValidState) {
2194                            text.setBackground(invalidModificationColor);
2195                            text.setToolTipText(getErrorMessage());
2196                         } else {
2197                                 text.setBackground(null);
2198                                 text.setToolTipText(null);
2199                         }
2200                         valueChanged(oldValidState, newValidState);
2201                 };
2202         }
2203         
2204         private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
2205                 
2206                 List<String> values;
2207                 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
2208                         super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2209                         values = modifier.getValues();
2210                         setValidator(new ModifierValidator(modifier));
2211                 }
2212                 @Override
2213                 protected void doSetValue(Object value) {
2214                         super.doSetValue((Integer)values.indexOf(value));
2215                 }
2216                 
2217                 @Override
2218                 protected Object doGetValue() {
2219                         return values.get((Integer)super.doGetValue());
2220                 }
2221         };
2222         
2223         private class EnumModifierEditor extends ComboBoxCellEditor {
2224                 
2225                 List<String> values;
2226                 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
2227                         super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2228                         values = modifier.getValues();
2229                         setValidator(new ModifierValidator(modifier));
2230                 }
2231                 @Override
2232                 protected void doSetValue(Object value) {
2233                         super.doSetValue((Integer)values.indexOf(value));
2234                 }
2235                 
2236                 @Override
2237                 protected Object doGetValue() {
2238                         return values.get((Integer)super.doGetValue());
2239                 }
2240         };
2241         
2242         
2243         private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
2244                 private CustomModifier modifier;
2245                 private TreeNode node;
2246                 private NodeContext context;
2247                 private int columnIndex;
2248                 private Composite control;
2249                 private Control origControl;
2250                 
2251                 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
2252                         this.modifier = modifier;
2253                         this.node = node;
2254                         this.context = node.getContext();
2255                         this.columnIndex = columnIndex;
2256                         setValidator(this);
2257                         create(parent);
2258                 }
2259                 
2260                 @Override
2261                 protected Control createControl(Composite parent) {
2262                         control = new Composite(parent, SWT.NONE);
2263                         control.setLayout(new FillLayout());
2264                         origControl = (Control) modifier.createControl(control, null, columnIndex, context);
2265                         return control;
2266                 }
2267                 
2268                 
2269                 
2270                 @Override
2271                 protected Object doGetValue() {
2272                         return modifier.getValue();
2273                 }
2274                 
2275                 @Override
2276                 protected void doSetValue(Object value) {
2277                         //CustomModifier handles value setting internally.
2278                 }
2279                 
2280                 
2281                 private void reCreate() {
2282                         modifier = (CustomModifier)getModifier(node, columnIndex);
2283                         if (control != null && !control.isDisposed()) {
2284                                 if (!origControl.isDisposed())
2285                                         origControl.dispose();
2286                                 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
2287                                 origControl.addDisposeListener(this);
2288                         }
2289                 }
2290                 protected void doSetFocus() {
2291                         if (control != null && !control.isDisposed())
2292                                 control.setFocus();
2293                 };
2294                 
2295                 @Override
2296                 public void widgetDisposed(DisposeEvent e) {
2297                         if (e.widget == origControl) {
2298                                 reCreate();
2299                         }
2300                         
2301                 }
2302                 
2303                 @Override
2304                 public String isValid(Object value) {
2305                         return modifier.isValid((String)value);
2306                 }
2307
2308         }
2309         
2310         private class TreeNode implements IAdaptable {
2311                 private NodeContext context;
2312                 
2313                 private TreeNode parent;
2314                 private List<TreeNode> children = new ArrayList<TreeNode>();
2315                 private GENodeQueryManager manager;
2316                 
2317                 private TreeNode(NodeContext context) {
2318                         if (context == null)
2319                                 throw new NullPointerException();
2320                         this.context = context;
2321                         contextToNodeMap.add(context, this);
2322                         manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
2323                 }
2324                 
2325                 public List<TreeNode> getChildren() {
2326                         synchronized (children) {
2327                                 return children;
2328                         }
2329                 }
2330                 
2331                 public TreeNode getParent() {
2332                         return parent;
2333                 }
2334                 
2335                 public NodeContext getContext() {
2336                         return context;
2337                 }
2338                 
2339                 public GENodeQueryManager getManager() {
2340                         return manager;
2341                 }
2342                 
2343                 public TreeNode addChild(NodeContext context) {
2344                         TreeNode child = new TreeNode(context);
2345                         child.parent = this;
2346                         children.add(child);
2347                         if (DEBUG) System.out.println("Add " + this  + " -> " + child);
2348                         return child;
2349                 }
2350                 
2351                 public TreeNode addChild(int index, NodeContext context) {
2352                         
2353                         TreeNode child = new TreeNode(context);
2354                         child.parent = this;
2355                         children.add(index,child);
2356                         if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);
2357                         return child;
2358                 }
2359                 
2360                 public TreeNode setChild(int index, NodeContext context) {
2361                         
2362                         TreeNode child = new TreeNode(context);
2363                         child.parent = this;
2364                         children.set(index,child);
2365                         if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);
2366                         return child;
2367                 }
2368                 
2369                 public int distanceToRoot() {
2370                         int i = 0;
2371                         TreeNode n = getParent();
2372                         while (n != null) {
2373                                 n = n.getParent();
2374                                 i++;
2375                         }
2376                         return i;
2377                                 
2378                 }
2379                 
2380                 public void dispose() {
2381                         if (parent != null)
2382                                 parent.children.remove(this);
2383                         dispose2();
2384                 }
2385                 
2386                 public void dispose2() {
2387                         if (DEBUG)      System.out.println("dispose " + this);
2388                         parent = null;
2389                         for (TreeNode n : children) {
2390                                 n.dispose2();
2391                         }
2392                         clearCache();
2393                         children.clear();
2394                         contextToNodeMap.remove(context, this);
2395                         context = null;
2396                         manager.dispose();
2397                         manager = null; 
2398                 }
2399                 
2400                 private void clearCache() {
2401                         if (explorerContext != null) {
2402                                 GECache2 cache = explorerContext.cache;
2403                                 
2404                                 if (cache != null) {
2405                                         cache.dispose(context);
2406                                 }
2407                         }
2408                 }
2409                 
2410                 public boolean updateChildren() {
2411                         if (context == null)
2412                                 throw new IllegalStateException("Node is disposed.");
2413                         
2414                 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
2415                 
2416                 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
2417                 
2418                 
2419                 boolean modified = false;
2420                 synchronized (children) {
2421                         
2422                         int oldCount = children.size();
2423                         BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
2424                         Set<Integer> mapped = new HashSet<Integer>();
2425                         boolean reorder = false;
2426                         // locate matching pairs form old and new children
2427                         for (int i = 0; i < oldCount; i++) {
2428                                 NodeContext oldCtx = children.get(i).context;
2429                                 for (int j = 0; j <childContexts.length; j++) {
2430                                         if (mapped.contains(j))
2431                                                 continue;
2432                                         if (oldCtx.equals(childContexts[j])) {
2433                                                 indexes.map(i, j);
2434                                                 mapped.add(j);
2435                                                 if (i != j)
2436                                                         reorder = true;
2437                                                 break;
2438                                         }
2439                                 }
2440                         }
2441                         // update children if required
2442                         if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
2443                                 modified = true;
2444                                 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
2445                                 oldChildren.addAll(children);
2446                                 if (childContexts.length >= oldCount) {
2447                                 for (int i = 0; i < oldCount; i++) {
2448                                         Integer oldIndex = indexes.getLeft(i);
2449                                         if (oldIndex == null) {
2450                                                 setChild(i, childContexts[i]);
2451                                         } else {
2452                                                 TreeNode n = oldChildren.get(oldIndex);
2453                                                 children.set(i, n);
2454                                         }
2455                                         
2456                                 }
2457                                 for (int i = oldCount; i < childContexts.length; i++) {
2458                                         addChild(childContexts[i]);
2459                                 }
2460                         } else {
2461                                 for (int i = 0; i < childContexts.length; i++) {
2462                                         Integer oldIndex = indexes.getLeft(i);
2463                                         if (oldIndex == null) {
2464                                                 setChild(i, childContexts[i]);
2465                                         } else {
2466                                                 TreeNode n = oldChildren.get(oldIndex);
2467                                                 children.set(i, n);
2468                                         }
2469                                 }
2470                                 for (int i = oldCount -1; i >= childContexts.length; i--) {
2471                                         children.remove(i);
2472                                 }
2473                         }
2474                                 for (int i = 0; i < oldChildren.size(); i++) {
2475                                         if (!indexes.containsLeft(i)) {
2476                                                 oldChildren.get(i).dispose2();
2477                                         }
2478                                 }
2479                                 
2480                         }
2481                         
2482                         }
2483                 return modified;
2484                 }
2485                 
2486                 public boolean isDisposed() {
2487                         return context == null;
2488                 }
2489                 
2490                 @SuppressWarnings("rawtypes")
2491                 @Override
2492                 public Object getAdapter(Class adapter) {
2493                         if (adapter == NodeContext.class)
2494                                 return context;
2495                         return context.getAdapter(adapter);
2496                 }
2497                 
2498 //              @Override
2499 //              public String toString() {
2500 //                      String s = "";
2501 //                      if (manager != null) {
2502 //                              
2503 //                              s+= super.toString() + " ";
2504 //                              try {
2505 //                                      Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
2506 //                                      Map<String,String> labels = labeler.getLabels();
2507 //                                      for (Entry<String, String> l : labels.entrySet()) {
2508 //                                              s+= l.getKey() + " : " + l.getValue() + " ";
2509 //                                      }
2510 //                              } catch (Exception e) {
2511 //                                      
2512 //                              }
2513 //                      } else {
2514 //                              s = super.toString(); 
2515 //                      }
2516 //                      if (context != null)
2517 //                              s += " context " + context.hashCode();
2518 //                      return s;
2519 //                      
2520 //              }
2521                                 
2522         }
2523         
2524         private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2525         IsExpandedProcessor, ProcessorLifecycle {
2526                  /**
2527              * The set of currently expanded node contexts.
2528              */
2529             private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();
2530             private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2531
2532             private Tree tree;
2533
2534             public TreeNodeIsExpandedProcessor() {
2535             }
2536
2537             @Override
2538             public Object getIdentifier() {
2539                 return BuiltinKeys.IS_EXPANDED;
2540             }
2541
2542             @Override
2543             public String toString() {
2544                 return "IsExpandedProcessor";
2545             }
2546
2547             @Override
2548             public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2549                 boolean isExpanded = expanded.contains(context);
2550                 expandedQueries.put(context, updater);
2551                 return Boolean.valueOf(isExpanded);
2552             }
2553
2554             @Override
2555             public Collection<NodeContext> getExpanded() {
2556                 return new HashSet<NodeContext>(expanded);
2557             }
2558
2559             @Override
2560             public boolean getExpanded(NodeContext context) {
2561                 return this.expanded.contains(context);
2562             }
2563
2564             @Override
2565             public boolean setExpanded(NodeContext context, boolean expanded) {
2566                 return _setExpanded(context, expanded);
2567             }
2568
2569             @Override
2570             public boolean replaceExpanded(NodeContext context, boolean expanded) {
2571                 return nodeStatusChanged(context, expanded);
2572             }
2573
2574             private boolean _setExpanded(NodeContext context, boolean expanded) {
2575                 if (expanded) {
2576                     return this.expanded.add(context);
2577                 } else {
2578                     return this.expanded.remove(context);
2579                 }
2580             }
2581
2582             Listener treeListener = new Listener() {
2583                 @Override
2584                 public void handleEvent(Event event) {
2585                     TreeNode node = (TreeNode) event.item.getData();
2586                     NodeContext context = node.getContext();
2587                     switch (event.type) {
2588                         case SWT.Expand:
2589                             nodeStatusChanged(context, true);
2590                             break;
2591                         case SWT.Collapse:
2592                             nodeStatusChanged(context, false);
2593                             break;
2594                     }
2595                 }
2596             };
2597
2598             protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2599                 boolean result = _setExpanded(context, expanded);
2600                 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2601                 if (updater != null)
2602                     updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2603                 return result;
2604             }
2605
2606             @Override
2607             public void attached(GraphExplorer explorer) {
2608                 Object control = explorer.getControl();
2609                 if (control instanceof Tree) {
2610                     this.tree = (Tree) control;
2611                     tree.addListener(SWT.Expand, treeListener);
2612                     tree.addListener(SWT.Collapse, treeListener);
2613                 } else {
2614                     System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2615                 }
2616             }
2617
2618             @Override
2619             public void clear() {
2620                 expanded.clear();
2621                 expandedQueries.clear();
2622             }
2623
2624             @Override
2625             public void detached(GraphExplorer explorer) {
2626                 clear();
2627                 if (tree != null) {
2628                     tree.removeListener(SWT.Expand, treeListener);
2629                     tree.removeListener(SWT.Collapse, treeListener);
2630                     tree = null;
2631                 }
2632             }
2633         }
2634         
2635         private void printTree(TreeNode node, int depth) {
2636                 String s = "";
2637                 for (int i = 0; i < depth; i++) {
2638                         s += "  ";
2639                 }
2640                 s += node;
2641                 System.out.println(s);
2642                 int d = depth+1;
2643                 for (TreeNode n : node.getChildren()) {
2644                         printTree(n, d);
2645                 }
2646                 
2647         }
2648         
2649         /**
2650      * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2651      */
2652         final private static class GECacheKey {
2653
2654                 private NodeContext context;
2655                 private CacheKey<?> key;
2656
2657                 GECacheKey(NodeContext context, CacheKey<?> key) {
2658                         this.context = context;
2659                         this.key = key;
2660                         if (context == null || key == null)
2661                                 throw new IllegalArgumentException("Null context or key is not accepted");
2662                 }
2663
2664                 GECacheKey(GECacheKey other) {
2665                         this.context = other.context;
2666                         this.key = other.key;
2667                         if (context == null || key == null)
2668                                 throw new IllegalArgumentException("Null context or key is not accepted");
2669                 }
2670
2671                 void setValues(NodeContext context, CacheKey<?> key) {
2672                         this.context = context;
2673                         this.key = key;
2674                         if (context == null || key == null)
2675                                 throw new IllegalArgumentException("Null context or key is not accepted");
2676                 }
2677
2678                 @Override
2679                 public int hashCode() {
2680                         return context.hashCode() | key.hashCode();
2681                 }
2682
2683                 @Override
2684                 public boolean equals(Object object) {
2685
2686                         if (this == object)
2687                                 return true;
2688                         else if (object == null)
2689                                 return false;
2690
2691                         GECacheKey i = (GECacheKey) object;
2692
2693                         return key.equals(i.key) && context.equals(i.context);
2694
2695                 }
2696
2697         };
2698         
2699     /**
2700      * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2701      */
2702         private static class GECache2 implements IGECache {
2703                 
2704                 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2705                 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2706                 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2707                 
2708                  /**
2709              * This single instance is used for all get operations from the cache. This
2710              * should work since the GE cache is meant to be single-threaded within the
2711              * current UI thread, what ever that thread is. For put operations which
2712              * store the key, this is not used.
2713              */
2714             NodeContext getNC = new NodeContext() {
2715                 @SuppressWarnings("rawtypes")
2716                         @Override
2717                 public Object getAdapter(Class adapter) {
2718                         return null;
2719                 }
2720                 
2721                 @Override
2722                 public <T> T getConstant(ConstantKey<T> key) {
2723                         return null;
2724                 }
2725                 
2726                 @Override
2727                 public Set<ConstantKey<?>> getKeys() {
2728                         return Collections.emptySet();
2729                 }
2730             };
2731             CacheKey<?> getCK = new CacheKey<Object>() {
2732                 @Override
2733                 public Object processorIdenfitier() {
2734                         return this;
2735                 }
2736                 };
2737             GECacheKey getKey = new GECacheKey(getNC, getCK);
2738             
2739             
2740             private void addKey(GECacheKey key) {
2741                 Set<GECacheKey> refs = keyRefs.get(key.context);
2742                 if (refs != null) {
2743                     refs.add(key);
2744                 } else {
2745                     refs = new HashSet<GECacheKey>();
2746                     refs.add(key);
2747                     keyRefs.put(key.context, refs);
2748                 }
2749             }
2750             
2751             private void removeKey(GECacheKey key) {
2752                 Set<GECacheKey> refs = keyRefs.get(key.context);
2753                 if (refs != null) {
2754                     refs.remove(key);
2755                 } 
2756             }
2757
2758             public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2759 //              if (DEBUG) System.out.println("Add entry " + context + " " + key);
2760                 IGECacheEntry entry = new GECacheEntry(context, key, value);
2761                 GECacheKey gekey = new GECacheKey(context, key);
2762                 entries.put(gekey, entry);
2763                 addKey(gekey);