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