]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/GraphDebugger.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / GraphDebugger.java
diff --git a/bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/GraphDebugger.java b/bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/GraphDebugger.java
new file mode 100644 (file)
index 0000000..ca2fa21
--- /dev/null
@@ -0,0 +1,1549 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.debug.ui;\r
+\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import java.lang.reflect.Array;\r
+import java.net.URI;\r
+import java.net.URISyntaxException;\r
+import java.net.URL;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.UUID;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.FileLocator;\r
+import org.eclipse.core.runtime.IPath;\r
+import org.eclipse.core.runtime.Path;\r
+import org.eclipse.jface.action.IStatusLineManager;\r
+import org.eclipse.jface.dialogs.Dialog;\r
+import org.eclipse.jface.dialogs.IDialogConstants;\r
+import org.eclipse.jface.dialogs.IDialogSettings;\r
+import org.eclipse.jface.dialogs.IInputValidator;\r
+import org.eclipse.jface.dialogs.InputDialog;\r
+import org.eclipse.jface.dialogs.MessageDialog;\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.SWTError;\r
+import org.eclipse.swt.browser.Browser;\r
+import org.eclipse.swt.browser.LocationAdapter;\r
+import org.eclipse.swt.browser.LocationEvent;\r
+import org.eclipse.swt.dnd.DND;\r
+import org.eclipse.swt.dnd.DropTarget;\r
+import org.eclipse.swt.dnd.DropTargetAdapter;\r
+import org.eclipse.swt.dnd.DropTargetEvent;\r
+import org.eclipse.swt.dnd.TextTransfer;\r
+import org.eclipse.swt.dnd.Transfer;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.FocusListener;\r
+import org.eclipse.swt.events.KeyAdapter;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.ModifyEvent;\r
+import org.eclipse.swt.events.ModifyListener;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.RGB;\r
+import org.eclipse.swt.layout.GridData;\r
+import org.eclipse.swt.layout.GridLayout;\r
+import org.eclipse.swt.widgets.Button;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Label;\r
+import org.eclipse.swt.widgets.Text;\r
+import org.eclipse.ui.IWorkbenchSite;\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.Databoard;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.type.ArrayType;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.type.StringType;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+import org.simantics.db.AsyncRequestProcessor;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.VirtualGraph;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.ResourceArray;\r
+import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
+import org.simantics.db.common.request.Queries;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.common.uri.ResourceToPossibleURI;\r
+import org.simantics.db.common.utils.ListUtils;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.common.utils.OrderedSetUtils;\r
+import org.simantics.db.event.ChangeEvent;\r
+import org.simantics.db.event.ChangeListener;\r
+import org.simantics.db.exception.AdaptionException;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.ResourceNotFoundException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.db.layer0.adapter.StringModifier;\r
+import org.simantics.db.layer0.variable.RVI;\r
+import org.simantics.db.layer0.variable.Variable;\r
+import org.simantics.db.layer0.variable.Variables;\r
+import org.simantics.db.service.ClusteringSupport;\r
+import org.simantics.db.service.GraphChangeListenerSupport;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.db.service.VirtualGraphSupport;\r
+import org.simantics.db.service.XSupport;\r
+import org.simantics.debug.ui.internal.Activator;\r
+import org.simantics.debug.ui.internal.DebugUtils;\r
+import org.simantics.debug.ui.internal.HashMultiMap;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.ui.dnd.LocalObjectTransfer;\r
+import org.simantics.ui.dnd.ResourceReferenceTransfer;\r
+import org.simantics.ui.dnd.ResourceTransferUtils;\r
+import org.simantics.ui.utils.ResourceAdaptionUtils;\r
+import org.simantics.utils.Container;\r
+import org.simantics.utils.FileUtils;\r
+import org.simantics.utils.datastructures.BijectionMap;\r
+import org.simantics.utils.datastructures.Callback;\r
+import org.simantics.utils.strings.AlphanumComparator;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+import org.simantics.utils.ui.PathUtils;\r
+import org.simantics.utils.ui.workbench.WorkbenchUtils;\r
+\r
+\r
+public class GraphDebugger extends Composite {\r
+\r
+    public interface HistoryListener {\r
+        void historyChanged();\r
+    }\r
+\r
+    private static final String                         STATEMENT_PART_SEPARATOR  = ",";\r
+    private final static String                         DEFAULT_DEBUGGER_CSS_FILE = "debugger.css";\r
+    private final static String                         DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE;\r
+\r
+    private static int                                  RESOURCE_NAME_MAX_LENGTH  = 1000;\r
+    private static int                                  RESOURCE_VALUE_MAX_SIZE = 16384;\r
+\r
+    private final LocalResourceManager                  resourceManager;\r
+\r
+    private String                                      cssPath;\r
+\r
+    private Browser                                     browser;\r
+    private final ColorDescriptor                       green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));\r
+\r
+    private final boolean                               displayClusters           = true;\r
+\r
+    private final BijectionMap<String, Resource>        links                     = new BijectionMap<String, Resource>();\r
+    private final LinkedList<Resource>                  backHistory               = new LinkedList<Resource>();\r
+    private final LinkedList<Resource>                  forwardHistory            = new LinkedList<Resource>();\r
+    private Resource                                    currentElement            = null;\r
+\r
+    /**\r
+     * The Session used to access the graph. Received from outside of this\r
+     * class and therefore it is not disposed here, just used.\r
+     */\r
+    private final Session                               session;\r
+\r
+    private final CopyOnWriteArrayList<HistoryListener> historyListeners          = new CopyOnWriteArrayList<HistoryListener>();\r
+\r
+    private final AsyncRequestProcessor                 updater;\r
+\r
+    protected Layer0                                    L0;\r
+\r
+    protected IWorkbenchSite                            site;\r
+\r
+    private final ChangeListener changeListener = new ChangeListener() {\r
+        @Override\r
+        public void graphChanged(ChangeEvent e) {\r
+            // This makes sure that the transaction for updating this\r
+            // GraphDebugger get executed in a serialized fashion.\r
+            updater.asyncRequest(new ReadRequest() {\r
+\r
+                @Override\r
+                public void run(ReadGraph graph) throws DatabaseException {\r
+                    updateContent(graph, currentElement);\r
+                }\r
+\r
+            });\r
+\r
+        }\r
+    };\r
+\r
+    /**\r
+     * @param parent\r
+     * @param style\r
+     * @param session\r
+     * @param resource the initial resource to debug or <code>null</code> for\r
+     *        initially blank UI.\r
+     * @param site the workbench site that contains this debugger, for workbench\r
+     *        service access\r
+     */\r
+    public GraphDebugger(Composite parent, int style, final Session session, Resource resource, IWorkbenchSite site) {\r
+        this(parent, style, session, resource);\r
+        this.site = site;\r
+    }\r
+\r
+    /**\r
+     * @param parent\r
+     * @param style\r
+     * @param session\r
+     * @param resource the initial resource to debug or <code>null</code> for\r
+     *        initially blank UI.\r
+     */\r
+    public GraphDebugger(Composite parent, int style, final Session session, Resource resource) {\r
+        super(parent, style);\r
+        Assert.isNotNull(session, "session is null");\r
+        this.session = session;\r
+        this.currentElement = resource;\r
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);\r
+\r
+        updater = session;//.getService(MergingGraphRequestProcessor.class);\r
+\r
+        initializeCSS();\r
+\r
+        addDisposeListener(new DisposeListener() {\r
+            @Override\r
+            public void widgetDisposed(DisposeEvent e) {\r
+                GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
+                support.removeListener(changeListener);\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * When given to setStatus, indicates that the message shouldn't be touched\r
+     * since <code>null</code> has a different meaning.\r
+     */\r
+    private static final String DONT_TOUCH = "DONT_TOUCH";\r
+\r
+    protected void setStatus(String message, String error) {\r
+        IStatusLineManager status = WorkbenchUtils.getStatusLine(site);\r
+        if (status != null) {\r
+            if (message != DONT_TOUCH)\r
+                status.setMessage(message);\r
+            if (error != DONT_TOUCH)\r
+                status.setErrorMessage(error);\r
+        }\r
+    }\r
+\r
+    public void defaultInitializeUI() {\r
+        setLayout(new GridLayout(2, false));\r
+        setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));\r
+\r
+        createResourceText(this);\r
+        createDropLabel(this);\r
+        createBrowser(this);\r
+\r
+    }\r
+\r
+    protected void initializeCSS() {\r
+        // Extract default css to a temporary location if necessary.\r
+        try {\r
+            IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH);\r
+            if (absolutePath != null) {\r
+                cssPath = absolutePath.toFile().toURI().toString();\r
+            } else {\r
+                File tempDir = FileUtils.getOrCreateTemporaryDirectory(false);\r
+                File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE);\r
+                if (!css.exists()) {\r
+                    URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null);\r
+                    if (url == null)\r
+                        throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'");\r
+                    cssPath = FileUtils.copyResource(url, css, true).toURI().toString();\r
+                } else {\r
+                    cssPath = css.toURI().toString();\r
+                }\r
+            }\r
+        } catch (IOException e) {\r
+            // CSS extraction failed, let's just live without it then.\r
+            ErrorLogger.defaultLogWarning(e);\r
+        }\r
+    }\r
+\r
+    private static final String PROMPT_TEXT = "Enter resource ID (RID) or URI";\r
+\r
+    public void createResourceText(final Composite parent) {\r
+        final Text text = new Text(parent, SWT.BORDER);\r
+        text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
+        text.setText(PROMPT_TEXT);\r
+        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(text);\r
+        \r
+        text.addFocusListener(new FocusListener() {    \r
+            @Override\r
+            public void focusLost(FocusEvent e) {\r
+                if (text.getText().trim().equals("")) {\r
+                    text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
+                    text.setText(PROMPT_TEXT);\r
+                }\r
+            }\r
+            @Override\r
+            public void focusGained(FocusEvent e) {\r
+                if (text.getText().trim().equals(PROMPT_TEXT)) {\r
+                    text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));\r
+                    text.setText("");\r
+                }\r
+                text.selectAll();\r
+            }\r
+        });\r
+        text.addKeyListener(new KeyAdapter() {\r
+            @Override\r
+            public void keyPressed(KeyEvent e) {\r
+                if (e.keyCode == SWT.CR) {\r
+                    String input = text.getText();\r
+                    setLookupInput(input);\r
+                }\r
+            }\r
+        });\r
+\r
+        final Button button = new Button(parent, SWT.FLAT);\r
+        button.setText("&Lookup");\r
+        button.setEnabled(false);\r
+        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false).applyTo(button);\r
+\r
+        text.addKeyListener(new KeyAdapter() {\r
+            \r
+            @Override\r
+            public void keyPressed(KeyEvent e) {\r
+                if (e.keyCode == 13) {\r
+                    String input = text.getText();\r
+                    setLookupInput(input);\r
+                    button.setFocus();\r
+                }\r
+            }\r
+        });\r
+        \r
+        button.addSelectionListener(new SelectionListener() {\r
+            @Override\r
+            public void widgetDefaultSelected(SelectionEvent e) {\r
+                widgetSelected(e);\r
+            }\r
+            @Override\r
+            public void widgetSelected(SelectionEvent e) {\r
+                String input = text.getText();\r
+                setLookupInput(input);\r
+            }\r
+        });\r
+        \r
+        text.addModifyListener(new ModifyListener() {\r
+            @Override\r
+            public void modifyText(ModifyEvent e) {\r
+                String input = text.getText().trim();\r
+                if (!input.equals(PROMPT_TEXT) && !input.equals(""))\r
+                    button.setEnabled(true);\r
+                else\r
+                    button.setEnabled(false);\r
+            }\r
+        });\r
+    }\r
+\r
+    public void setLookupInput(String input) {\r
+        // There's no harm in trimming out spaces from both ends of the input.\r
+        input = input.trim();\r
+\r
+        SerialisationSupport support = session.getService(SerialisationSupport.class);\r
+        if (input.startsWith("$")) {\r
+            try {\r
+                Resource r = support.getResource(Long.parseLong(input.substring(1)));\r
+                changeLocation(r);\r
+            } catch (NumberFormatException e1) {\r
+                // Ignore, may happen for crap input\r
+                setStatus(DONT_TOUCH, "Invalid '$'-prefixed input, expected resource ID");\r
+            } catch (Exception e1) {\r
+                ErrorLogger.defaultLogError(e1);\r
+                setStatus(DONT_TOUCH, "Resource ID lookup failed. See Error Log.");\r
+            }\r
+            return;\r
+        }\r
+\r
+        String[] parts = input.split("-");\r
+\r
+        if (parts.length == 1) {\r
+            try {\r
+                int resourceKey = Integer.parseInt(parts[0].trim());\r
+                Resource r = support.getResource(resourceKey);\r
+                // Some validation, not enough though\r
+                ClusteringSupport cs = session.getService(ClusteringSupport.class);\r
+                long cluster = cs.getCluster(r);\r
+                if(cluster > 0) {\r
+                    changeLocation(r);\r
+                }\r
+            } catch (NumberFormatException e1) {\r
+                // Ignore, may happen for crap input\r
+                setStatus(DONT_TOUCH, "Invalid input, expected transient resource ID");\r
+            } catch (Exception e1) {\r
+                ErrorLogger.defaultLogError(e1);\r
+                setStatus(DONT_TOUCH, "Transient resource ID lookup failed. See Error Log.");\r
+            }\r
+        } else if (parts.length == 2) {\r
+            try {\r
+                int resourceIndex = Integer.parseInt(parts[1]);\r
+                long clusterId = Long.parseLong(parts[0]);\r
+                ClusteringSupport cs = session.getService(ClusteringSupport.class);\r
+                Resource r = cs.getResourceByIndexAndCluster(resourceIndex, clusterId);\r
+                changeLocation(r);\r
+            } catch (NumberFormatException e1) {\r
+                // Ignore, may happen for crap input\r
+                setStatus(DONT_TOUCH, "Invalid input, expected index & cluster IDs");\r
+            } catch (Exception e1) {\r
+                ErrorLogger.defaultLogError(e1);\r
+                setStatus(DONT_TOUCH, "Index & cluster -based lookup failed. See Error Log.");\r
+            }\r
+        }\r
+\r
+        // Try to see if the input data is an URI reference\r
+        try {\r
+            // First check that the input really is a proper URI.\r
+            String uri = input;\r
+            if (!input.equals("http:/") && input.endsWith("/"))\r
+                uri = input.substring(0, input.length() - 1);\r
+            new URI(uri);\r
+            Resource r = session.syncRequest( Queries.resource( uri ) );\r
+            changeLocation(r);\r
+            return;\r
+        } catch (URISyntaxException e) {\r
+            // Ignore, this is not a proper URI at all.\r
+        } catch (ResourceNotFoundException e1) {\r
+            // Ok, this was an URI, but no resource was found.\r
+            setStatus(DONT_TOUCH, "Resource for URI '" + input + "' not found");\r
+            return;\r
+        } catch (DatabaseException e1) {\r
+            setStatus(DONT_TOUCH, "URI lookup failed. See Error Log.");\r
+            ErrorLogger.defaultLogError(e1);\r
+        }\r
+\r
+        setStatus(DONT_TOUCH, "Invalid input, resource ID or URI expected");\r
+    }\r
+\r
+    public Label createDropLabel(Composite parent) {\r
+        final Label label = new Label(parent, SWT.BORDER);\r
+        label.setAlignment(SWT.CENTER);\r
+        label.setText("Drag a resource here to examine it in this debugger!");\r
+        label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
+        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).hint(SWT.DEFAULT, 20).span(2, 1).grab(true, false).applyTo(label);\r
+\r
+        // Add resource id drop support to the drop-area.\r
+        DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY);\r
+        dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() });\r
+        dropTarget.addDropListener(new DropTargetAdapter() {\r
+            @Override\r
+            public void dragEnter(DropTargetEvent event) {\r
+                event.detail = DND.DROP_LINK;\r
+                label.setBackground((Color) resourceManager.get(green));\r
+                return;\r
+            }\r
+            @Override\r
+            public void dragLeave(DropTargetEvent event) {\r
+                label.setBackground(null);\r
+            }\r
+\r
+            @Override\r
+            public void drop(DropTargetEvent event) {\r
+                label.setBackground(null);\r
+                ResourceArray[] data = parseEventData(event);\r
+                if (data == null || data.length != 1) {\r
+                    event.detail = DND.DROP_NONE;\r
+                    return;\r
+                }\r
+                final ResourceArray array = data[0];\r
+                final Resource r = array.resources[array.resources.length - 1];\r
+\r
+                changeLocation(r);\r
+            }\r
+\r
+            private ResourceArray[] parseEventData(DropTargetEvent event) {\r
+                //System.out.println("DATA: " + event.data);\r
+                if (event.data instanceof String) {\r
+                    try {\r
+                        SerialisationSupport support = session.getService(SerialisationSupport.class);\r
+                        return ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();\r
+                    } catch (IllegalArgumentException e) {\r
+                        ErrorLogger.defaultLogError(e);\r
+                    } catch (DatabaseException e) {\r
+                        ErrorLogger.defaultLogError(e);\r
+                    }\r
+                }\r
+                ResourceArray[] ret = ResourceAdaptionUtils.toResourceArrays(event.data);\r
+                if (ret.length > 0)\r
+                    return ret;\r
+                return null;\r
+            }\r
+        });\r
+\r
+        return label;\r
+    }\r
+\r
+    public Browser createBrowser(Composite parent) {\r
+        try {\r
+            browser = new Browser(parent, SWT.MOZILLA);\r
+        } catch (SWTError e) {\r
+            //System.out.println("Could not instantiate Browser: " + e.getMessage());\r
+            browser = new Browser(parent, SWT.NONE);\r
+        }\r
+        GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(browser);\r
+\r
+        // Left/right arrows for back/forward\r
+        browser.addKeyListener(new KeyAdapter() {\r
+//             \r
+//             @Override\r
+//            public void keyPressed(KeyEvent e) {\r
+//                     if (e.keyCode == SWT.F5) {\r
+//                     refreshBrowser();\r
+//                     }\r
+//             }\r
+//          }\r
+            @Override\r
+            public void keyReleased(KeyEvent e) {\r
+//                System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")");\r
+                if (e.keyCode == SWT.BS) {\r
+                    back();\r
+                }\r
+                \r
+                if (e.keyCode == SWT.F5) {\r
+                       refreshBrowser();\r
+                }\r
+                \r
+                if ((e.stateMask & SWT.ALT) != 0) {\r
+                    if (e.keyCode == SWT.ARROW_RIGHT)\r
+                        forward();\r
+                    if (e.keyCode == SWT.ARROW_LEFT)\r
+                        back();\r
+                }\r
+            }\r
+        });\r
+\r
+        // Add listener for debugging functionality\r
+        browser.addLocationListener(new LocationAdapter() {\r
+            @Override\r
+            public void changing(LocationEvent event) {\r
+                String location = event.location;\r
+                if (location.startsWith("simantics:browser"))\r
+                    location = "about:" + location.substring(17);\r
+                //System.out.println("changing: location=" + location);\r
+\r
+                // Do not follow links that are meant as actions that are\r
+                // handled below.\r
+                event.doit = false;\r
+                if ("about:blank".equals(location)) {\r
+                    // Just changing to the same old blank url is ok since it\r
+                    // allows the browser to refresh itself.\r
+                    event.doit = true;\r
+                }\r
+\r
+                if (location.startsWith("about:-link")) {\r
+                    String target = location.replace("about:-link", "");\r
+                    Resource element = links.getRight(target);\r
+                    if (element == currentElement) {\r
+                        event.doit = false;\r
+                        return;\r
+                    }\r
+                    changeLocation(element);\r
+                } else if (location.startsWith("about:-remove")) {\r
+                    String target = location.replace("about:-remove", "");\r
+                    String n[] = target.split(STATEMENT_PART_SEPARATOR);\r
+                    if (n.length != 3)\r
+                        return;\r
+\r
+                    final Resource s = links.getRight(n[0]);\r
+                    final Resource p = links.getRight(n[1]);\r
+                    final Resource o = links.getRight(n[2]);\r
+\r
+                    // Make sure this is what the use wants.\r
+                    MessageDialog md = new MessageDialog(\r
+                            getShell(),\r
+                            "Confirm action...",\r
+                            null,\r
+                            "This action will remove the selected statement.\nAre you sure you want to proceed with this action?",\r
+                            MessageDialog.QUESTION, new String[] { "Cancel", "Continue" }, 0);\r
+                    if (md.open() != 1) {\r
+                        return;\r
+                    }\r
+\r
+                    session.asyncRequest(new WriteRequest() {\r
+\r
+                        @Override\r
+                        public void perform(WriteGraph g) throws DatabaseException {\r
+                               try {\r
+                                       List<Resource> ls = OrderedSetUtils.toList(g, s);\r
+                                       if(ls.contains(o))\r
+                                               OrderedSetUtils.remove(g, s, o);\r
+                               } catch (DatabaseException e) {\r
+                                       \r
+                               }\r
+                               try {\r
+                                       List<Resource> ls = ListUtils.toList(g, s);\r
+                                       if(ls.contains(o))\r
+                                               ListUtils.removeElement(g, s, o);\r
+                               } catch (DatabaseException e) {\r
+                                       \r
+                               }\r
+                            g.denyStatement(s, p, o);\r
+                        }\r
+\r
+                    }, new Callback<DatabaseException>() {\r
+\r
+                        @Override\r
+                        public void run(DatabaseException parameter) {\r
+                            refreshBrowser();\r
+                        }\r
+\r
+                    });\r
+                } else if (location.startsWith("about:-edit-value")) {\r
+                    String target = location.replace("about:-edit-value", "");\r
+                    final Resource o = links.getRight(target);\r
+\r
+                    session.asyncRequest(new ReadRequest() {\r
+\r
+                        String previousValue;\r
+\r
+                        @Override\r
+                        public void run(ReadGraph graph) throws DatabaseException {\r
+\r
+                            previousValue = getResourceName(graph, o);\r
+                            final StringModifier modifier = graph.adapt(o, StringModifier.class);\r
+                            getDisplay().asyncExec(new Runnable() {\r
+                                @Override\r
+                                public void run() {\r
+                                    InputDialog dialog = new InputDialog(\r
+                                            getShell(),\r
+                                            "Edit Value",\r
+                                            null,\r
+                                            previousValue,\r
+                                            new IInputValidator() {\r
+                                                @Override\r
+                                                public String isValid(String newText) {\r
+                                                    return modifier.isValid(newText);\r
+                                                }\r
+                                            }) {\r
+                                        private static final String DIALOG = "DebuggerEditValueDialog"; //$NON-NLS-1$\r
+                                        private IDialogSettings dialogBoundsSettings;\r
+                                        @Override\r
+                                        protected IDialogSettings getDialogBoundsSettings() {\r
+                                            if (dialogBoundsSettings == null) {\r
+                                                IDialogSettings settings = Activator.getDefault().getDialogSettings();\r
+                                                dialogBoundsSettings = settings.getSection(DIALOG);\r
+                                                if (dialogBoundsSettings == null)\r
+                                                    dialogBoundsSettings = settings.addNewSection(DIALOG);\r
+                                            }\r
+                                            return dialogBoundsSettings;\r
+                                        }\r
+                                        @Override\r
+                                        protected Point getInitialSize() {\r
+                                            Point defaultSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);\r
+                                            Point result = super.getInitialSize();\r
+                                            if (defaultSize.equals(result))\r
+                                                return new Point(600, 400);\r
+                                            return result;\r
+                                        }\r
+                                        protected int getShellStyle() {\r
+                                            return super.getShellStyle() | SWT.RESIZE;\r
+                                        }\r
+                                        protected org.eclipse.swt.widgets.Control createDialogArea(Composite parent) {\r
+                                            Composite composite = (Composite) super.createDialogArea(parent);\r
+                                            getText().setLayoutData(\r
+                                                    new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL\r
+                                                            | GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL));\r
+                                            {\r
+                                                Label label = new Label(composite, SWT.NONE);\r
+                                                label.moveAbove(getText());\r
+                                                label.setText("Input new property value. For numeric vector values, separate numbers with comma (',').");\r
+                                                GridData data = new GridData(GridData.GRAB_HORIZONTAL\r
+                                                        | GridData.HORIZONTAL_ALIGN_FILL\r
+                                                        | GridData.VERTICAL_ALIGN_CENTER);\r
+                                                data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);\r
+                                                label.setLayoutData(data);\r
+                                                label.setFont(parent.getFont());\r
+                                            }\r
+                                            return composite;\r
+                                        }\r
+                                        protected int getInputTextStyle() {\r
+                                            return SWT.MULTI | SWT.BORDER;\r
+                                        }\r
+                                    };\r
+                                    int ok = dialog.open();\r
+                                    if (ok != Dialog.OK)\r
+                                        return;\r
+\r
+                                    final String value = dialog.getValue();\r
+                                    session.asyncRequest(new WriteRequest() {\r
+                                        @Override\r
+                                        public void perform(WriteGraph g) throws DatabaseException {\r
+                                            //modifier.modify( g, htmlEscape( value ) );\r
+                                            modifier.modify( g, value );\r
+                                        }\r
+                                    }, new Callback<DatabaseException>() {\r
+                                        @Override\r
+                                        public void run(DatabaseException parameter) {\r
+                                            if (parameter != null)\r
+                                                ErrorLogger.defaultLogError(parameter);\r
+                                            refreshBrowser();\r
+                                        }\r
+                                    });\r
+                                    return;\r
+                                }\r
+                            });\r
+                        }\r
+\r
+                    }, new ProcedureAdapter<Object>() {\r
+                        @Override\r
+                        public void exception(Throwable t) {\r
+                            ErrorLogger.defaultLogError(t);\r
+                        }\r
+                    });\r
+\r
+                }\r
+            }\r
+        });\r
+\r
+        // Schedule a request that updates the browser content.\r
+        refreshBrowser();\r
+        GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
+        support.removeListener(changeListener);\r
+\r
+        return browser;\r
+    }\r
+\r
+    public void refreshBrowser() {\r
+        if (currentElement == null)\r
+            return;\r
+\r
+        // Schedule a request that updates the browser content.\r
+        updater.asyncRequest(new ReadRequest() {\r
+\r
+            @Override\r
+            public void run(ReadGraph graph) throws DatabaseException {\r
+                updateContent(graph, currentElement);\r
+            }\r
+\r
+        });\r
+\r
+    }\r
+\r
+    public Resource getDebuggerLocation() {\r
+        return currentElement;\r
+    }\r
+\r
+    public void changeLocation(Resource element) {\r
+        if (currentElement != null) {\r
+            backHistory.addLast(currentElement);\r
+        }\r
+        currentElement = element;\r
+        forwardHistory.clear();\r
+\r
+        refreshBrowser();\r
+        setStatus(DONT_TOUCH, null);\r
+        fireHistoryChanged();\r
+    }\r
+\r
+    public void addHistoryListener(HistoryListener l) {\r
+        historyListeners.add(l);\r
+    }\r
+\r
+    public void removeHistoryListener(HistoryListener l) {\r
+        historyListeners.remove(l);\r
+    }\r
+\r
+    private void fireHistoryChanged() {\r
+        for (HistoryListener l : historyListeners)\r
+            l.historyChanged();\r
+    }\r
+\r
+    public boolean hasBackHistory() {\r
+        return backHistory.isEmpty();\r
+    }\r
+\r
+    public boolean hasForwardHistory() {\r
+        return forwardHistory.isEmpty();\r
+    }\r
+\r
+    public void back() {\r
+        if (backHistory.isEmpty())\r
+            return;\r
+\r
+        forwardHistory.addFirst(currentElement);\r
+        currentElement = backHistory.removeLast();\r
+\r
+        refreshBrowser();\r
+        fireHistoryChanged();\r
+    }\r
+\r
+    public void forward() {\r
+        if (forwardHistory.isEmpty())\r
+            return;\r
+\r
+        backHistory.addLast(currentElement);\r
+        currentElement = forwardHistory.removeFirst();\r
+\r
+        refreshBrowser();\r
+        fireHistoryChanged();\r
+    }\r
+\r
+    protected String toName(Object o) {\r
+        Class<?> clazz = o.getClass();\r
+        if (clazz.isArray()) {\r
+            int length = Array.getLength(o);\r
+            if (length > RESOURCE_NAME_MAX_LENGTH) {\r
+                if (o instanceof byte[]) {\r
+                    byte[] arr = (byte[]) o;\r
+                    byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("byte", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof int[]) {\r
+                    int[] arr = (int[]) o;\r
+                    int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("int", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof long[]) {\r
+                    long[] arr = (long[]) o;\r
+                    long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("long", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof float[]) {\r
+                    float[] arr = (float[]) o;\r
+                    float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("float", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof double[]) {\r
+                    double[] arr = (double[]) o;\r
+                    double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("double", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof boolean[]) {\r
+                    boolean[] arr = (boolean[]) o;\r
+                    boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("boolean", Arrays.toString(arr2), arr.length);\r
+                } else if (o instanceof Object[]) {\r
+                    Object[] arr = (Object[]) o;\r
+                    Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
+                    return truncated("Object", Arrays.toString(arr2), arr.length);\r
+                } else {\r
+                    return "Unknown big array " + o.getClass();\r
+                }\r
+            } else {\r
+                return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o);\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    protected String truncated(String type, String string, int originalLength) {\r
+        return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string;\r
+    }\r
+    \r
+    public static String htmlEscape(String s)\r
+    {\r
+        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br/>");\r
+    }\r
+\r
+    /**\r
+     * Get resource name(?) \r
+     * \r
+     * @param graph\r
+     * @param r\r
+     * @return\r
+     */\r
+    protected String getResourceName(ReadGraph graph, Resource r) {\r
+        try {\r
+\r
+            String name = null;\r
+            //System.out.println("hasValue(" + NameUtils.getSafeName(graph, r, true));\r
+            if (graph.hasValue(r)) {\r
+                // too large array may cause application to run out of memory.\r
+                //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));\r
+                Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
+                if (type!=null) {\r
+                       Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );\r
+                       if (type.equals( rviBinding.type() )) {\r
+                               RVI rvi = graph.getValue(r, rviBinding);\r
+                                                               \r
+                               try {\r
+                                       Variable v = Variables.getConfigurationContext( graph, currentElement );\r
+                                       name = rvi.asString(graph, v);\r
+//                                     name = rvi.resolve(graph, v).getURI(graph);\r
+                               } catch (DatabaseException dbe ) {\r
+                                       name = rvi.toString( graph );\r
+                               }\r
+                       } else {\r
+                               long valueSize = NameUtils.getPossibleValueSize(graph, r);\r
+                               if (valueSize > RESOURCE_NAME_MAX_LENGTH) {\r
+//                                     Binding b = Bindings.getBinding(type);\r
+//                                     Object v = graph.getValue(r, b);\r
+//                                     Serializer s = Bindings.getSerializerUnchecked(b);\r
+//                                     int size = s.getSize(v);\r
+                                   name = "Approx. " + valueSize + " byte literal of type " + type.toSingleLineString();\r
+                               } else {                                        \r
+                                       Binding b = Bindings.getBinding(type);\r
+                                       Object v = graph.getValue(r, b);\r
+                                       if (b.type() instanceof StringType) {\r
+                                               name = (String) graph.getValue(r, b);\r
+                                       } else {\r
+                                           name = b.toString(v, false);\r
+                                       }\r
+                                   if (type instanceof ArrayType){\r
+                                       name = name.substring(1, name.length()-1);\r
+                                   }\r
+                               }\r
+                       }\r
+                } else {\r
+                    Object o = graph.getValue(r);\r
+                    name = toName(o);\r
+                }\r
+                \r
+                if(name.isEmpty()) {\r
+                       name = "<empty value>";\r
+                }\r
+                \r
+            }\r
+            // Does resource have a file ??\r
+            if (name == null) {\r
+//                try {\r
+//                    Accessor accessor = graph.getAccessor(r);\r
+//                    name = "File of type " + accessor.type().toSingleLineString();\r
+//                } catch (DatabaseException e) {\r
+//                    // No file, try next alternative.\r
+//                }\r
+            }\r
+            if (name == null) {\r
+                //name = graph.adapt(r, String.class);\r
+                //if(name.isEmpty())\r
+                name = DebugUtils.getSafeLabel(graph, r);\r
+                if (name.isEmpty())\r
+                    name = "<empty name>";\r
+            }\r
+//            ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);\r
+//            if(name == null)\r
+//                return "[" + r.getResourceId() + " - " + support.getCluster(r) + "]";\r
+            if(displayClusters) {\r
+//                SessionDebug debug = graph.getSession().getDebug();\r
+//                name += " (" + debug.getCluster(r) + ")";\r
+            }\r
+            return name;\r
+        } catch (AdaptionException e) {\r
+//            e.printStackTrace();\r
+            String name = safeReadableString(graph, r);\r
+//                 try {\r
+//                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_adaption_problem, MessageUtil.resource(session, r, "this resource")), e));\r
+//            } catch (ReferenceSerializationException e1) {\r
+//                e1.printStackTrace();\r
+//                ErrorLogger.defaultLogWarning(e1);\r
+//            }\r
+            return name;\r
+        } catch (Exception e) {\r
+            ErrorLogger.defaultLogError(e);\r
+            String name = safeReadableString(graph, r);\r
+//         try {\r
+//                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_formulation_problem, MessageUtil.resource(session, r, "this resource")), e));\r
+//            } catch (ReferenceSerializationException e1) {\r
+//                e1.printStackTrace();\r
+//                ErrorLogger.defaultLogWarning(e1);\r
+//            }\r
+            return name;\r
+        }\r
+    }\r
+\r
+    private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {\r
+        String name;\r
+        try {\r
+            Layer0 L0 = Layer0.getInstance(graph);\r
+            if (graph.isInstanceOf(r, L0.Assertion)) {\r
+                Resource pred = graph.getSingleObject(r, L0.HasPredicate);\r
+                Resource obj = graph.getSingleObject(r, L0.HasObject);\r
+                String tmp = htmlEscape( getResourceName(graph, pred) + " -> " + getResourceName(graph, obj) + " (Assertion)" );\r
+                name = tmp.substring(0, Math.min(80, tmp.length()));\r
+            } else {\r
+                String resourceName = getResourceName(graph, r);\r
+                if(resourceName.equals("Inverse")) {\r
+                    Resource inverse = graph.getPossibleInverse(r);\r
+                    if(inverse != null && graph.hasStatement(inverse, L0.ConsistsOf, r))\r
+                        resourceName = getResourceName(graph, inverse) + "/Inverse";\r
+                }\r
+                String tmp = htmlEscape( resourceName );\r
+                name = tmp.substring(0, Math.min(80, tmp.length()));\r
+            }\r
+            \r
+        } catch (OutOfMemoryError e) {\r
+            name = "OutOfMemoryError";\r
+        }\r
+        String ret = "<a href=\"simantics:browser-link" + getLinkString(r) + "\">"\r
+        + name\r
+        + "</a>";\r
+        if (graph.isInstanceOf(r, L0.Literal)) {\r
+            ret += "&nbsp;<a class=\"edit-link\" href=\"simantics:browser-edit-value" + getLinkString(r) + "\">"\r
+            + "(edit)"\r
+            + "</a>";\r
+        }\r
+        return ret;\r
+    }\r
+\r
+    private String getStatementRemoveRef(Resource s, Resource p, Resource o) {\r
+        return "<a href=\"simantics:browser-remove" + getStatementString(s, p, o)\r
+        + "\" title=\"Remove this statement\">X</a>";\r
+    }\r
+\r
+    private void updatePred(StringBuffer content, ReadGraph graph, Resource subj, Resource pred, List<Resource[]> stats) throws DatabaseException {\r
+        // Generate output content from statements\r
+        String[][] objects = new String[stats.size()][];\r
+        for (int i = 0; i < stats.size(); ++i) {\r
+            Resource stmSubject = stats.get(i)[0];\r
+            Resource object = stats.get(i)[1];\r
+\r
+            objects[i] = new String[4];\r
+            objects[i][0] = getLinkString(object);\r
+            objects[i][1] = htmlEscape( getResourceName(graph, object) );\r
+            objects[i][2] = getResourceRef(graph, object);\r
+\r
+            // Make a note if the statement was acquired.\r
+            if(!stmSubject.equals(subj)) {\r
+                objects[i][3] = " (in " + getResourceRef(graph, stmSubject) + ")";\r
+            }\r
+        }\r
+\r
+        // Sort statements by object name\r
+        Arrays.sort(objects, new Comparator<String[]>() {\r
+            @Override\r
+            public int compare(String[] o1, String[] o2) {\r
+                return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1[1], o2[1]);\r
+            }\r
+        });\r
+\r
+        // Output table rows\r
+        for (int i = 0; i < objects.length; ++i) {\r
+            content.append("<tr>");\r
+            // Predicate column\r
+            if (i == 0)\r
+                content.append("<td rowspan=\"").append(objects.length).append("\" valign=\"top\">").append(getResourceRef(graph, pred)).append("</td>");\r
+\r
+            // Object column\r
+            if (objects[i][3] == null) content.append("<td>");\r
+            else content.append("<td class=\"acquired\">");\r
+\r
+            content.append(objects[i][2]);\r
+            if (objects[i][3] != null)\r
+                content.append(objects[i][3]);\r
+\r
+            content.append("</td>");\r
+            \r
+            VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class);\r
+            VirtualGraph vg = vgs.getGraph(graph, subj, pred, links.getRight(objects[i][0]));\r
+            \r
+            if(vg != null) {\r
+                content.append("<td>").append(vg.toString()).append("</td>");\r
+            } else {\r
+                content.append("<td>DB</td>");\r
+            }\r
+            \r
+\r
+            // Statement remove -link column\r
+            // Only allowed for non-acquired statements.\r
+            if (objects[i][3] == null) {\r
+                content.append("<td class=\"remove\">");\r
+                content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
+                content.append("</td>");\r
+            }\r
+            content.append("</tr>");\r
+        }\r
+    }\r
+\r
+    private void updateTag(StringBuffer content, ReadGraph graph, Resource subj, Resource tag) throws DatabaseException {\r
+\r
+        // Generate output content from statements\r
+        String ref = getResourceRef(graph, tag);\r
+\r
+        content.append("<tr>");\r
+        content.append("<td rowspan=\"1\" colspan=\"3\" valign=\"top\">").append(ref).append("</td>");\r
+        //content.append("<td>" + name + "</td>");\r
+        content.append("<td class=\"remove\">");\r
+        content.append(getStatementRemoveRef(subj, tag, subj));\r
+        content.append("</td>");\r
+        content.append("</tr>");\r
+\r
+    }\r
+\r
+    private void updateOrderedSet(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {\r
+        //List<Resource> list = OrderedSetUtils.toList(graph, subj);\r
+        /*\r
+        // Generate output content from statements\r
+        String[][] objects = new String[stats.size()][];\r
+        for (int i = 0; i < stats.size(); ++i) {\r
+            Resource stmSubject = stats.get(i)[0];\r
+            Resource object = stats.get(i)[1];\r
+\r
+            objects[i] = new String[4];\r
+            objects[i][0] = getLinkString(object);\r
+            objects[i][1] = getResourceName(graph, object);\r
+            objects[i][2] = getResourceRef(graph, object);\r
+\r
+            // Make a note if the statement was acquired.\r
+            if(!stmSubject.equals(subj)) {\r
+                objects[i][3] = " (acquired from " + getResourceRef(graph, stmSubject) + ")";\r
+            }\r
+        }\r
+\r
+        // Sort statements by object name\r
+        Arrays.sort(objects, new Comparator<String[]>() {\r
+            @Override\r
+            public int compare(String[] o1, String[] o2) {\r
+               return o1[1].compareTo(o2[1]);\r
+            }\r
+        });*/\r
+\r
+        List<String> list = new ArrayList<String>();\r
+        Resource cur = subj;\r
+        while(true) {\r
+            try {\r
+                cur = OrderedSetUtils.next(graph, subj, cur);\r
+            } catch(DatabaseException e) {\r
+                list.add("<span style=\"color:red;font-weight:bold\">BROKEN ORDERED SET:<br/></span><span style=\"color:red\">" + e.getMessage() + "</span>");\r
+                Resource inv = graph.getPossibleInverse(subj);\r
+                for(Statement stat : graph.getStatements(cur, L0.IsRelatedTo)) {\r
+                    if(stat.getSubject().equals(cur)) {\r
+                        if(stat.getPredicate().equals(subj)) {\r
+                            list.add("next " + getResourceRef(graph, stat.getObject()));\r
+                        }\r
+                        else if(stat.getPredicate().equals(inv)) {\r
+                            list.add("prev " + getResourceRef(graph, stat.getObject()));\r
+                        }\r
+                    }\r
+                }\r
+                break;\r
+            }\r
+            if(cur.equals(subj))\r
+                break;\r
+            list.add(getResourceRef(graph, cur));\r
+        }\r
+\r
+        // Output table rows\r
+        for (int i = 0; i < list.size() ; ++i) {\r
+            content.append("<tr>");\r
+            // Predicate column\r
+            if (i == 0)\r
+                content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Ordered Set Elements</td>");\r
+\r
+            // Object column\r
+            content.append("<td>");\r
+            content.append(list.get(i));\r
+            content.append("</td>");\r
+\r
+            // Statement remove -link column\r
+            // Only allowed for non-acquired statements.\r
+            /*if (objects[i][3] == null) {\r
+                content.append("<td class=\"remove\">");\r
+                content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
+                content.append("</td>");\r
+            }*/\r
+            content.append("</tr>");\r
+        }\r
+    }\r
+\r
+    private void updateLinkedList(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {\r
+\r
+        List<String> list = new ArrayList<String>();\r
+        \r
+        try {\r
+               List<Resource> resources = ListUtils.toList(graph, subj);\r
+               for(Resource element : resources) {\r
+                   list.add(getResourceRef(graph, element));\r
+               }\r
+        } catch (DatabaseException e) {\r
+               throw new ValidationException(e);\r
+        }\r
+\r
+        // Output table rows\r
+        for (int i = 0; i < list.size() ; ++i) {\r
+            content.append("<tr>");\r
+            // Predicate column\r
+            if (i == 0)\r
+                content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Linked List Elements</td>");\r
+\r
+            // Object column\r
+            content.append("<td>");\r
+            content.append(list.get(i));\r
+            content.append("</td><td>DB</td>");\r
+\r
+            // Statement remove -link column\r
+            // Only allowed for non-acquired statements.\r
+            /*if (objects[i][3] == null) {\r
+                content.append("<td class=\"remove\">");\r
+                content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
+                content.append("</td>");\r
+            }*/\r
+            content.append("</tr>");\r
+        }\r
+    }\r
+\r
+    protected synchronized void updateContent(final ReadGraph graph, Resource... resources) throws DatabaseException {\r
+        L0 = Layer0.getInstance(graph);\r
+\r
+        links.clear();\r
+        StringBuffer content = new StringBuffer();\r
+\r
+        // Generate HTML -page\r
+        content.append("<html>\n<head>\n")\r
+        .append(getHead())\r
+        .append("\n</head>\n")\r
+        .append("<body>\n")\r
+        .append("<div id=\"mainContent\">\n\n");\r
+\r
+        for (Resource r : resources) {\r
+            if (r == null)\r
+                continue;\r
+\r
+            String uri = null;\r
+            try {\r
+                uri = graph.syncRequest(new ResourceToPossibleURI(r));\r
+            } catch (Exception e) {\r
+                ErrorLogger.defaultLogError(e);\r
+                uri = "Cannot get URI: " + e.getMessage();\r
+            }\r
+\r
+            // Top DIV\r
+            content.append("<div id=\"top\">\n");\r
+            content.append("<table class=\"top\">\n");\r
+            if (uri != null) {\r
+                content.append("<tr><td class=\"top_key\">URI</td><td class=\"top_value\"><span id=\"uri\">").append(uri).append("</span></td></tr>\n");\r
+            }\r
+\r
+            XSupport xs = graph.getService(XSupport.class);\r
+            boolean immutable = xs.getImmutable(r);\r
+\r
+            Collection<Statement> statements = graph.getStatements(r, L0.IsWeaklyRelatedTo);\r
+            HashMultiMap<Resource, Resource[]> map = new HashMultiMap<Resource, Resource[]>();\r
+            for(org.simantics.db.Statement statement : statements) {\r
+                Resource predicate = null;\r
+                Resource subject = null;\r
+                Resource obj = null;\r
+                try {\r
+                    predicate = statement.getPredicate();\r
+                    subject = statement.getSubject();\r
+                    obj = statement.getObject();\r
+                    map.add(predicate, new Resource[] {subject, obj});\r
+                } catch (Throwable e) {\r
+                    ErrorLogger.defaultLogError("Cannot find statement " + subject + " " + predicate + " " + obj, e);\r
+                }\r
+            }\r
+            SerialisationSupport ss = graph.getSession().getService(SerialisationSupport.class);\r
+            ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);\r
+            content.append("<tr><td class=\"top_key\">Identifiers</td><td class=\"top_value\">");\r
+            content.append("<span id=\"resource_id\">")\r
+            .append(" RID = $").append(r.getResourceId())\r
+            .append(" Resource Key = ").append(ss.getTransientId(r))\r
+            .append(" CID = ").append(support.getCluster(r));\r
+            content.append("</span></td>");\r
+            if (immutable)\r
+                content.append("<td class=\"remove\">[IMMUTABLE]</td>");\r
+            content.append("</tr>\n");\r
\r
+            boolean isClusterSet = support.isClusterSet(r);\r
+            Resource parentSet = support.getClusterSetOfCluster(r);\r
+            String parentSetURI = parentSet != null ? graph.getPossibleURI(parentSet) : null;\r
+            \r
+            content.append("<tr><td class=\"top_key\">Clustering</td><td class=\"top_value\">");\r
+            content.append("<span id=\"resource_id\">");\r
+            \r
+            if(parentSetURI != null)\r
+                content.append(" Containing cluster set = ").append(parentSetURI);\r
+            else if (parentSet != null)\r
+               content.append(" Containing cluster set = ").append(parentSet.toString());\r
+            else \r
+                content.append(" Not in any cluster set ");\r
+            \r
+            content.append("</span></td>");\r
+            if (isClusterSet)\r
+                content.append("<td class=\"remove\">[CLUSTER SET]</td>");\r
+            content.append("</tr>\n");\r
+            \r
+            // If the resource has a value, show it.\r
+            String resourceValue = getResourceValue(graph, r);\r
+            if (resourceValue != null) {\r
+                content\r
+                .append("<tr><td class=\"top_key\">Attached value</td><td class=\"top_value\">")\r
+                .append(htmlEscape(resourceValue))\r
+                .append("</td></tr>\n");\r
+            }\r
+\r
+            // Close #top\r
+            content.append("</table>\n");\r
+            content.append("</div>\n");\r
+\r
+            content.append("\n<div id=\"data\">\n");\r
+            content.append("<table>\n")\r
+            .append("<tr><th>Predicate</th><th>Object</th><th>Graph</th></tr>")\r
+            .append("<tr><td class=\"subtitle\" colspan=\"3\">Basic information</td></tr>");\r
+\r
+            boolean isOrderedSet = graph.isInstanceOf(r, L0.OrderedSet);\r
+            boolean isLinkedList = graph.isInstanceOf(r, L0.List);\r
+//            map.remove(r);\r
+\r
+            // BASIC INFORMATION:\r
+            for (Resource pred :\r
+                new Resource[] {L0.HasName, L0.InstanceOf,\r
+                    L0.Inherits, L0.SubrelationOf,\r
+                    L0.PartOf, L0.ConsistsOf})\r
+                if (map.containsKey(pred))\r
+                    updatePred(content, graph, r, pred, map.remove(pred));\r
+\r
+            // TAGS\r
+            content.append("<tr><td class=\"subtitle\" colspan=\"3\">Tags</td></tr>");\r
+            for(Statement stm : statements) {\r
+                if(stm.getSubject().equals(stm.getObject())) {\r
+                    updateTag(content, graph, r, stm.getPredicate());\r
+                    map.remove(stm.getPredicate());\r
+                }\r
+            }\r
+\r
+            // ORDERED SETS\r
+            content.append("<tr><td class=\"subtitle\" colspan=\"3\">Ordered Sets</td></tr>");\r
+            for(Statement stm : statements) {\r
+                Resource predicate = stm.getPredicate();\r
+                if(graph.isInstanceOf(stm.getPredicate(), L0.OrderedSet)) {\r
+                    updateTag(content, graph, r, stm.getPredicate());\r
+                    if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)\r
+                        map.remove(stm.getPredicate());\r
+                }\r
+                Resource inverse = graph.getPossibleInverse(predicate);\r
+                if (inverse != null) {\r
+                    if(graph.isInstanceOf(inverse, L0.OrderedSet)) {\r
+                        if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)\r
+                            map.remove(stm.getPredicate());\r
+                    }\r
+                } else {\r
+                    // FIXME : should we infor missing inverse\r
+                }\r
+            }\r
+\r
+            // IS RELATED TO\r
+            content.append("<tr><td class=\"subtitle\" colspan=\"3\">Is Related To</td></tr>");\r
+\r
+            // ELEMENTS OF ORDERED SET\r
+            if(isOrderedSet) {\r
+                //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");\r
+                try {\r
+                    updateOrderedSet(content, graph, r);\r
+                } catch (ValidationException e) {\r
+                    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>");\r
+                }\r
+            }\r
+\r
+            // ELEMENTS OF LINKED LIST\r
+            if(isLinkedList) {\r
+                //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");\r
+                try {\r
+                    updateLinkedList(content, graph, r);\r
+                } catch (ValidationException e) {\r
+                    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>");\r
+                }\r
+            }\r
+\r
+            // IS RELATED TO (other)\r
+            Resource[] preds = map.keySet().toArray(new Resource[0]);\r
+            final Map<Resource, String> strmap = new HashMap<Resource, String>(preds.length);\r
+            for(Resource pred : preds) {\r
+                String str = htmlEscape( getResourceName(graph, pred) );\r
+                if(str == null)\r
+                    str = "<null>";\r
+                strmap.put(pred, str);\r
+            }\r
+            Arrays.sort(preds, new Comparator<Resource>() {\r
+                @Override\r
+                public int compare(Resource o1, Resource o2) {\r
+                    return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(strmap.get(o1), strmap.get(o2));\r
+                }\r
+            });\r
+            for(Resource pred : preds)\r
+                if(graph.isSubrelationOf(pred, L0.IsRelatedTo))\r
+                    updatePred(content, graph, r, pred, map.get(pred));\r
+\r
+            // OTHER STATEMENTS\r
+            content.append("<tr><td class=\"subtitle\" colspan=\"3\">Other statements</td></tr>");\r
+            for(Resource pred : preds)\r
+                if(!graph.isSubrelationOf(pred, L0.IsRelatedTo))\r
+                    updatePred(content, graph, r, pred, map.get(pred));\r
+            content.append("</table>\n");\r
+        }\r
+        // Close #data\r
+        content.append("</div>\n\n");\r
+        // Close #mainContent\r
+        content.append("</div>\n");\r
+        content.append("</body>\n</html>\n");\r
+\r
+        // Update content\r
+        final String finalContent = content.toString();\r
+        if (!isDisposed()) {\r
+            getDisplay().asyncExec(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    if (!browser.isDisposed())\r
+                        browser.setText(finalContent);\r
+                }\r
+            });\r
+        }\r
+    }\r
+\r
+    private String getResourceValue(ReadGraph graph, Resource r) {\r
+        try {\r
+            if (graph.hasValue(r)) {\r
+                // too large array may cause application to run out of memory.\r
+                //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));\r
+                Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
+                if (type != null) {\r
+                    Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );\r
+                    if (type.equals( rviBinding.type() )) {\r
+                        RVI rvi = graph.getValue(r, rviBinding);\r
+                        try {\r
+                            Variable v = Variables.getConfigurationContext( graph, r );\r
+                            return rvi.asString(graph, v);\r
+                        } catch (DatabaseException dbe ) {\r
+                            return rvi.toString( graph );\r
+                        }\r
+                    } else {\r
+                        Binding b = Bindings.getBinding(type);\r
+                        Object v = graph.getValue(r, b);\r
+                        Serializer s = Bindings.getSerializerUnchecked(b);\r
+                        int size = s.getSize(v);\r
+                        if (size > RESOURCE_VALUE_MAX_SIZE) {\r
+                            return "Approx. " + size + " byte literal of type " + type.toSingleLineString();\r
+                        } else {\r
+                            return b.toString(v, false);\r
+                        }\r
+                    }\r
+                } else {\r
+                    Object o = graph.getValue(r);\r
+                    return toName(o);\r
+                }\r
+            }\r
+            return null;\r
+        } catch (DatabaseException e) {\r
+            return e.getMessage();\r
+        } catch (IOException e) {\r
+            return e.getMessage();\r
+        } catch (BindingException e) {\r
+            return e.getMessage();\r
+        }\r
+    }\r
+\r
+    private static String safeReadableString(ReadGraph g, Resource r) {\r
+        try {\r
+            return NameUtils.getSafeName(g, r);\r
+        } catch(Throwable throwable) {\r
+            ErrorLogger.defaultLogError(throwable);\r
+            return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";\r
+        }\r
+    }\r
+\r
+//    private static String safeReadableString(IEntity t) {\r
+//        try {\r
+//            return ResourceDebugUtils.getReadableNameForEntity(t);\r
+//        } catch(Throwable throwable) {\r
+//            throwable.printStackTrace();\r
+//            return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";\r
+//        }\r
+//    }\r
+\r
+    private String getLinkString(Container<Resource> t) {\r
+        String link = links.getLeft(t.get());\r
+        if(link == null) {\r
+            link = UUID.randomUUID().toString();\r
+            links.map(link, t.get());\r
+        }\r
+        return link;\r
+    }\r
+\r
+//    private String getPropertyEditString(Container<Resource> t) {\r
+//        String link = links.getLeft(t.get());\r
+//        if(link == null) {\r
+//            link = UUID.randomUUID().toString();\r
+//            links.map(link, t.get());\r
+//        }\r
+//        return link;\r
+//    }\r
+\r
+    private String getStatementString(Resource _s, Resource _p, Resource _o) {\r
+        String s = getLinkString(_s);\r
+        String p = getLinkString(_p);\r
+        String o = getLinkString(_o);\r
+        return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;\r
+    }\r
+\r
+//    private String getStatementString(Statement stm) {\r
+//        String s = getLinkString(stm.getSubject());\r
+//        String p = getLinkString(stm.getPredicate());\r
+//        String o = getLinkString(stm.getObject());\r
+//        return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;\r
+//    }\r
+\r
+//    private String toOutgoingTableRow(IEntity subject, Statement stm) {\r
+//        boolean isAcquired = !subject.equals(stm.getSubject());\r
+//\r
+//        String plainCell = "%s";\r
+//        String hrefCell = "<a href=\"%s\">%s</a>";\r
+//        String formatTemplate = isAcquired\r
+//                ? "<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>"\r
+//                : "<tr>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n</tr>";\r
+//        String format = NLS.bind(formatTemplate, plainCell, hrefCell);\r
+////        System.out.println("format: " + format);\r
+//\r
+//        IEntity s = stm.getSubject();\r
+//        IEntity p = stm.getPredicate();\r
+//        IEntity o = stm.getObject();\r
+//\r
+////        String timePart = "[" + timeToString(t.getBegin()) + ", " + timeToString(t.getEnd()) + "]";\r
+//\r
+//        try {\r
+//            return !isAcquired\r
+//                ? String.format(format,\r
+//                    "about:blank-remove" + getStatementString(stm), "Remove",\r
+//                    "about:blank-link" + getLinkString(s), safeReadableString(s),\r
+//                    "about:blank-link" + getLinkString(p), safeReadableString(p),\r
+//                    "about:blank-link" + getLinkString(o), safeReadableString(o))\r
+//                : String.format(format,\r
+//                    "Acquired",\r
+//                    "about:blank-link" + getLinkString(s), safeReadableString(s),\r
+//                    "about:blank-link" + getLinkString(p), safeReadableString(p),\r
+//                    "about:blank-link" + getLinkString(o), safeReadableString(o)\r
+//            );\r
+//        } catch (Throwable throwable) {\r
+//            return "<tr><td colspan=\"4\"><font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font></td></tr>";\r
+//        }\r
+//    }\r
+\r
+//    private String intervalToString(Interval time) {\r
+//        return timeToString(time.getBegin()) + ", " + timeToString(time.getEnd());\r
+//    }\r
+//\r
+//    DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);\r
+//\r
+//    private String timeToString(long time) {\r
+//        if (time == Long.MIN_VALUE)\r
+//            return "-&infin;";\r
+//        if (time == Long.MAX_VALUE)\r
+//            return "+&infin;";\r
+//        //return String.valueOf(time);\r
+//        Date d = new Date(time);\r
+//        return dateTimeFormat.format(d);\r
+//    }\r
+\r
+    private String getHead() {\r
+        String result = "";\r
+        if (cssPath != null) {\r
+            result = "<link href=\"" + cssPath + "\" rel=\"stylesheet\" type=\"text/css\">";\r
+        }\r
+        return result;\r
+    }\r
+\r
+}\r