]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/GraphDebugger.java
Graph debugger: handle case where assertions does not have predicate
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / GraphDebugger.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.debug.ui;
13
14 import java.io.File;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.lang.reflect.Array;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.UUID;
30 import java.util.concurrent.CopyOnWriteArrayList;
31
32 import org.eclipse.core.runtime.Assert;
33 import org.eclipse.core.runtime.FileLocator;
34 import org.eclipse.core.runtime.IPath;
35 import org.eclipse.core.runtime.Path;
36 import org.eclipse.jface.action.IStatusLineManager;
37 import org.eclipse.jface.dialogs.Dialog;
38 import org.eclipse.jface.dialogs.IDialogConstants;
39 import org.eclipse.jface.dialogs.IDialogSettings;
40 import org.eclipse.jface.dialogs.IInputValidator;
41 import org.eclipse.jface.dialogs.InputDialog;
42 import org.eclipse.jface.dialogs.MessageDialog;
43 import org.eclipse.jface.layout.GridDataFactory;
44 import org.eclipse.jface.resource.ColorDescriptor;
45 import org.eclipse.jface.resource.JFaceResources;
46 import org.eclipse.jface.resource.LocalResourceManager;
47 import org.eclipse.swt.SWT;
48 import org.eclipse.swt.SWTError;
49 import org.eclipse.swt.browser.Browser;
50 import org.eclipse.swt.browser.LocationAdapter;
51 import org.eclipse.swt.browser.LocationEvent;
52 import org.eclipse.swt.dnd.DND;
53 import org.eclipse.swt.dnd.DropTarget;
54 import org.eclipse.swt.dnd.DropTargetAdapter;
55 import org.eclipse.swt.dnd.DropTargetEvent;
56 import org.eclipse.swt.dnd.TextTransfer;
57 import org.eclipse.swt.dnd.Transfer;
58 import org.eclipse.swt.events.DisposeEvent;
59 import org.eclipse.swt.events.DisposeListener;
60 import org.eclipse.swt.events.FocusEvent;
61 import org.eclipse.swt.events.FocusListener;
62 import org.eclipse.swt.events.KeyAdapter;
63 import org.eclipse.swt.events.KeyEvent;
64 import org.eclipse.swt.events.ModifyEvent;
65 import org.eclipse.swt.events.ModifyListener;
66 import org.eclipse.swt.events.SelectionEvent;
67 import org.eclipse.swt.events.SelectionListener;
68 import org.eclipse.swt.graphics.Color;
69 import org.eclipse.swt.graphics.Point;
70 import org.eclipse.swt.graphics.RGB;
71 import org.eclipse.swt.layout.GridData;
72 import org.eclipse.swt.layout.GridLayout;
73 import org.eclipse.swt.widgets.Button;
74 import org.eclipse.swt.widgets.Composite;
75 import org.eclipse.swt.widgets.Label;
76 import org.eclipse.swt.widgets.Text;
77 import org.eclipse.ui.IWorkbenchSite;
78 import org.simantics.databoard.Bindings;
79 import org.simantics.databoard.Databoard;
80 import org.simantics.databoard.binding.Binding;
81 import org.simantics.databoard.binding.error.BindingException;
82 import org.simantics.databoard.serialization.Serializer;
83 import org.simantics.databoard.type.ArrayType;
84 import org.simantics.databoard.type.Datatype;
85 import org.simantics.databoard.type.StringType;
86 import org.simantics.databoard.util.ObjectUtils;
87 import org.simantics.db.AsyncRequestProcessor;
88 import org.simantics.db.ReadGraph;
89 import org.simantics.db.Resource;
90 import org.simantics.db.Session;
91 import org.simantics.db.Statement;
92 import org.simantics.db.VirtualGraph;
93 import org.simantics.db.WriteGraph;
94 import org.simantics.db.common.ResourceArray;
95 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
96 import org.simantics.db.common.request.Queries;
97 import org.simantics.db.common.request.ReadRequest;
98 import org.simantics.db.common.request.WriteRequest;
99 import org.simantics.db.common.uri.ResourceToPossibleURI;
100 import org.simantics.db.common.utils.ListUtils;
101 import org.simantics.db.common.utils.NameUtils;
102 import org.simantics.db.common.utils.OrderedSetUtils;
103 import org.simantics.db.event.ChangeEvent;
104 import org.simantics.db.event.ChangeListener;
105 import org.simantics.db.exception.AdaptionException;
106 import org.simantics.db.exception.DatabaseException;
107 import org.simantics.db.exception.ResourceNotFoundException;
108 import org.simantics.db.exception.ValidationException;
109 import org.simantics.db.layer0.adapter.StringModifier;
110 import org.simantics.db.layer0.variable.RVI;
111 import org.simantics.db.layer0.variable.Variable;
112 import org.simantics.db.layer0.variable.Variables;
113 import org.simantics.db.service.ClusteringSupport;
114 import org.simantics.db.service.GraphChangeListenerSupport;
115 import org.simantics.db.service.SerialisationSupport;
116 import org.simantics.db.service.VirtualGraphSupport;
117 import org.simantics.db.service.XSupport;
118 import org.simantics.debug.ui.internal.Activator;
119 import org.simantics.debug.ui.internal.DebugUtils;
120 import org.simantics.debug.ui.internal.HashMultiMap;
121 import org.simantics.layer0.Layer0;
122 import org.simantics.ui.dnd.LocalObjectTransfer;
123 import org.simantics.ui.dnd.ResourceReferenceTransfer;
124 import org.simantics.ui.dnd.ResourceTransferUtils;
125 import org.simantics.ui.utils.ResourceAdaptionUtils;
126 import org.simantics.utils.Container;
127 import org.simantics.utils.FileUtils;
128 import org.simantics.utils.datastructures.BijectionMap;
129 import org.simantics.utils.strings.AlphanumComparator;
130 import org.simantics.utils.ui.ErrorLogger;
131 import org.simantics.utils.ui.PathUtils;
132 import org.simantics.utils.ui.workbench.WorkbenchUtils;
133
134
135 public class GraphDebugger extends Composite {
136
137     public interface HistoryListener {
138         void historyChanged();
139     }
140
141     private static final String                         STATEMENT_PART_SEPARATOR  = ",";
142     private final static String                         DEFAULT_DEBUGGER_CSS_FILE = "debugger.css";
143     private final static String                         DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE;
144
145     private static int                                  RESOURCE_NAME_MAX_LENGTH  = 1000;
146     private static int                                  RESOURCE_VALUE_MAX_SIZE = 16384;
147
148     private final LocalResourceManager                  resourceManager;
149
150     private String                                      cssPath;
151
152     private Browser                                     browser;
153     private final ColorDescriptor                       green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));
154
155     private final boolean                               displayClusters           = true;
156
157     private final BijectionMap<String, Resource>        links                     = new BijectionMap<String, Resource>();
158     private final LinkedList<Resource>                  backHistory               = new LinkedList<Resource>();
159     private final LinkedList<Resource>                  forwardHistory            = new LinkedList<Resource>();
160     private Resource                                    currentElement            = null;
161
162     /**
163      * The Session used to access the graph. Received from outside of this
164      * class and therefore it is not disposed here, just used.
165      */
166     private final Session                               session;
167
168     private final CopyOnWriteArrayList<HistoryListener> historyListeners          = new CopyOnWriteArrayList<HistoryListener>();
169
170     private final AsyncRequestProcessor                 updater;
171
172     protected Layer0                                    L0;
173
174     protected IWorkbenchSite                            site;
175
176     private final ChangeListener changeListener = new ChangeListener() {
177         @Override
178         public void graphChanged(ChangeEvent e) {
179             // This makes sure that the transaction for updating this
180             // GraphDebugger get executed in a serialized fashion.
181             updater.asyncRequest(new ReadRequest() {
182
183                 @Override
184                 public void run(ReadGraph graph) throws DatabaseException {
185                     updateContent(graph, currentElement);
186                 }
187
188             });
189
190         }
191     };
192
193     /**
194      * @param parent
195      * @param style
196      * @param session
197      * @param resource the initial resource to debug or <code>null</code> for
198      *        initially blank UI.
199      * @param site the workbench site that contains this debugger, for workbench
200      *        service access
201      */
202     public GraphDebugger(Composite parent, int style, final Session session, Resource resource, IWorkbenchSite site) {
203         this(parent, style, session, resource);
204         this.site = site;
205     }
206
207     /**
208      * @param parent
209      * @param style
210      * @param session
211      * @param resource the initial resource to debug or <code>null</code> for
212      *        initially blank UI.
213      */
214     public GraphDebugger(Composite parent, int style, final Session session, Resource resource) {
215         super(parent, style);
216         Assert.isNotNull(session, "session is null");
217         this.session = session;
218         this.currentElement = resource;
219         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
220
221         updater = session;//.getService(MergingGraphRequestProcessor.class);
222
223         initializeCSS();
224
225         addDisposeListener(new DisposeListener() {
226             @Override
227             public void widgetDisposed(DisposeEvent e) {
228                 GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);
229                 support.removeListener(changeListener);
230             }
231         });
232     }
233
234     /**
235      * When given to setStatus, indicates that the message shouldn't be touched
236      * since <code>null</code> has a different meaning.
237      */
238     private static final String DONT_TOUCH = "DONT_TOUCH";
239
240     protected void setStatus(String message, String error) {
241         IStatusLineManager status = WorkbenchUtils.getStatusLine(site);
242         if (status != null) {
243             if (message != DONT_TOUCH)
244                 status.setMessage(message);
245             if (error != DONT_TOUCH)
246                 status.setErrorMessage(error);
247         }
248     }
249
250     public void defaultInitializeUI() {
251         setLayout(new GridLayout(2, false));
252         setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
253
254         createResourceText(this);
255         createDropLabel(this);
256         createBrowser(this);
257
258     }
259
260     protected void initializeCSS() {
261         // Extract default css to a temporary location if necessary.
262         try {
263             IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH);
264             if (absolutePath != null) {
265                 cssPath = absolutePath.toFile().toURI().toString();
266             } else {
267                 File tempDir = FileUtils.getOrCreateTemporaryDirectory(false);
268                 File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE);
269                 if (!css.exists()) {
270                     URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null);
271                     if (url == null)
272                         throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'");
273                     cssPath = FileUtils.copyResource(url, css, true).toURI().toString();
274                 } else {
275                     cssPath = css.toURI().toString();
276                 }
277             }
278         } catch (IOException e) {
279             // CSS extraction failed, let's just live without it then.
280             ErrorLogger.defaultLogWarning(e);
281         }
282     }
283
284     private static final String PROMPT_TEXT = "Enter resource ID (RID) or URI";
285
286     public void createResourceText(final Composite parent) {
287         final Text text = new Text(parent, SWT.BORDER);
288         text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
289         text.setText(PROMPT_TEXT);
290         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(text);
291         
292         text.addFocusListener(new FocusListener() {    
293             @Override
294             public void focusLost(FocusEvent e) {
295                 if (text.getText().trim().equals("")) {
296                     text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
297                     text.setText(PROMPT_TEXT);
298                 }
299             }
300             @Override
301             public void focusGained(FocusEvent e) {
302                 if (text.getText().trim().equals(PROMPT_TEXT)) {
303                     text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));
304                     text.setText("");
305                 }
306                 text.selectAll();
307             }
308         });
309         text.addKeyListener(new KeyAdapter() {
310             @Override
311             public void keyPressed(KeyEvent e) {
312                 if (e.keyCode == SWT.CR) {
313                     String input = text.getText();
314                     setLookupInput(input);
315                 }
316             }
317         });
318
319         final Button button = new Button(parent, SWT.FLAT);
320         button.setText("&Lookup");
321         button.setEnabled(false);
322         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false).applyTo(button);
323
324         text.addKeyListener(new KeyAdapter() {
325             
326             @Override
327             public void keyPressed(KeyEvent e) {
328                 if (e.keyCode == 13) {
329                     String input = text.getText();
330                     setLookupInput(input);
331                     button.setFocus();
332                 }
333             }
334         });
335         
336         button.addSelectionListener(new SelectionListener() {
337             @Override
338             public void widgetDefaultSelected(SelectionEvent e) {
339                 widgetSelected(e);
340             }
341             @Override
342             public void widgetSelected(SelectionEvent e) {
343                 String input = text.getText();
344                 setLookupInput(input);
345             }
346         });
347         
348         text.addModifyListener(new ModifyListener() {
349             @Override
350             public void modifyText(ModifyEvent e) {
351                 String input = text.getText().trim();
352                 if (!input.equals(PROMPT_TEXT) && !input.equals(""))
353                     button.setEnabled(true);
354                 else
355                     button.setEnabled(false);
356             }
357         });
358     }
359
360     public void setLookupInput(String input) {
361         // There's no harm in trimming out spaces from both ends of the input.
362         input = input.trim();
363
364         SerialisationSupport support = session.getService(SerialisationSupport.class);
365         if (input.startsWith("$")) {
366             try {
367                 Resource r = support.getResource(Long.parseLong(input.substring(1)));
368                 changeLocation(r);
369             } catch (NumberFormatException e1) {
370                 // Ignore, may happen for crap input
371                 setStatus(DONT_TOUCH, "Invalid '$'-prefixed input, expected resource ID");
372             } catch (Exception e1) {
373                 ErrorLogger.defaultLogError(e1);
374                 setStatus(DONT_TOUCH, "Resource ID lookup failed. See Error Log.");
375             }
376             return;
377         }
378
379         String[] parts = input.split("-");
380
381         if (parts.length == 1) {
382             try {
383                 int resourceKey = Integer.parseInt(parts[0].trim());
384                 Resource r = support.getResource(resourceKey);
385                 // Some validation, not enough though
386                 ClusteringSupport cs = session.getService(ClusteringSupport.class);
387                 long cluster = cs.getCluster(r);
388                 if(cluster > 0) {
389                     changeLocation(r);
390                 }
391             } catch (NumberFormatException e1) {
392                 // Ignore, may happen for crap input
393                 setStatus(DONT_TOUCH, "Invalid input, expected transient resource ID");
394             } catch (Exception e1) {
395                 ErrorLogger.defaultLogError(e1);
396                 setStatus(DONT_TOUCH, "Transient resource ID lookup failed. See Error Log.");
397             }
398         } else if (parts.length == 2) {
399             try {
400                 int resourceIndex = Integer.parseInt(parts[1]);
401                 long clusterId = Long.parseLong(parts[0]);
402                 ClusteringSupport cs = session.getService(ClusteringSupport.class);
403                 Resource r = cs.getResourceByIndexAndCluster(resourceIndex, clusterId);
404                 changeLocation(r);
405             } catch (NumberFormatException e1) {
406                 // Ignore, may happen for crap input
407                 setStatus(DONT_TOUCH, "Invalid input, expected index & cluster IDs");
408             } catch (Exception e1) {
409                 ErrorLogger.defaultLogError(e1);
410                 setStatus(DONT_TOUCH, "Index & cluster -based lookup failed. See Error Log.");
411             }
412         }
413
414         // Try to see if the input data is an URI reference
415         try {
416             // First check that the input really is a proper URI.
417             String uri = input;
418             if (!input.equals("http:/") && input.endsWith("/"))
419                 uri = input.substring(0, input.length() - 1);
420             new URI(uri);
421             Resource r = session.syncRequest( Queries.resource( uri ) );
422             changeLocation(r);
423             return;
424         } catch (URISyntaxException e) {
425             // Ignore, this is not a proper URI at all.
426         } catch (ResourceNotFoundException e1) {
427             // Ok, this was an URI, but no resource was found.
428             setStatus(DONT_TOUCH, "Resource for URI '" + input + "' not found");
429             return;
430         } catch (DatabaseException e1) {
431             setStatus(DONT_TOUCH, "URI lookup failed. See Error Log.");
432             ErrorLogger.defaultLogError(e1);
433         }
434
435         setStatus(DONT_TOUCH, "Invalid input, resource ID or URI expected");
436     }
437
438     public Label createDropLabel(Composite parent) {
439         final Label label = new Label(parent, SWT.BORDER);
440         label.setAlignment(SWT.CENTER);
441         label.setText("Drag a resource here to examine it in this debugger!");
442         label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
443         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).hint(SWT.DEFAULT, 20).span(2, 1).grab(true, false).applyTo(label);
444
445         // Add resource id drop support to the drop-area.
446         DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY);
447         dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() });
448         dropTarget.addDropListener(new DropTargetAdapter() {
449             @Override
450             public void dragEnter(DropTargetEvent event) {
451                 event.detail = DND.DROP_LINK;
452                 label.setBackground((Color) resourceManager.get(green));
453                 return;
454             }
455             @Override
456             public void dragLeave(DropTargetEvent event) {
457                 label.setBackground(null);
458             }
459
460             @Override
461             public void drop(DropTargetEvent event) {
462                 label.setBackground(null);
463                 ResourceArray[] data = parseEventData(event);
464                 if (data == null || data.length != 1) {
465                     event.detail = DND.DROP_NONE;
466                     return;
467                 }
468                 final ResourceArray array = data[0];
469                 final Resource r = array.resources[array.resources.length - 1];
470
471                 changeLocation(r);
472             }
473
474             private ResourceArray[] parseEventData(DropTargetEvent event) {
475                 //System.out.println("DATA: " + event.data);
476                 if (event.data instanceof String) {
477                     try {
478                         SerialisationSupport support = session.getService(SerialisationSupport.class);
479                         return ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();
480                     } catch (IllegalArgumentException e) {
481                         ErrorLogger.defaultLogError(e);
482                     } catch (DatabaseException e) {
483                         ErrorLogger.defaultLogError(e);
484                     }
485                 }
486                 ResourceArray[] ret = ResourceAdaptionUtils.toResourceArrays(event.data);
487                 if (ret.length > 0)
488                     return ret;
489                 return null;
490             }
491         });
492
493         return label;
494     }
495
496     public Browser createBrowser(Composite parent) {
497         try {
498             browser = new Browser(parent, SWT.MOZILLA);
499         } catch (SWTError e) {
500             //System.out.println("Could not instantiate Browser: " + e.getMessage());
501             browser = new Browser(parent, SWT.NONE);
502         }
503         GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(browser);
504
505         // Left/right arrows for back/forward
506         browser.addKeyListener(new KeyAdapter() {
507 //              
508 //              @Override
509 //            public void keyPressed(KeyEvent e) {
510 //                      if (e.keyCode == SWT.F5) {
511 //                      refreshBrowser();
512 //                      }
513 //              }
514 //          }
515             @Override
516             public void keyReleased(KeyEvent e) {
517 //                System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")");
518                 if (e.keyCode == SWT.BS) {
519                     back();
520                 }
521                 
522                 if (e.keyCode == SWT.F5) {
523                         refreshBrowser();
524                 }
525                 
526                 if ((e.stateMask & SWT.ALT) != 0) {
527                     if (e.keyCode == SWT.ARROW_RIGHT)
528                         forward();
529                     if (e.keyCode == SWT.ARROW_LEFT)
530                         back();
531                 }
532             }
533         });
534
535         // Add listener for debugging functionality
536         browser.addLocationListener(new LocationAdapter() {
537             @Override
538             public void changing(LocationEvent event) {
539                 String location = event.location;
540                 if (location.startsWith("simantics:browser"))
541                     location = "about:" + location.substring(17);
542                 //System.out.println("changing: location=" + location);
543
544                 // Do not follow links that are meant as actions that are
545                 // handled below.
546                 event.doit = false;
547                 if ("about:blank".equals(location)) {
548                     // Just changing to the same old blank url is ok since it
549                     // allows the browser to refresh itself.
550                     event.doit = true;
551                 }
552
553                 if (location.startsWith("about:-link")) {
554                     String target = location.replace("about:-link", "");
555                     Resource element = links.getRight(target);
556                     if (element == currentElement) {
557                         event.doit = false;
558                         return;
559                     }
560                     changeLocation(element);
561                 } else if (location.startsWith("about:-remove")) {
562                     String target = location.replace("about:-remove", "");
563                     String n[] = target.split(STATEMENT_PART_SEPARATOR);
564                     if (n.length != 3)
565                         return;
566
567                     final Resource s = links.getRight(n[0]);
568                     final Resource p = links.getRight(n[1]);
569                     final Resource o = links.getRight(n[2]);
570
571                     // Make sure this is what the use wants.
572                     MessageDialog md = new MessageDialog(
573                             getShell(),
574                             "Confirm action...",
575                             null,
576                             "This action will remove the selected statement.\nAre you sure you want to proceed with this action?",
577                             MessageDialog.QUESTION, new String[] { "Cancel", "Continue" }, 0);
578                     if (md.open() != 1) {
579                         return;
580                     }
581
582                     session.asyncRequest(new WriteRequest() {
583
584                         @Override
585                         public void perform(WriteGraph g) throws DatabaseException {
586                                 try {
587                                         List<Resource> ls = OrderedSetUtils.toList(g, s);
588                                         if(ls.contains(o))
589                                                 OrderedSetUtils.remove(g, s, o);
590                                 } catch (DatabaseException e) {
591                                         
592                                 }
593                                 try {
594                                         List<Resource> ls = ListUtils.toList(g, s);
595                                         if(ls.contains(o))
596                                                 ListUtils.removeElement(g, s, o);
597                                 } catch (DatabaseException e) {
598                                         
599                                 }
600                             g.denyStatement(s, p, o);
601                         }
602
603                     }, parameter -> refreshBrowser()
604                     );
605                 } else if (location.startsWith("about:-edit-value")) {
606                     String target = location.replace("about:-edit-value", "");
607                     final Resource o = links.getRight(target);
608
609                     session.asyncRequest(new ReadRequest() {
610
611                         String previousValue;
612
613                         @Override
614                         public void run(ReadGraph graph) throws DatabaseException {
615
616                             previousValue = getResourceName(graph, o);
617                             final StringModifier modifier = graph.adapt(o, StringModifier.class);
618                             getDisplay().asyncExec(new Runnable() {
619                                 @Override
620                                 public void run() {
621                                     InputDialog dialog = new InputDialog(
622                                             getShell(),
623                                             "Edit Value",
624                                             null,
625                                             previousValue,
626                                             new IInputValidator() {
627                                                 @Override
628                                                 public String isValid(String newText) {
629                                                     return modifier.isValid(newText);
630                                                 }
631                                             }) {
632                                         private static final String DIALOG = "DebuggerEditValueDialog"; //$NON-NLS-1$
633                                         private IDialogSettings dialogBoundsSettings;
634                                         @Override
635                                         protected IDialogSettings getDialogBoundsSettings() {
636                                             if (dialogBoundsSettings == null) {
637                                                 IDialogSettings settings = Activator.getDefault().getDialogSettings();
638                                                 dialogBoundsSettings = settings.getSection(DIALOG);
639                                                 if (dialogBoundsSettings == null)
640                                                     dialogBoundsSettings = settings.addNewSection(DIALOG);
641                                             }
642                                             return dialogBoundsSettings;
643                                         }
644                                         @Override
645                                         protected Point getInitialSize() {
646                                             Point defaultSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
647                                             Point result = super.getInitialSize();
648                                             if (defaultSize.equals(result))
649                                                 return new Point(600, 400);
650                                             return result;
651                                         }
652                                         protected int getShellStyle() {
653                                             return super.getShellStyle() | SWT.RESIZE;
654                                         }
655                                         protected org.eclipse.swt.widgets.Control createDialogArea(Composite parent) {
656                                             Composite composite = (Composite) super.createDialogArea(parent);
657                                             getText().setLayoutData(
658                                                     new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL
659                                                             | GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL));
660                                             {
661                                                 Label label = new Label(composite, SWT.NONE);
662                                                 label.moveAbove(getText());
663                                                 label.setText("Input new property value. For numeric vector values, separate numbers with comma (',').");
664                                                 GridData data = new GridData(GridData.GRAB_HORIZONTAL
665                                                         | GridData.HORIZONTAL_ALIGN_FILL
666                                                         | GridData.VERTICAL_ALIGN_CENTER);
667                                                 data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
668                                                 label.setLayoutData(data);
669                                                 label.setFont(parent.getFont());
670                                             }
671                                             return composite;
672                                         }
673                                         protected int getInputTextStyle() {
674                                             return SWT.MULTI | SWT.BORDER;
675                                         }
676                                     };
677                                     int ok = dialog.open();
678                                     if (ok != Dialog.OK)
679                                         return;
680
681                                     final String value = dialog.getValue();
682                                     session.asyncRequest(new WriteRequest() {
683                                         @Override
684                                         public void perform(WriteGraph g) throws DatabaseException {
685                                             //modifier.modify( g, htmlEscape( value ) );
686                                             modifier.modify( g, value );
687                                         }
688                                     }, parameter -> {
689                                         if (parameter != null)
690                                             ErrorLogger.defaultLogError(parameter);
691                                         refreshBrowser();
692                                     });
693                                     return;
694                                 }
695                             });
696                         }
697
698                     }, new ProcedureAdapter<Object>() {
699                         @Override
700                         public void exception(Throwable t) {
701                             ErrorLogger.defaultLogError(t);
702                         }
703                     });
704
705                 }
706             }
707         });
708
709         // Schedule a request that updates the browser content.
710         refreshBrowser();
711         GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);
712         support.removeListener(changeListener);
713
714         return browser;
715     }
716
717     public void refreshBrowser() {
718         if (currentElement == null)
719             return;
720
721         // Schedule a request that updates the browser content.
722         updater.asyncRequest(new ReadRequest() {
723
724             @Override
725             public void run(ReadGraph graph) throws DatabaseException {
726                 updateContent(graph, currentElement);
727             }
728
729         });
730
731     }
732
733     public Resource getDebuggerLocation() {
734         return currentElement;
735     }
736
737     public void changeLocation(Resource element) {
738         if (currentElement != null) {
739             backHistory.addLast(currentElement);
740         }
741         currentElement = element;
742         forwardHistory.clear();
743
744         refreshBrowser();
745         setStatus(DONT_TOUCH, null);
746         fireHistoryChanged();
747     }
748
749     public void addHistoryListener(HistoryListener l) {
750         historyListeners.add(l);
751     }
752
753     public void removeHistoryListener(HistoryListener l) {
754         historyListeners.remove(l);
755     }
756
757     private void fireHistoryChanged() {
758         for (HistoryListener l : historyListeners)
759             l.historyChanged();
760     }
761
762     public boolean hasBackHistory() {
763         return backHistory.isEmpty();
764     }
765
766     public boolean hasForwardHistory() {
767         return forwardHistory.isEmpty();
768     }
769
770     public void back() {
771         if (backHistory.isEmpty())
772             return;
773
774         forwardHistory.addFirst(currentElement);
775         currentElement = backHistory.removeLast();
776
777         refreshBrowser();
778         fireHistoryChanged();
779     }
780
781     public void forward() {
782         if (forwardHistory.isEmpty())
783             return;
784
785         backHistory.addLast(currentElement);
786         currentElement = forwardHistory.removeFirst();
787
788         refreshBrowser();
789         fireHistoryChanged();
790     }
791
792     protected String toName(Object o) {
793         Class<?> clazz = o.getClass();
794         if (clazz.isArray()) {
795             int length = Array.getLength(o);
796             if (length > RESOURCE_NAME_MAX_LENGTH) {
797                 if (o instanceof byte[]) {
798                     byte[] arr = (byte[]) o;
799                     byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
800                     return truncated("byte", Arrays.toString(arr2), arr.length);
801                 } else if (o instanceof int[]) {
802                     int[] arr = (int[]) o;
803                     int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
804                     return truncated("int", Arrays.toString(arr2), arr.length);
805                 } else if (o instanceof long[]) {
806                     long[] arr = (long[]) o;
807                     long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
808                     return truncated("long", Arrays.toString(arr2), arr.length);
809                 } else if (o instanceof float[]) {
810                     float[] arr = (float[]) o;
811                     float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
812                     return truncated("float", Arrays.toString(arr2), arr.length);
813                 } else if (o instanceof double[]) {
814                     double[] arr = (double[]) o;
815                     double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
816                     return truncated("double", Arrays.toString(arr2), arr.length);
817                 } else if (o instanceof boolean[]) {
818                     boolean[] arr = (boolean[]) o;
819                     boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
820                     return truncated("boolean", Arrays.toString(arr2), arr.length);
821                 } else if (o instanceof Object[]) {
822                     Object[] arr = (Object[]) o;
823                     Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
824                     return truncated("Object", Arrays.toString(arr2), arr.length);
825                 } else {
826                     return "Unknown big array " + o.getClass();
827                 }
828             } else {
829                 return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o);
830             }
831         }
832         return null;
833     }
834
835     protected String truncated(String type, String string, int originalLength) {
836         return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string;
837     }
838     
839     public static String htmlEscape(String s)
840     {
841         return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br/>");
842     }
843
844     /**
845      * Get resource name(?) 
846      * 
847      * @param graph
848      * @param r
849      * @return
850      */
851     protected String getResourceName(ReadGraph graph, Resource r) {
852         try {
853
854             String name = null;
855             //System.out.println("hasValue(" + NameUtils.getSafeName(graph, r, true));
856             if (graph.hasValue(r)) {
857                 // too large array may cause application to run out of memory.
858                 //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));
859                 Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));
860                 if (type!=null) {
861                         Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );
862                         if (type.equals( rviBinding.type() )) {
863                                 RVI rvi = graph.getValue(r, rviBinding);
864                                                                 
865                                 try {
866                                         Variable v = Variables.getConfigurationContext( graph, currentElement );
867                                         name = rvi.asString(graph, v);
868 //                                      name = rvi.resolve(graph, v).getURI(graph);
869                                 } catch (DatabaseException dbe ) {
870                                         name = rvi.toString( graph );
871                                 }
872                         } else {
873                                 long valueSize = NameUtils.getPossibleValueSize(graph, r);
874                                 if (valueSize > RESOURCE_NAME_MAX_LENGTH) {
875 //                                      Binding b = Bindings.getBinding(type);
876 //                                      Object v = graph.getValue(r, b);
877 //                                      Serializer s = Bindings.getSerializerUnchecked(b);
878 //                                      int size = s.getSize(v);
879                                     name = "Approx. " + valueSize + " byte literal of type " + type.toSingleLineString();
880                                 } else {                                        
881                                         Binding b = Bindings.getBinding(type);
882                                         Object v = graph.getValue(r, b);
883                                         if (b.type() instanceof StringType) {
884                                                 name = (String) graph.getValue(r, b);
885                                         } else {
886                                             name = b.toString(v, false);
887                                         }
888                                     if (type instanceof ArrayType){
889                                         name = name.substring(1, name.length()-1);
890                                     }
891                                 }
892                         }
893                 } else {
894                     Object o = graph.getValue(r);
895                     name = toName(o);
896                 }
897                 
898                 if(name.isEmpty()) {
899                         name = "<empty value>";
900                 }
901                 
902             }
903             // Does resource have a file ??
904             if (name == null) {
905 //                try {
906 //                    Accessor accessor = graph.getAccessor(r);
907 //                    name = "File of type " + accessor.type().toSingleLineString();
908 //                } catch (DatabaseException e) {
909 //                    // No file, try next alternative.
910 //                }
911             }
912             if (name == null) {
913                 //name = graph.adapt(r, String.class);
914                 //if(name.isEmpty())
915                 name = DebugUtils.getSafeLabel(graph, r);
916                 if (name.isEmpty())
917                     name = "<empty name>";
918             }
919 //            ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);
920 //            if(name == null)
921 //                return "[" + r.getResourceId() + " - " + support.getCluster(r) + "]";
922             if(displayClusters) {
923 //                SessionDebug debug = graph.getSession().getDebug();
924 //                name += " (" + debug.getCluster(r) + ")";
925             }
926             return name;
927         } catch (AdaptionException e) {
928 //            e.printStackTrace();
929             String name = safeReadableString(graph, r);
930 //          try {
931 //                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_adaption_problem, MessageUtil.resource(session, r, "this resource")), e));
932 //            } catch (ReferenceSerializationException e1) {
933 //                e1.printStackTrace();
934 //                ErrorLogger.defaultLogWarning(e1);
935 //            }
936             return name;
937         } catch (Exception e) {
938             ErrorLogger.defaultLogError(e);
939             String name = safeReadableString(graph, r);
940 //          try {
941 //                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_formulation_problem, MessageUtil.resource(session, r, "this resource")), e));
942 //            } catch (ReferenceSerializationException e1) {
943 //                e1.printStackTrace();
944 //                ErrorLogger.defaultLogWarning(e1);
945 //            }
946             return name;
947         }
948     }
949
950     private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {
951         String name;
952         try {
953             Layer0 L0 = Layer0.getInstance(graph);
954             if (graph.isInstanceOf(r, L0.Assertion)) {
955                 Resource pred = graph.getPossibleObject(r, L0.HasPredicate);
956                 // Don't know how I encountered this but it seems to be possible in some cases..
957                 // Resource obj = graph.getSingleObject(r, L0.HasObject);
958                 Resource obj = graph.getPossibleObject(r, L0.HasObject);
959                 String tmp = htmlEscape( (pred == null ? "No predicate ?" : getResourceName(graph, pred)) + " -> " + (obj == null ? "No object ?" : getResourceName(graph, obj)) + " (Assertion)" );
960                 name = tmp.substring(0, Math.min(80, tmp.length()));
961             } else {
962                 String resourceName = getResourceName(graph, r);
963                 if(resourceName.equals("Inverse")) {
964                     Resource inverse = graph.getPossibleInverse(r);
965                     if(inverse != null && graph.hasStatement(inverse, L0.ConsistsOf, r))
966                         resourceName = getResourceName(graph, inverse) + "/Inverse";
967                 }
968                 String tmp = htmlEscape( resourceName );
969                 name = tmp.substring(0, Math.min(80, tmp.length()));
970             }
971             
972         } catch (OutOfMemoryError e) {
973             name = "OutOfMemoryError";
974         }
975         String ret = "<a href=\"simantics:browser-link" + getLinkString(r) + "\">"
976         + name
977         + "</a>";
978         if (graph.isInstanceOf(r, L0.Literal)) {
979             ret += "&nbsp;<a class=\"edit-link\" href=\"simantics:browser-edit-value" + getLinkString(r) + "\">"
980             + "(edit)"
981             + "</a>";
982         }
983         return ret;
984     }
985
986     private String getStatementRemoveRef(Resource s, Resource p, Resource o) {
987         return "<a href=\"simantics:browser-remove" + getStatementString(s, p, o)
988         + "\" title=\"Remove this statement\">X</a>";
989     }
990
991     private void updatePred(StringBuffer content, ReadGraph graph, Resource subj, Resource pred, List<Resource[]> stats) throws DatabaseException {
992         // Generate output content from statements
993         String[][] objects = new String[stats.size()][];
994         for (int i = 0; i < stats.size(); ++i) {
995             Resource stmSubject = stats.get(i)[0];
996             Resource object = stats.get(i)[1];
997
998             objects[i] = new String[4];
999             objects[i][0] = getLinkString(object);
1000             objects[i][1] = htmlEscape( getResourceName(graph, object) );
1001             objects[i][2] = getResourceRef(graph, object);
1002
1003             // Make a note if the statement was acquired.
1004             if(!stmSubject.equals(subj)) {
1005                 objects[i][3] = " (in " + getResourceRef(graph, stmSubject) + ")";
1006             }
1007         }
1008
1009         // Sort statements by object name
1010         Arrays.sort(objects, new Comparator<String[]>() {
1011             @Override
1012             public int compare(String[] o1, String[] o2) {
1013                 return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1[1], o2[1]);
1014             }
1015         });
1016
1017         // Output table rows
1018         for (int i = 0; i < objects.length; ++i) {
1019             content.append("<tr>");
1020             // Predicate column
1021             if (i == 0)
1022                 content.append("<td rowspan=\"").append(objects.length).append("\" valign=\"top\">").append(getResourceRef(graph, pred)).append("</td>");
1023
1024             // Object column
1025             if (objects[i][3] == null) content.append("<td>");
1026             else content.append("<td class=\"acquired\">");
1027
1028             content.append(objects[i][2]);
1029             if (objects[i][3] != null)
1030                 content.append(objects[i][3]);
1031
1032             content.append("</td>");
1033             
1034             VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class);
1035             VirtualGraph vg = vgs.getGraph(graph, subj, pred, links.getRight(objects[i][0]));
1036             
1037             if(vg != null) {
1038                 content.append("<td>").append(vg.toString()).append("</td>");
1039             } else {
1040                 content.append("<td>DB</td>");
1041             }
1042             
1043
1044             // Statement remove -link column
1045             // Only allowed for non-acquired statements.
1046             if (objects[i][3] == null) {
1047                 content.append("<td class=\"remove\">");
1048                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));
1049                 content.append("</td>");
1050             }
1051             content.append("</tr>");
1052         }
1053     }
1054
1055     private void updateTag(StringBuffer content, ReadGraph graph, Resource subj, Resource tag) throws DatabaseException {
1056
1057         // Generate output content from statements
1058         String ref = getResourceRef(graph, tag);
1059
1060         content.append("<tr>");
1061         content.append("<td rowspan=\"1\" colspan=\"3\" valign=\"top\">").append(ref).append("</td>");
1062         //content.append("<td>" + name + "</td>");
1063         content.append("<td class=\"remove\">");
1064         content.append(getStatementRemoveRef(subj, tag, subj));
1065         content.append("</td>");
1066         content.append("</tr>");
1067
1068     }
1069
1070     private void updateOrderedSet(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {
1071         //List<Resource> list = OrderedSetUtils.toList(graph, subj);
1072         /*
1073         // Generate output content from statements
1074         String[][] objects = new String[stats.size()][];
1075         for (int i = 0; i < stats.size(); ++i) {
1076             Resource stmSubject = stats.get(i)[0];
1077             Resource object = stats.get(i)[1];
1078
1079             objects[i] = new String[4];
1080             objects[i][0] = getLinkString(object);
1081             objects[i][1] = getResourceName(graph, object);
1082             objects[i][2] = getResourceRef(graph, object);
1083
1084             // Make a note if the statement was acquired.
1085             if(!stmSubject.equals(subj)) {
1086                 objects[i][3] = " (acquired from " + getResourceRef(graph, stmSubject) + ")";
1087             }
1088         }
1089
1090         // Sort statements by object name
1091         Arrays.sort(objects, new Comparator<String[]>() {
1092             @Override
1093             public int compare(String[] o1, String[] o2) {
1094                 return o1[1].compareTo(o2[1]);
1095             }
1096         });*/
1097
1098         List<String> list = new ArrayList<String>();
1099         Resource cur = subj;
1100         while(true) {
1101             try {
1102                 cur = OrderedSetUtils.next(graph, subj, cur);
1103             } catch(DatabaseException e) {
1104                 list.add("<span style=\"color:red;font-weight:bold\">BROKEN ORDERED SET:<br/></span><span style=\"color:red\">" + e.getMessage() + "</span>");
1105                 Resource inv = graph.getPossibleInverse(subj);
1106                 for(Statement stat : graph.getStatements(cur, L0.IsRelatedTo)) {
1107                     if(stat.getSubject().equals(cur)) {
1108                         if(stat.getPredicate().equals(subj)) {
1109                             list.add("next " + getResourceRef(graph, stat.getObject()));
1110                         }
1111                         else if(stat.getPredicate().equals(inv)) {
1112                             list.add("prev " + getResourceRef(graph, stat.getObject()));
1113                         }
1114                     }
1115                 }
1116                 break;
1117             }
1118             if(cur.equals(subj))
1119                 break;
1120             list.add(getResourceRef(graph, cur));
1121         }
1122
1123         // Output table rows
1124         for (int i = 0; i < list.size() ; ++i) {
1125             content.append("<tr>");
1126             // Predicate column
1127             if (i == 0)
1128                 content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Ordered Set Elements</td>");
1129
1130             // Object column
1131             content.append("<td>");
1132             content.append(list.get(i));
1133             content.append("</td>");
1134
1135             // Statement remove -link column
1136             // Only allowed for non-acquired statements.
1137             /*if (objects[i][3] == null) {
1138                 content.append("<td class=\"remove\">");
1139                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));
1140                 content.append("</td>");
1141             }*/
1142             content.append("</tr>");
1143         }
1144     }
1145
1146     private void updateLinkedList(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {
1147
1148         List<String> list = new ArrayList<String>();
1149         
1150         try {
1151                 List<Resource> resources = ListUtils.toList(graph, subj);
1152                 for(Resource element : resources) {
1153                     list.add(getResourceRef(graph, element));
1154                 }
1155         } catch (DatabaseException e) {
1156                 throw new ValidationException(e);
1157         }
1158
1159         // Output table rows
1160         for (int i = 0; i < list.size() ; ++i) {
1161             content.append("<tr>");
1162             // Predicate column
1163             if (i == 0)
1164                 content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Linked List Elements</td>");
1165
1166             // Object column
1167             content.append("<td>");
1168             content.append(list.get(i));
1169             content.append("</td><td>DB</td>");
1170
1171             // Statement remove -link column
1172             // Only allowed for non-acquired statements.
1173             /*if (objects[i][3] == null) {
1174                 content.append("<td class=\"remove\">");
1175                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));
1176                 content.append("</td>");
1177             }*/
1178             content.append("</tr>");
1179         }
1180     }
1181
1182     protected synchronized void updateContent(final ReadGraph graph, Resource... resources) throws DatabaseException {
1183         L0 = Layer0.getInstance(graph);
1184
1185         links.clear();
1186         StringBuffer content = new StringBuffer();
1187
1188         // Generate HTML -page
1189         content.append("<html>\n<head>\n")
1190         .append(getHead())
1191         .append("\n</head>\n")
1192         .append("<body>\n")
1193         .append("<div id=\"mainContent\">\n\n");
1194
1195         for (Resource r : resources) {
1196             if (r == null)
1197                 continue;
1198
1199             String uri = null;
1200             try {
1201                 uri = graph.syncRequest(new ResourceToPossibleURI(r));
1202             } catch (Exception e) {
1203                 ErrorLogger.defaultLogError(e);
1204                 uri = "Cannot get URI: " + e.getMessage();
1205             }
1206
1207             // Top DIV
1208             content.append("<div id=\"top\">\n");
1209             content.append("<table class=\"top\">\n");
1210             if (uri != null) {
1211                 content.append("<tr><td class=\"top_key\">URI</td><td class=\"top_value\"><span id=\"uri\">").append(uri).append("</span></td></tr>\n");
1212             }
1213
1214             XSupport xs = graph.getService(XSupport.class);
1215             boolean immutable = xs.getImmutable(r);
1216
1217             Collection<Statement> statements = graph.getStatements(r, L0.IsWeaklyRelatedTo);
1218             HashMultiMap<Resource, Resource[]> map = new HashMultiMap<Resource, Resource[]>();
1219             for(org.simantics.db.Statement statement : statements) {
1220                 Resource predicate = null;
1221                 Resource subject = null;
1222                 Resource obj = null;
1223                 try {
1224                     predicate = statement.getPredicate();
1225                     subject = statement.getSubject();
1226                     obj = statement.getObject();
1227                     map.add(predicate, new Resource[] {subject, obj});
1228                 } catch (Throwable e) {
1229                     ErrorLogger.defaultLogError("Cannot find statement " + subject + " " + predicate + " " + obj, e);
1230                 }
1231             }
1232             SerialisationSupport ss = graph.getSession().getService(SerialisationSupport.class);
1233             ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);
1234             content.append("<tr><td class=\"top_key\">Identifiers</td><td class=\"top_value\">");
1235             content.append("<span id=\"resource_id\">")
1236             .append(" RID = $").append(r.getResourceId())
1237             .append(" Resource Key = ").append(ss.getTransientId(r))
1238             .append(" CID = ").append(support.getCluster(r));
1239             content.append("</span></td>");
1240             if (immutable)
1241                 content.append("<td class=\"remove\">[IMMUTABLE]</td>");
1242             content.append("</tr>\n");
1243  
1244             boolean isClusterSet = support.isClusterSet(r);
1245             Resource parentSet = support.getClusterSetOfCluster(r);
1246             String parentSetURI = parentSet != null ? graph.getPossibleURI(parentSet) : null;
1247             
1248             content.append("<tr><td class=\"top_key\">Clustering</td><td class=\"top_value\">");
1249             content.append("<span id=\"resource_id\">");
1250             
1251             if(parentSetURI != null)
1252                 content.append(" Containing cluster set = ").append(parentSetURI);
1253             else if (parentSet != null)
1254                 content.append(" Containing cluster set = ").append(parentSet.toString());
1255             else 
1256                 content.append(" Not in any cluster set ");
1257             
1258             content.append("</span></td>");
1259             if (isClusterSet)
1260                 content.append("<td class=\"remove\">[CLUSTER SET]</td>");
1261             content.append("</tr>\n");
1262             
1263             // If the resource has a value, show it.
1264             String resourceValue = getResourceValue(graph, r);
1265             if (resourceValue != null) {
1266                 content
1267                 .append("<tr><td class=\"top_key\">Attached value</td><td class=\"top_value\">")
1268                 .append(htmlEscape(resourceValue))
1269                 .append("</td></tr>\n");
1270             }
1271
1272             // Close #top
1273             content.append("</table>\n");
1274             content.append("</div>\n");
1275
1276             content.append("\n<div id=\"data\">\n");
1277             content.append("<table>\n")
1278             .append("<tr><th>Predicate</th><th>Object</th><th>Graph</th></tr>")
1279             .append("<tr><td class=\"subtitle\" colspan=\"3\">Basic information</td></tr>");
1280
1281             boolean isOrderedSet = graph.isInstanceOf(r, L0.OrderedSet);
1282             boolean isLinkedList = graph.isInstanceOf(r, L0.List);
1283 //            map.remove(r);
1284
1285             // BASIC INFORMATION:
1286             for (Resource pred :
1287                 new Resource[] {L0.HasName, L0.InstanceOf,
1288                     L0.Inherits, L0.SubrelationOf,
1289                     L0.PartOf, L0.ConsistsOf})
1290                 if (map.containsKey(pred))
1291                     updatePred(content, graph, r, pred, map.remove(pred));
1292
1293             // TAGS
1294             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Tags</td></tr>");
1295             for(Statement stm : statements) {
1296                 if(stm.getSubject().equals(stm.getObject())) {
1297                     updateTag(content, graph, r, stm.getPredicate());
1298                     map.remove(stm.getPredicate());
1299                 }
1300             }
1301
1302             // ORDERED SETS
1303             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Ordered Sets</td></tr>");
1304             for(Statement stm : statements) {
1305                 Resource predicate = stm.getPredicate();
1306                 if(graph.isInstanceOf(stm.getPredicate(), L0.OrderedSet)) {
1307                     updateTag(content, graph, r, stm.getPredicate());
1308                     if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)
1309                         map.remove(stm.getPredicate());
1310                 }
1311                 Resource inverse = graph.getPossibleInverse(predicate);
1312                 if (inverse != null) {
1313                     if(graph.isInstanceOf(inverse, L0.OrderedSet)) {
1314                         if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)
1315                             map.remove(stm.getPredicate());
1316                     }
1317                 } else {
1318                     // FIXME : should we infor missing inverse
1319                 }
1320             }
1321
1322             // IS RELATED TO
1323             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Is Related To</td></tr>");
1324
1325             // ELEMENTS OF ORDERED SET
1326             if(isOrderedSet) {
1327                 //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");
1328                 try {
1329                     updateOrderedSet(content, graph, r);
1330                 } catch (ValidationException e) {
1331                     content.append("<td colspan=\"3\"><span style=\"color:red;font-weight:bold\">BROKEN ORDERED SET:<br/></span><span style=\"color:red\">").append(e.getMessage()).append("</span></td>");
1332                 }
1333             }
1334
1335             // ELEMENTS OF LINKED LIST
1336             if(isLinkedList) {
1337                 //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");
1338                 try {
1339                     updateLinkedList(content, graph, r);
1340                 } catch (ValidationException e) {
1341                     content.append("<td colspan=\"3\"><span style=\"color:red;font-weight:bold\">BROKEN LINKED LIST:<br/></span><span style=\"color:red\">").append(e.getMessage()).append("</span></td>");
1342                 }
1343             }
1344
1345             // IS RELATED TO (other)
1346             Resource[] preds = map.keySet().toArray(new Resource[0]);
1347             final Map<Resource, String> strmap = new HashMap<Resource, String>(preds.length);
1348             for(Resource pred : preds) {
1349                 String str = htmlEscape( getResourceName(graph, pred) );
1350                 if(str == null)
1351                     str = "<null>";
1352                 strmap.put(pred, str);
1353             }
1354             Arrays.sort(preds, new Comparator<Resource>() {
1355                 @Override
1356                 public int compare(Resource o1, Resource o2) {
1357                     return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(strmap.get(o1), strmap.get(o2));
1358                 }
1359             });
1360             for(Resource pred : preds)
1361                 if(graph.isSubrelationOf(pred, L0.IsRelatedTo))
1362                     updatePred(content, graph, r, pred, map.get(pred));
1363
1364             // OTHER STATEMENTS
1365             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Other statements</td></tr>");
1366             for(Resource pred : preds)
1367                 if(!graph.isSubrelationOf(pred, L0.IsRelatedTo))
1368                     updatePred(content, graph, r, pred, map.get(pred));
1369             content.append("</table>\n");
1370         }
1371         // Close #data
1372         content.append("</div>\n\n");
1373         // Close #mainContent
1374         content.append("</div>\n");
1375         content.append("</body>\n</html>\n");
1376
1377         // Update content
1378         final String finalContent = content.toString();
1379         if (!isDisposed()) {
1380             getDisplay().asyncExec(new Runnable() {
1381                 @Override
1382                 public void run() {
1383                     if (!browser.isDisposed())
1384                         browser.setText(finalContent);
1385                 }
1386             });
1387         }
1388     }
1389
1390     private String getResourceValue(ReadGraph graph, Resource r) {
1391         try {
1392             if (graph.hasValue(r)) {
1393                 // too large array may cause application to run out of memory.
1394                 //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));
1395                 Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));
1396                 if (type != null) {
1397                     Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );
1398                     if (type.equals( rviBinding.type() )) {
1399                         RVI rvi = graph.getValue(r, rviBinding);
1400                         try {
1401                             Variable v = Variables.getConfigurationContext( graph, r );
1402                             return rvi.asString(graph, v);
1403                         } catch (DatabaseException dbe ) {
1404                             return rvi.toString( graph );
1405                         }
1406                     } else {
1407                         Binding b = Bindings.getBinding(type);
1408                         Object v = graph.getValue(r, b);
1409                         Serializer s = Bindings.getSerializerUnchecked(b);
1410                         int size = s.getSize(v);
1411                         if (size > RESOURCE_VALUE_MAX_SIZE) {
1412                             return "Approx. " + size + " byte literal of type " + type.toSingleLineString();
1413                         } else {
1414                             return b.toString(v, false);
1415                         }
1416                     }
1417                 } else {
1418                     Object o = graph.getValue(r);
1419                     return toName(o);
1420                 }
1421             }
1422             return null;
1423         } catch (DatabaseException e) {
1424             return e.getMessage();
1425         } catch (IOException e) {
1426             return e.getMessage();
1427         } catch (BindingException e) {
1428             return e.getMessage();
1429         }
1430     }
1431
1432     private static String safeReadableString(ReadGraph g, Resource r) {
1433         try {
1434             return NameUtils.getSafeName(g, r);
1435         } catch(Throwable throwable) {
1436             ErrorLogger.defaultLogError(throwable);
1437             return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";
1438         }
1439     }
1440
1441 //    private static String safeReadableString(IEntity t) {
1442 //        try {
1443 //            return ResourceDebugUtils.getReadableNameForEntity(t);
1444 //        } catch(Throwable throwable) {
1445 //            throwable.printStackTrace();
1446 //            return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";
1447 //        }
1448 //    }
1449
1450     private String getLinkString(Container<Resource> t) {
1451         String link = links.getLeft(t.get());
1452         if(link == null) {
1453             link = UUID.randomUUID().toString();
1454             links.map(link, t.get());
1455         }
1456         return link;
1457     }
1458
1459 //    private String getPropertyEditString(Container<Resource> t) {
1460 //        String link = links.getLeft(t.get());
1461 //        if(link == null) {
1462 //            link = UUID.randomUUID().toString();
1463 //            links.map(link, t.get());
1464 //        }
1465 //        return link;
1466 //    }
1467
1468     private String getStatementString(Resource _s, Resource _p, Resource _o) {
1469         String s = getLinkString(_s);
1470         String p = getLinkString(_p);
1471         String o = getLinkString(_o);
1472         return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;
1473     }
1474
1475 //    private String getStatementString(Statement stm) {
1476 //        String s = getLinkString(stm.getSubject());
1477 //        String p = getLinkString(stm.getPredicate());
1478 //        String o = getLinkString(stm.getObject());
1479 //        return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;
1480 //    }
1481
1482 //    private String toOutgoingTableRow(IEntity subject, Statement stm) {
1483 //        boolean isAcquired = !subject.equals(stm.getSubject());
1484 //
1485 //        String plainCell = "%s";
1486 //        String hrefCell = "<a href=\"%s\">%s</a>";
1487 //        String formatTemplate = isAcquired
1488 //                ? "<tr>\n<td class=\"acquired\">{0}</td>\n<td class=\"acquired\">{1}</td>\n<td class=\"acquired\">{1}</td>\n<td class=\"acquired\">{1}</td>\n</tr>"
1489 //                : "<tr>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n</tr>";
1490 //        String format = NLS.bind(formatTemplate, plainCell, hrefCell);
1491 ////        System.out.println("format: " + format);
1492 //
1493 //        IEntity s = stm.getSubject();
1494 //        IEntity p = stm.getPredicate();
1495 //        IEntity o = stm.getObject();
1496 //
1497 ////        String timePart = "[" + timeToString(t.getBegin()) + ", " + timeToString(t.getEnd()) + "]";
1498 //
1499 //        try {
1500 //            return !isAcquired
1501 //                ? String.format(format,
1502 //                    "about:blank-remove" + getStatementString(stm), "Remove",
1503 //                    "about:blank-link" + getLinkString(s), safeReadableString(s),
1504 //                    "about:blank-link" + getLinkString(p), safeReadableString(p),
1505 //                    "about:blank-link" + getLinkString(o), safeReadableString(o))
1506 //                : String.format(format,
1507 //                    "Acquired",
1508 //                    "about:blank-link" + getLinkString(s), safeReadableString(s),
1509 //                    "about:blank-link" + getLinkString(p), safeReadableString(p),
1510 //                    "about:blank-link" + getLinkString(o), safeReadableString(o)
1511 //            );
1512 //        } catch (Throwable throwable) {
1513 //            return "<tr><td colspan=\"4\"><font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font></td></tr>";
1514 //        }
1515 //    }
1516
1517 //    private String intervalToString(Interval time) {
1518 //        return timeToString(time.getBegin()) + ", " + timeToString(time.getEnd());
1519 //    }
1520 //
1521 //    DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
1522 //
1523 //    private String timeToString(long time) {
1524 //        if (time == Long.MIN_VALUE)
1525 //            return "-&infin;";
1526 //        if (time == Long.MAX_VALUE)
1527 //            return "+&infin;";
1528 //        //return String.valueOf(time);
1529 //        Date d = new Date(time);
1530 //        return dateTimeFormat.format(d);
1531 //    }
1532
1533     private String getHead() {
1534         String result = "";
1535         if (cssPath != null) {
1536             result = "<link href=\"" + cssPath + "\" rel=\"stylesheet\" type=\"text/css\">";
1537         }
1538         return result;
1539     }
1540
1541 }