]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/VariableDebugger.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / VariableDebugger.java
diff --git a/bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/VariableDebugger.java b/bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/VariableDebugger.java
new file mode 100644 (file)
index 0000000..a3f3050
--- /dev/null
@@ -0,0 +1,758 @@
+/*******************************************************************************\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.URL;\r
+import java.nio.charset.Charset;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.LinkedList;\r
+import java.util.TreeMap;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.atomic.AtomicReference;\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.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.KeyAdapter;\r
+import org.eclipse.swt.events.KeyEvent;\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.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.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.common.ResourceArray;\r
+import org.simantics.db.common.procedure.adapter.DisposableListener;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.SelectionHints;\r
+import org.simantics.db.layer0.request.PossibleURI;\r
+import org.simantics.db.layer0.request.ResourceURIToVariable;\r
+import org.simantics.db.layer0.request.VariableURI;\r
+import org.simantics.db.layer0.variable.AbstractChildVariable;\r
+import org.simantics.db.layer0.variable.AbstractPropertyVariable;\r
+import org.simantics.db.layer0.variable.Variable;\r
+import org.simantics.db.layer0.variable.VariableNode;\r
+import org.simantics.db.layer0.variable.Variables;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.debug.ui.internal.Activator;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.structural2.variables.Connection;\r
+import org.simantics.structural2.variables.VariableConnectionPointDescriptor;\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.FileUtils;\r
+import org.simantics.utils.bytes.Base64;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+import org.simantics.utils.ui.ISelectionUtils;\r
+import org.simantics.utils.ui.PathUtils;\r
+\r
+\r
+/**\r
+ * @author Antti Villberg\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class VariableDebugger extends Composite {\r
+\r
+    public interface HistoryListener {\r
+        void historyChanged();\r
+    }\r
+\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
+\r
+       private final Charset                               utf8 = Charset.forName("UTF-8");\r
+\r
+    private final LocalResourceManager                  resourceManager;\r
+\r
+    private String                                      cssPath;\r
+\r
+    private Text                                        updateTriggerCounter; \r
+    private Browser                                     browser;\r
+    private final ColorDescriptor                       green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));\r
+\r
+    private final LinkedList<String>                    backHistory               = new LinkedList<String>();\r
+    private final LinkedList<String>                    forwardHistory            = new LinkedList<String>();\r
+    private String                                      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
+    protected Layer0                                    L0;\r
+\r
+    protected boolean                                   disposed;\r
+\r
+    class PageContentListener extends DisposableListener<String> {\r
+        int triggerCounter;\r
+        int updateCount;\r
+        AtomicReference<String> lastResult = new AtomicReference<String>();\r
+        @Override\r
+        public void execute(final String content) {\r
+            ++triggerCounter;\r
+            //System.out.println("LISTENER TRIGGERED: " + triggerCounter);\r
+            //System.out.println("LISTENER:\n" + content);\r
+            if (lastResult.getAndSet(content) == null) {\r
+                if (!disposed) {\r
+                    getDisplay().asyncExec(new Runnable() {\r
+                        @Override\r
+                        public void run() {\r
+                            String content = lastResult.getAndSet(null);\r
+                            if (content == null)\r
+                                return;\r
+\r
+                            ++updateCount;\r
+                            //System.out.println("UPDATE " + updateCount);\r
+\r
+                            if (!browser.isDisposed())\r
+                                browser.setText(content);\r
+                            if (!updateTriggerCounter.isDisposed())\r
+                                updateTriggerCounter.setText(updateCount + "/" + triggerCounter);\r
+                        }\r
+                    });\r
+                }\r
+            }\r
+        }\r
+\r
+        @Override\r
+        public void exception(Throwable t) {\r
+            Logger.defaultLogError(t);\r
+        }\r
+    }\r
+\r
+    private PageContentListener pageContentListener;\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 VariableDebugger(Composite parent, int style, final Session session, String initialURI) {\r
+        super(parent, style);\r
+        Assert.isNotNull(session, "session is null");\r
+        this.session = session;\r
+        this.currentElement = initialURI;\r
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);\r
+\r
+        initializeCSS();\r
+\r
+        addDisposeListener(new DisposeListener() {\r
+            @Override\r
+            public void widgetDisposed(DisposeEvent e) {\r
+                disposed = true;\r
+                PageContentListener l = pageContentListener;\r
+                if (l != null)\r
+                    l.dispose();\r
+            }\r
+        });\r
+    }\r
+\r
+    public void defaultInitializeUI() {\r
+        setLayout(new GridLayout(4, false));\r
+        setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));\r
+\r
+        createDropLabel(this);\r
+        createResourceText(this);\r
+        createUpdateTriggerCounter(this);\r
+        Browser browser = createBrowser(this);\r
+        GridDataFactory.fillDefaults().span(4, 1).grab(true, true).applyTo(browser);\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
+            e.printStackTrace();\r
+            // CSS extraction failed, let's just live without it then.\r
+            ErrorLogger.defaultLogWarning(e);\r
+        }\r
+    }\r
+\r
+    public Label createDropLabel(Composite parent) {\r
+        final Label label = new Label(parent, SWT.BORDER | SWT.FLAT);\r
+        label.setAlignment(SWT.CENTER);\r
+        label.setText("  Drag a resource or a variable here to examine it in this debugger!  ");\r
+        label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
+        GridData data = new GridData(SWT.LEFT, SWT.FILL, false, false);\r
+        label.setLayoutData(data);\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
+                try {\r
+                    String uri = parseUri(event);\r
+                    if (uri == null) {\r
+                        event.detail = DND.DROP_NONE;\r
+                        return;\r
+                    }\r
+                    changeLocation(uri);\r
+                } catch (DatabaseException e) {\r
+                    Logger.defaultLogError(e);\r
+                }\r
+            }\r
+\r
+            private String parseUri(DropTargetEvent event) throws DatabaseException {\r
+                Variable v = parseVariable(event);\r
+                String uri = v != null ? session.sync(new VariableURI(v)) : null;\r
+                if (uri == null) {\r
+                    Resource r = parseResource(event);\r
+                    uri = r != null ? session.sync(new PossibleURI(r)) : null;\r
+                }\r
+                return uri;\r
+            }\r
+\r
+            private Variable parseVariable(DropTargetEvent event) {\r
+                return ISelectionUtils.getSinglePossibleKey(event.data, SelectionHints.KEY_MAIN, Variable.class);\r
+            }\r
+\r
+            private Resource parseResource(DropTargetEvent event) throws DatabaseException {\r
+                ResourceArray[] ra = null;\r
+                if (event.data instanceof String) {\r
+                    try {\r
+                        SerialisationSupport support = session.getService(SerialisationSupport.class);\r
+                        ra = ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();\r
+                    } catch (IllegalArgumentException e) {\r
+                        e.printStackTrace();\r
+                    } catch (DatabaseException e) {\r
+                        e.printStackTrace();\r
+                    }\r
+                } else {\r
+                    ra = ResourceAdaptionUtils.toResourceArrays(event.data);\r
+                }\r
+                if (ra != null && ra.length > 0)\r
+                    return ra[0].resources[ra[0].resources.length - 1];\r
+                return null;\r
+            }\r
+        });\r
+\r
+        return label;\r
+    }\r
+\r
+    public void createResourceText(Composite parent) {\r
+        final Text text = new Text(parent, SWT.BORDER);\r
+        GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);\r
+        text.setLayoutData(data);\r
+\r
+        Button button = new Button(parent, SWT.NONE);\r
+        button.setText("Lookup");\r
+        GridData data2 = new GridData(SWT.FILL, SWT.FILL, false, false);\r
+        button.setLayoutData(data2);\r
+\r
+        button.addSelectionListener(new SelectionListener() {\r
+\r
+            @Override\r
+            public void widgetDefaultSelected(SelectionEvent e) {\r
+                widgetSelected(e);\r
+            }\r
+\r
+            @Override\r
+            public void widgetSelected(SelectionEvent e) {\r
+\r
+                try {\r
+                    String uri = text.getText();\r
+                    // Make sure that URI is resolvable to Variable\r
+                    session.sync(new ResourceURIToVariable(uri));\r
+                    changeLocation(uri);\r
+                } catch (DatabaseException e1) {\r
+                    Logger.defaultLogError(e1);\r
+                }\r
+\r
+            }\r
+\r
+        });\r
+    }\r
+\r
+    protected Text createUpdateTriggerCounter(Composite parent) {\r
+        Text label = new Text(parent, SWT.BORDER | SWT.FLAT);\r
+        label.setEditable(false);\r
+        label.setToolTipText("Amount of Screen/Listener Updates Received for Shown Variable");\r
+        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)\r
+        .grab(false, false).hint(32, SWT.DEFAULT).applyTo(label);\r
+        updateTriggerCounter = label;\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
+        browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));\r
+\r
+        // Left/right arrows for back/forward\r
+        browser.addKeyListener(new KeyAdapter() {\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
+                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
+                    try {\r
+                        byte[] bytes = Base64.decode(target);\r
+                        String url = new String(bytes, utf8);\r
+                        if (url.equals(currentElement)) {\r
+                            event.doit = false;\r
+                            return;\r
+                        }\r
+                        changeLocation(url);\r
+                    } catch (IOException e) {\r
+                        ErrorLogger.defaultLogError(e);\r
+                    }\r
+                } else if (location.startsWith("about:-remove")) {\r
+                } else if (location.startsWith("about:-edit-value")) {\r
+                }\r
+            }\r
+        });\r
+\r
+        // Schedule a request that updates the browser content.\r
+        refreshBrowser();\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
+        if (pageContentListener != null)\r
+            pageContentListener.dispose();\r
+        pageContentListener = new PageContentListener();\r
+        session.asyncRequest(new UnaryRead<String, String>(currentElement) {\r
+            @Override\r
+            public String perform(ReadGraph graph) throws DatabaseException {\r
+                String content = calculateContent(graph, parameter);\r
+                //System.out.println("HTML: " + content);\r
+                return content;\r
+            }\r
+        }, pageContentListener);\r
+\r
+    }\r
+\r
+    public String getDebuggerLocation() {\r
+        return currentElement;\r
+    }\r
+\r
+    public void changeLocation(String url) {\r
+        if (currentElement != null) {\r
+            backHistory.addLast(currentElement);\r
+        }\r
+        currentElement = url;\r
+        forwardHistory.clear();\r
+\r
+        refreshBrowser();\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
+    protected String getVariableName(ReadGraph graph, Variable r) {\r
+        try {\r
+            return r.getName(graph);\r
+        } catch (Exception e) {\r
+            return e.getMessage();\r
+        }\r
+    }\r
+\r
+    protected String getValue(ReadGraph graph, Variable base, Object o) throws DatabaseException {\r
+        Class<?> clazz = o.getClass();\r
+        if(o instanceof Connection) {\r
+            Connection c = (Connection)o;\r
+            ArrayList<String> result = new ArrayList<String>();\r
+            for(VariableConnectionPointDescriptor v : c.getConnectionPointDescriptors(graph, null)) {\r
+                result.add(v.getRelativeRVI(graph, base));\r
+            }\r
+            return "c " + result.toString();\r
+        } else if (clazz.isArray()) {\r
+            if(int[].class == clazz) {\r
+                return Arrays.toString((int[])o);\r
+            } else if(float[].class == clazz) {\r
+                return Arrays.toString((float[])o);\r
+            } else if(double[].class == clazz) {\r
+                return Arrays.toString((double[])o);\r
+            } else if(long[].class == clazz) {\r
+                return Arrays.toString((long[])o);\r
+            } else if(byte[].class == clazz) {\r
+                return Arrays.toString((byte[])o);\r
+            } else if(boolean[].class == clazz) {\r
+                return Arrays.toString((boolean[])o);\r
+            } else if(char[].class == clazz) {\r
+                return Arrays.toString((char[])o);\r
+            } else {\r
+                return Arrays.toString((Object[])o);\r
+            }\r
+        }\r
+        return o.toString();\r
+    }\r
+    \r
+    protected String getValue(ReadGraph graph, Variable r) {\r
+        try {\r
+            Object value = r.getValue(graph);\r
+            if(value instanceof Resource) return getResourceRef(graph, (Resource)value);\r
+            else if (value instanceof Variable) return getVariableRef(graph, (Variable)value);\r
+            else return value != null ? getValue(graph, r, value) : "null";\r
+        } catch (Throwable e) {\r
+            try {\r
+                Logger.defaultLogError("getValue " + r.getURI(graph), e);\r
+            } catch (DatabaseException e1) {\r
+                Logger.defaultLogError(e1);\r
+            }\r
+            return e.getMessage();\r
+        }\r
+    }\r
+\r
+    protected String getDatatype(ReadGraph graph, Variable r) {\r
+        try {\r
+            Datatype dt = r.getPossibleDatatype(graph);\r
+            return dt != null ? dt.toSingleLineString() : "undefined";\r
+        } catch (Exception e) {\r
+            return e.getMessage();\r
+        }\r
+    }\r
+\r
+    private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {\r
+        return getVariableRef(graph, graph.adapt(r, Variable.class));\r
+    }\r
+\r
+    private String getVariableRef(ReadGraph graph, Variable r) throws DatabaseException {\r
+        String ret = "<a href=\"simantics:browser-link" + getLinkString(graph, r) + "\">"\r
+        + getVariableName(graph, r)\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 value)"\r
+//            + "</a>";\r
+//        }\r
+        return ret;\r
+    }\r
+\r
+    private String getLinkString(ReadGraph graph, Variable t) throws DatabaseException {\r
+        try {\r
+            String uri = t.getURI(graph);\r
+            //return uri;\r
+            String encoded = Base64.encode(uri.getBytes(utf8));\r
+            return encoded;\r
+        } catch (Exception e) {\r
+            Logger.defaultLogError(e);\r
+            return e.getMessage();\r
+        }\r
+    }\r
+\r
+    private void updateProperty(StringBuilder content, ReadGraph graph, Variable property) throws DatabaseException {\r
+//        try {\r
+//            System.out.println("update property " + property.getURI(graph));\r
+//        } catch (Exception e) {\r
+//            e.printStackTrace();\r
+//        }\r
+        content.append("<tr>");\r
+        content.append("<td>").append(getVariableRef(graph, property)).append("</td>");\r
+        content.append("<td>").append(getValue(graph, property)).append("</td>");\r
+        content.append("<td>").append(getDatatype(graph, property)).append("</td>");\r
+        content.append("</tr>");\r
+    }\r
+\r
+    protected String getRVIString(ReadGraph graph, Variable var) throws DatabaseException {\r
+        \r
+        try {\r
+            return var.getRVI(graph).toString(graph);\r
+        } catch (Throwable e) {\r
+            return "No RVI";\r
+        }\r
+        \r
+    }\r
+    \r
+    protected synchronized String calculateContent(final ReadGraph graph, String... uris) throws DatabaseException {\r
+       \r
+        L0 = Layer0.getInstance(graph);\r
+\r
+        StringBuilder content = new StringBuilder();\r
+\r
+        // Generate HTML -page\r
+        content.append("<html><head>").append(getHead()).append("</head>\n");\r
+        content.append("<body>\n");\r
+        content.append("<div id=\"mainContent\">\n");\r
+        for (String uri : uris) {\r
+            //System.out.println("URI: " + uri);\r
+            Variable var = Variables.getPossibleVariable(graph, uri);\r
+            if (var == null)\r
+                continue;\r
+\r
+            String rviString = getRVIString(graph, var);\r
+            Object node = null;\r
+            if(var instanceof AbstractChildVariable) {\r
+                VariableNode vn = ((AbstractChildVariable)var).node; \r
+                if(vn != null) node = vn.node;\r
+            }\r
+            if(var instanceof AbstractPropertyVariable) {\r
+                VariableNode vn = ((AbstractPropertyVariable)var).node;\r
+                if(vn != null) node = vn.node;\r
+            }\r
+            \r
+            // Begin #top DIV\r
+            content.append("<div id=\"top\">\n");\r
+            content.append("<table class=\"top\">\n");\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
+            content.append("<tr><td class=\"top_key\">RVI</td><td class=\"top_value\"><span id=\"uri\">").append(rviString).append("</span></td></tr>\n");\r
+            content.append("<tr><td class=\"top_key\">Class</td><td class=\"top_value\"><span id=\"class\">").append(var.getClass().getCanonicalName()).append("</span></td></tr>\n");\r
+            content.append("<tr><td class=\"top_key\">Solver node</td><td class=\"top_value\"><span id=\"class\">").append(node).append("</span></td></tr>\n");\r
+            content.append("</table>\n");\r
+            content.append("</div>\n");\r
+            // Close #top DIV\r
+\r
+            // Content\r
+            TreeMap<String, Variable> map = new TreeMap<String, Variable>();\r
+            try {\r
+                for(Variable child : var.getChildren(graph)) {\r
+                    String name = getVariableName(graph, child);\r
+                    map.put(name, child);\r
+                }\r
+            } catch (DatabaseException e) {\r
+                // This may happen if the Variable implementation is broken\r
+                ErrorLogger.defaultLogError("Broken variable child retrieval implementation or serious modelling error encountered. See exception for details.", e);\r
+            }\r
+\r
+            TreeMap<String, Variable> map2 = new TreeMap<String, Variable>();\r
+            try {\r
+                for(Variable child : var.getProperties(graph)) {\r
+                    String name = getVariableName(graph, child);\r
+                    map2.put(name, child);\r
+                }\r
+            } catch (DatabaseException e) {\r
+                // This may happen if the Variable implementation is broken\r
+                ErrorLogger.defaultLogError("Broken variable property retrieval implementation or serious modelling error encountered. See exception for details.", e);\r
+            }\r
+\r
+            content.append("\n<div id=\"data\">\n");\r
+            content.append("<table>\n");\r
+\r
+            content.append("<tr><th>Child</th></tr>");\r
+            for (Variable child : map.values()) {\r
+                content.append("<tr><td>").append(getVariableRef(graph, child)).append("</td></tr>");\r
+            }\r
+\r
+            content.append("<tr><th>Property</th><th>Value</th><th>Datatype</th></tr>");\r
+            for (Variable property : map2.values()) {\r
+                updateProperty(content, graph, property);\r
+            }\r
+            // Close #data\r
+            content.append("</div>\n\n");\r
+        }\r
+\r
+        // Close #mainContent\r
+        content.append("</div>\n");\r
+        content.append("</body></html>\n");\r
+\r
+        // Update content\r
+        return content.toString();\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