X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.debug.ui%2Fsrc%2Forg%2Fsimantics%2Fdebug%2Fui%2FVariableDebugger.java;h=996efb19626617aed12596a868762fd1dca91690;hb=785f638bab44e70ec6103c3891daea95bcda9a07;hp=a3f305040ab24555f75a25eede6020801d0c4a0e;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git 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 index a3f305040..996efb196 100644 --- 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 @@ -1,758 +1,763 @@ -/******************************************************************************* - * 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.URL; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.TreeMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicReference; - -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.layout.GridDataFactory; -import org.eclipse.jface.resource.ColorDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -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.KeyAdapter; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Color; -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.simantics.databoard.type.Datatype; -import org.simantics.databoard.util.ObjectUtils; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.Session; -import org.simantics.db.common.ResourceArray; -import org.simantics.db.common.procedure.adapter.DisposableListener; -import org.simantics.db.common.request.UnaryRead; -import org.simantics.db.common.utils.Logger; -import org.simantics.db.exception.DatabaseException; -import org.simantics.db.layer0.SelectionHints; -import org.simantics.db.layer0.request.PossibleURI; -import org.simantics.db.layer0.request.ResourceURIToVariable; -import org.simantics.db.layer0.request.VariableURI; -import org.simantics.db.layer0.variable.AbstractChildVariable; -import org.simantics.db.layer0.variable.AbstractPropertyVariable; -import org.simantics.db.layer0.variable.Variable; -import org.simantics.db.layer0.variable.VariableNode; -import org.simantics.db.layer0.variable.Variables; -import org.simantics.db.service.SerialisationSupport; -import org.simantics.debug.ui.internal.Activator; -import org.simantics.layer0.Layer0; -import org.simantics.structural2.variables.Connection; -import org.simantics.structural2.variables.VariableConnectionPointDescriptor; -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.FileUtils; -import org.simantics.utils.bytes.Base64; -import org.simantics.utils.ui.ErrorLogger; -import org.simantics.utils.ui.ISelectionUtils; -import org.simantics.utils.ui.PathUtils; - - -/** - * @author Antti Villberg - * @author Tuukka Lehtonen - */ -public class VariableDebugger extends Composite { - - public interface HistoryListener { - void historyChanged(); - } - - private final static String DEFAULT_DEBUGGER_CSS_FILE = "debugger.css"; - private final static String DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE; - - private static int RESOURCE_NAME_MAX_LENGTH = 1000; - - private final Charset utf8 = Charset.forName("UTF-8"); - - private final LocalResourceManager resourceManager; - - private String cssPath; - - private Text updateTriggerCounter; - private Browser browser; - private final ColorDescriptor green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95)); - - private final LinkedList backHistory = new LinkedList(); - private final LinkedList forwardHistory = new LinkedList(); - private String 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 historyListeners = new CopyOnWriteArrayList(); - - protected Layer0 L0; - - protected boolean disposed; - - class PageContentListener extends DisposableListener { - int triggerCounter; - int updateCount; - AtomicReference lastResult = new AtomicReference(); - @Override - public void execute(final String content) { - ++triggerCounter; - //System.out.println("LISTENER TRIGGERED: " + triggerCounter); - //System.out.println("LISTENER:\n" + content); - if (lastResult.getAndSet(content) == null) { - if (!disposed) { - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - String content = lastResult.getAndSet(null); - if (content == null) - return; - - ++updateCount; - //System.out.println("UPDATE " + updateCount); - - if (!browser.isDisposed()) - browser.setText(content); - if (!updateTriggerCounter.isDisposed()) - updateTriggerCounter.setText(updateCount + "/" + triggerCounter); - } - }); - } - } - } - - @Override - public void exception(Throwable t) { - Logger.defaultLogError(t); - } - } - - private PageContentListener pageContentListener; - - /** - * @param parent - * @param style - * @param session - * @param resource the initial resource to debug or null for - * initially blank UI. - */ - public VariableDebugger(Composite parent, int style, final Session session, String initialURI) { - super(parent, style); - Assert.isNotNull(session, "session is null"); - this.session = session; - this.currentElement = initialURI; - this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); - - initializeCSS(); - - addDisposeListener(new DisposeListener() { - @Override - public void widgetDisposed(DisposeEvent e) { - disposed = true; - PageContentListener l = pageContentListener; - if (l != null) - l.dispose(); - } - }); - } - - public void defaultInitializeUI() { - setLayout(new GridLayout(4, false)); - setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - createDropLabel(this); - createResourceText(this); - createUpdateTriggerCounter(this); - Browser browser = createBrowser(this); - GridDataFactory.fillDefaults().span(4, 1).grab(true, true).applyTo(browser); - } - - 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 + "'"); - cssPath = FileUtils.copyResource(url, css, true).toURI().toString(); - } else { - cssPath = css.toURI().toString(); - } - } - } catch (IOException e) { - e.printStackTrace(); - // CSS extraction failed, let's just live without it then. - ErrorLogger.defaultLogWarning(e); - } - } - - public Label createDropLabel(Composite parent) { - final Label label = new Label(parent, SWT.BORDER | SWT.FLAT); - label.setAlignment(SWT.CENTER); - label.setText(" Drag a resource or a variable here to examine it in this debugger! "); - label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); - GridData data = new GridData(SWT.LEFT, SWT.FILL, false, false); - label.setLayoutData(data); - - // 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); - try { - String uri = parseUri(event); - if (uri == null) { - event.detail = DND.DROP_NONE; - return; - } - changeLocation(uri); - } catch (DatabaseException e) { - Logger.defaultLogError(e); - } - } - - private String parseUri(DropTargetEvent event) throws DatabaseException { - Variable v = parseVariable(event); - String uri = v != null ? session.sync(new VariableURI(v)) : null; - if (uri == null) { - Resource r = parseResource(event); - uri = r != null ? session.sync(new PossibleURI(r)) : null; - } - return uri; - } - - private Variable parseVariable(DropTargetEvent event) { - return ISelectionUtils.getSinglePossibleKey(event.data, SelectionHints.KEY_MAIN, Variable.class); - } - - private Resource parseResource(DropTargetEvent event) throws DatabaseException { - ResourceArray[] ra = null; - if (event.data instanceof String) { - try { - SerialisationSupport support = session.getService(SerialisationSupport.class); - ra = ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (DatabaseException e) { - e.printStackTrace(); - } - } else { - ra = ResourceAdaptionUtils.toResourceArrays(event.data); - } - if (ra != null && ra.length > 0) - return ra[0].resources[ra[0].resources.length - 1]; - return null; - } - }); - - return label; - } - - public void createResourceText(Composite parent) { - final Text text = new Text(parent, SWT.BORDER); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); - text.setLayoutData(data); - - Button button = new Button(parent, SWT.NONE); - button.setText("Lookup"); - GridData data2 = new GridData(SWT.FILL, SWT.FILL, false, false); - button.setLayoutData(data2); - - button.addSelectionListener(new SelectionListener() { - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - widgetSelected(e); - } - - @Override - public void widgetSelected(SelectionEvent e) { - - try { - String uri = text.getText(); - // Make sure that URI is resolvable to Variable - session.sync(new ResourceURIToVariable(uri)); - changeLocation(uri); - } catch (DatabaseException e1) { - Logger.defaultLogError(e1); - } - - } - - }); - } - - protected Text createUpdateTriggerCounter(Composite parent) { - Text label = new Text(parent, SWT.BORDER | SWT.FLAT); - label.setEditable(false); - label.setToolTipText("Amount of Screen/Listener Updates Received for Shown Variable"); - GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) - .grab(false, false).hint(32, SWT.DEFAULT).applyTo(label); - updateTriggerCounter = label; - return label; - } - - public Browser createBrowser(Composite parent) { - try { - browser = new Browser(parent, SWT.MOZILLA); - } catch (SWTError e) { - //System.out.println("Could not instantiate Browser: " + e.getMessage()); - browser = new Browser(parent, SWT.NONE); - } - browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - // Left/right arrows for back/forward - browser.addKeyListener(new KeyAdapter() { - @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.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")) - location = "about:" + location.substring(17); - //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)) { - // 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")) { - String target = location.replace("about:-link", ""); - try { - byte[] bytes = Base64.decode(target); - String url = new String(bytes, utf8); - if (url.equals(currentElement)) { - event.doit = false; - return; - } - changeLocation(url); - } catch (IOException e) { - ErrorLogger.defaultLogError(e); - } - } else if (location.startsWith("about:-remove")) { - } else if (location.startsWith("about:-edit-value")) { - } - } - }); - - // Schedule a request that updates the browser content. - refreshBrowser(); - - return browser; - } - - public void refreshBrowser() { - if (currentElement == null) - return; - - // Schedule a request that updates the browser content. - if (pageContentListener != null) - pageContentListener.dispose(); - pageContentListener = new PageContentListener(); - session.asyncRequest(new UnaryRead(currentElement) { - @Override - public String perform(ReadGraph graph) throws DatabaseException { - String content = calculateContent(graph, parameter); - //System.out.println("HTML: " + content); - return content; - } - }, pageContentListener); - - } - - public String getDebuggerLocation() { - return currentElement; - } - - public void changeLocation(String url) { - if (currentElement != null) { - backHistory.addLast(currentElement); - } - currentElement = url; - forwardHistory.clear(); - - refreshBrowser(); - 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); - } 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); - } 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); - } 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); - } 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); - } 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); - } 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); - } else { - return "Unknown big array " + o.getClass(); - } - } else { - return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o); - } - } - return null; - } - - protected String truncated(String type, String string, int originalLength) { - return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string; - } - - protected String getVariableName(ReadGraph graph, Variable r) { - try { - return r.getName(graph); - } catch (Exception e) { - return e.getMessage(); - } - } - - protected String getValue(ReadGraph graph, Variable base, Object o) throws DatabaseException { - Class clazz = o.getClass(); - if(o instanceof Connection) { - Connection c = (Connection)o; - ArrayList result = new ArrayList(); - for(VariableConnectionPointDescriptor v : c.getConnectionPointDescriptors(graph, null)) { - result.add(v.getRelativeRVI(graph, base)); - } - return "c " + result.toString(); - } else if (clazz.isArray()) { - if(int[].class == clazz) { - return Arrays.toString((int[])o); - } else if(float[].class == clazz) { - return Arrays.toString((float[])o); - } else if(double[].class == clazz) { - return Arrays.toString((double[])o); - } else if(long[].class == clazz) { - return Arrays.toString((long[])o); - } else if(byte[].class == clazz) { - return Arrays.toString((byte[])o); - } else if(boolean[].class == clazz) { - return Arrays.toString((boolean[])o); - } else if(char[].class == clazz) { - return Arrays.toString((char[])o); - } else { - return Arrays.toString((Object[])o); - } - } - return o.toString(); - } - - protected String getValue(ReadGraph graph, Variable r) { - try { - Object value = r.getValue(graph); - if(value instanceof Resource) return getResourceRef(graph, (Resource)value); - else if (value instanceof Variable) return getVariableRef(graph, (Variable)value); - else return value != null ? getValue(graph, r, value) : "null"; - } catch (Throwable e) { - try { - Logger.defaultLogError("getValue " + r.getURI(graph), e); - } catch (DatabaseException e1) { - Logger.defaultLogError(e1); - } - return e.getMessage(); - } - } - - protected String getDatatype(ReadGraph graph, Variable r) { - try { - Datatype dt = r.getPossibleDatatype(graph); - return dt != null ? dt.toSingleLineString() : "undefined"; - } catch (Exception e) { - return e.getMessage(); - } - } - - private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException { - return getVariableRef(graph, graph.adapt(r, Variable.class)); - } - - private String getVariableRef(ReadGraph graph, Variable r) throws DatabaseException { - String ret = "" - + getVariableName(graph, r) - + ""; -// if (graph.isInstanceOf(r, L0.Literal)) { -// ret += " " -// + "(edit value)" -// + ""; -// } - return ret; - } - - private String getLinkString(ReadGraph graph, Variable t) throws DatabaseException { - try { - String uri = t.getURI(graph); - //return uri; - String encoded = Base64.encode(uri.getBytes(utf8)); - return encoded; - } catch (Exception e) { - Logger.defaultLogError(e); - return e.getMessage(); - } - } - - private void updateProperty(StringBuilder content, ReadGraph graph, Variable property) throws DatabaseException { -// try { -// System.out.println("update property " + property.getURI(graph)); -// } catch (Exception e) { -// e.printStackTrace(); -// } - content.append(""); - content.append("").append(getVariableRef(graph, property)).append(""); - content.append("").append(getValue(graph, property)).append(""); - content.append("").append(getDatatype(graph, property)).append(""); - content.append(""); - } - - protected String getRVIString(ReadGraph graph, Variable var) throws DatabaseException { - - try { - return var.getRVI(graph).toString(graph); - } catch (Throwable e) { - return "No RVI"; - } - - } - - protected synchronized String calculateContent(final ReadGraph graph, String... uris) throws DatabaseException { - - L0 = Layer0.getInstance(graph); - - StringBuilder content = new StringBuilder(); - - // Generate HTML -page - content.append("").append(getHead()).append("\n"); - content.append("\n"); - content.append("
\n"); - for (String uri : uris) { - //System.out.println("URI: " + uri); - Variable var = Variables.getPossibleVariable(graph, uri); - if (var == null) - continue; - - String rviString = getRVIString(graph, var); - Object node = null; - if(var instanceof AbstractChildVariable) { - VariableNode vn = ((AbstractChildVariable)var).node; - if(vn != null) node = vn.node; - } - if(var instanceof AbstractPropertyVariable) { - VariableNode vn = ((AbstractPropertyVariable)var).node; - if(vn != null) node = vn.node; - } - - // Begin #top DIV - content.append("
\n"); - content.append("\n"); - content.append("\n"); - content.append("\n"); - content.append("\n"); - content.append("\n"); - content.append("
URI").append(uri).append("
RVI").append(rviString).append("
Class").append(var.getClass().getCanonicalName()).append("
Solver node").append(node).append("
\n"); - content.append("
\n"); - // Close #top DIV - - // Content - TreeMap map = new TreeMap(); - try { - for(Variable child : var.getChildren(graph)) { - String name = getVariableName(graph, child); - map.put(name, child); - } - } catch (DatabaseException e) { - // This may happen if the Variable implementation is broken - ErrorLogger.defaultLogError("Broken variable child retrieval implementation or serious modelling error encountered. See exception for details.", e); - } - - TreeMap map2 = new TreeMap(); - try { - for(Variable child : var.getProperties(graph)) { - String name = getVariableName(graph, child); - map2.put(name, child); - } - } catch (DatabaseException e) { - // This may happen if the Variable implementation is broken - ErrorLogger.defaultLogError("Broken variable property retrieval implementation or serious modelling error encountered. See exception for details.", e); - } - - content.append("\n
\n"); - content.append("\n"); - - content.append(""); - for (Variable child : map.values()) { - content.append(""); - } - - content.append(""); - for (Variable property : map2.values()) { - updateProperty(content, graph, property); - } - // Close #data - content.append("\n\n"); - } - - // Close #mainContent - content.append("\n"); - content.append("\n"); - - // Update content - return content.toString(); - } - - private String getHead() { - String result = ""; - if (cssPath != null) { - result = ""; - } - return result; - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2020 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.URL; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; + +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.layout.GridDataFactory; +import org.eclipse.jface.resource.ColorDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +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.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +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.simantics.databoard.type.Datatype; +import org.simantics.databoard.util.ObjectUtils; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.common.ResourceArray; +import org.simantics.db.common.procedure.adapter.DisposableListener; +import org.simantics.db.common.request.UnaryRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.SelectionHints; +import org.simantics.db.layer0.request.PossibleURI; +import org.simantics.db.layer0.request.ResourceURIToVariable; +import org.simantics.db.layer0.request.VariableURI; +import org.simantics.db.layer0.variable.AbstractChildVariable; +import org.simantics.db.layer0.variable.AbstractPropertyVariable; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.layer0.variable.VariableNode; +import org.simantics.db.layer0.variable.Variables; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.debug.ui.internal.Activator; +import org.simantics.layer0.Layer0; +import org.simantics.structural2.variables.Connection; +import org.simantics.structural2.variables.VariableConnectionPointDescriptor; +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.FileUtils; +import org.simantics.utils.bytes.Base64; +import org.simantics.utils.ui.ErrorLogger; +import org.simantics.utils.ui.ISelectionUtils; +import org.simantics.utils.ui.PathUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * @author Antti Villberg + * @author Tuukka Lehtonen + */ +public class VariableDebugger extends Composite { + + private static final Logger LOGGER = LoggerFactory.getLogger(VariableDebugger.class); + + public interface HistoryListener { + void historyChanged(); + } + + 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 final Charset utf8 = Charset.forName("UTF-8"); //$NON-NLS-1$ + + private final LocalResourceManager resourceManager; + + private String cssPath; + + private Text updateTriggerCounter; + private Browser browser; + private final ColorDescriptor green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95)); + + private final LinkedList backHistory = new LinkedList(); + private final LinkedList forwardHistory = new LinkedList(); + private String 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 historyListeners = new CopyOnWriteArrayList(); + + protected Layer0 L0; + + protected boolean disposed; + + class PageContentListener extends DisposableListener { + int triggerCounter; + int updateCount; + AtomicReference lastResult = new AtomicReference(); + @Override + public void execute(final String content) { + ++triggerCounter; + //System.out.println("LISTENER TRIGGERED: " + triggerCounter); + //System.out.println("LISTENER:\n" + content); + if (lastResult.getAndSet(content) == null) { + if (!disposed) { + getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + String content = lastResult.getAndSet(null); + if (content == null) + return; + + ++updateCount; + //System.out.println("UPDATE " + updateCount); + + if (!browser.isDisposed()) + browser.setText(content); + if (!updateTriggerCounter.isDisposed()) + updateTriggerCounter.setText(updateCount + "/" + triggerCounter); //$NON-NLS-1$ + } + }); + } + } + } + + @Override + public void exception(Throwable t) { + LOGGER.error("Page content listener failed unexpectedly", t); + } + } + + private PageContentListener pageContentListener; + + /** + * @param parent + * @param style + * @param session + * @param resource the initial resource to debug or null for + * initially blank UI. + */ + public VariableDebugger(Composite parent, int style, final Session session, String initialURI) { + super(parent, style); + Assert.isNotNull(session, "session is null"); //$NON-NLS-1$ + this.session = session; + this.currentElement = initialURI; + this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); + + initializeCSS(); + + addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + disposed = true; + PageContentListener l = pageContentListener; + if (l != null) + l.dispose(); + } + }); + } + + public void defaultInitializeUI() { + setLayout(new GridLayout(4, false)); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + createDropLabel(this); + createResourceText(this); + createUpdateTriggerCounter(this); + Browser browser = createBrowser(this); + GridDataFactory.fillDefaults().span(4, 1).grab(true, true).applyTo(browser); + } + + 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) { + e.printStackTrace(); + // CSS extraction failed, let's just live without it then. + ErrorLogger.defaultLogWarning(e); + } + } + + public Label createDropLabel(Composite parent) { + final Label label = new Label(parent, SWT.BORDER | SWT.FLAT); + label.setAlignment(SWT.CENTER); + label.setText(Messages.VariableDebugger_DragResourceToDebugger); + label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); + GridData data = new GridData(SWT.LEFT, SWT.FILL, false, false); + label.setLayoutData(data); + + // 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); + String uri = null; + try { + uri = parseUri(event); + if (uri == null) { + event.detail = DND.DROP_NONE; + return; + } + changeLocation(uri); + } catch (DatabaseException e) { + LOGGER.error("Changing location to URI {} failed", uri, e); + } + } + + private String parseUri(DropTargetEvent event) throws DatabaseException { + Variable v = parseVariable(event); + String uri = v != null ? session.sync(new VariableURI(v)) : null; + if (uri == null) { + Resource r = parseResource(event); + uri = r != null ? session.sync(new PossibleURI(r)) : null; + } + return uri; + } + + private Variable parseVariable(DropTargetEvent event) { + return ISelectionUtils.getSinglePossibleKey(event.data, SelectionHints.KEY_MAIN, Variable.class); + } + + private Resource parseResource(DropTargetEvent event) throws DatabaseException { + ResourceArray[] ra = null; + if (event.data instanceof String) { + try { + SerialisationSupport support = session.getService(SerialisationSupport.class); + ra = ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (DatabaseException e) { + e.printStackTrace(); + } + } else { + ra = ResourceAdaptionUtils.toResourceArrays(event.data); + } + if (ra != null && ra.length > 0) + return ra[0].resources[ra[0].resources.length - 1]; + return null; + } + }); + + return label; + } + + public void createResourceText(Composite parent) { + final Text text = new Text(parent, SWT.BORDER); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, false); + text.setLayoutData(data); + + Button button = new Button(parent, SWT.NONE); + button.setText(Messages.VariableDebugger_Lookup); + GridData data2 = new GridData(SWT.FILL, SWT.FILL, false, false); + button.setLayoutData(data2); + + button.addSelectionListener(new SelectionListener() { + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + } + + @Override + public void widgetSelected(SelectionEvent e) { + + String uri = null; + try { + uri = text.getText(); + // Make sure that URI is resolvable to Variable + session.sync(new ResourceURIToVariable(uri)); + changeLocation(uri); + } catch (DatabaseException e1) { + LOGGER.error("Lookup failed for URI {}", uri, e1); + } + + } + + }); + } + + protected Text createUpdateTriggerCounter(Composite parent) { + Text label = new Text(parent, SWT.BORDER | SWT.FLAT); + label.setEditable(false); + label.setToolTipText(Messages.VariableDebugger_TextToolTip); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL) + .grab(false, false).hint(32, SWT.DEFAULT).applyTo(label); + updateTriggerCounter = label; + return label; + } + + public Browser createBrowser(Composite parent) { + try { + browser = new Browser(parent, SWT.NONE); + } catch (SWTError e) { + //System.out.println("Could not instantiate Browser: " + e.getMessage()); + browser = new Browser(parent, SWT.NONE); + } + browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + // Left/right arrows for back/forward + browser.addKeyListener(new KeyAdapter() { + @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.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$ + try { + byte[] bytes = Base64.decode(target); + String url = new String(bytes, utf8); + if (url.equals(currentElement)) { + event.doit = false; + return; + } + changeLocation(url); + } catch (IOException e) { + ErrorLogger.defaultLogError(e); + } + } else if (location.startsWith("about:-remove")) { //$NON-NLS-1$ + } else if (location.startsWith("about:-edit-value")) { //$NON-NLS-1$ + } + } + }); + + // Schedule a request that updates the browser content. + refreshBrowser(); + + return browser; + } + + public void refreshBrowser() { + if (currentElement == null) + return; + + // Schedule a request that updates the browser content. + if (pageContentListener != null) + pageContentListener.dispose(); + pageContentListener = new PageContentListener(); + session.asyncRequest(new UnaryRead(currentElement) { + @Override + public String perform(ReadGraph graph) throws DatabaseException { + String content = calculateContent(graph, parameter); + //System.out.println("HTML: " + content); + return content; + } + }, pageContentListener); + + } + + public String getDebuggerLocation() { + return currentElement; + } + + public void changeLocation(String url) { + if (currentElement != null) { + backHistory.addLast(currentElement); + } + currentElement = url; + forwardHistory.clear(); + + refreshBrowser(); + 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$ + } + + protected String getVariableName(ReadGraph graph, Variable r) { + try { + return r.getName(graph); + } catch (Exception e) { + return e.getMessage(); + } + } + + protected String getValue(ReadGraph graph, Variable base, Object o) throws DatabaseException { + Class clazz = o.getClass(); + if(o instanceof Connection) { + Connection c = (Connection)o; + TreeSet rvis = new TreeSet<>(); + for(VariableConnectionPointDescriptor v : c.getConnectionPointDescriptors(graph, null)) { + rvis.add(v.getRelativeRVI(graph, base)); + } + return "c " + rvis.toString(); //$NON-NLS-1$ + } else if (clazz.isArray()) { + if(int[].class == clazz) { + return Arrays.toString((int[])o); + } else if(float[].class == clazz) { + return Arrays.toString((float[])o); + } else if(double[].class == clazz) { + return Arrays.toString((double[])o); + } else if(long[].class == clazz) { + return Arrays.toString((long[])o); + } else if(byte[].class == clazz) { + return Arrays.toString((byte[])o); + } else if(boolean[].class == clazz) { + return Arrays.toString((boolean[])o); + } else if(char[].class == clazz) { + return Arrays.toString((char[])o); + } else { + return Arrays.toString((Object[])o); + } + } + return o.toString(); + } + + protected String getValue(ReadGraph graph, Variable r) { + try { + Object value = r.getValue(graph); + if(value instanceof Resource) return getResourceRef(graph, (Resource)value); + else if (value instanceof Variable) return getVariableRef(graph, (Variable)value); + else return value != null ? getValue(graph, r, value) : "null"; //$NON-NLS-1$ + } catch (Throwable e) { + try { + LOGGER.error("getValue({})", r.getURI(graph), e); //$NON-NLS-1$ + } catch (DatabaseException e1) { + LOGGER.error("Failed to get URI for problematic value variable", e1); + } + return e.getMessage(); + } + } + + protected String getDatatype(ReadGraph graph, Variable r) { + try { + Datatype dt = r.getPossibleDatatype(graph); + return dt != null ? dt.toSingleLineString() : "undefined"; //$NON-NLS-1$ + } catch (Exception e) { + return e.getMessage(); + } + } + + private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException { + return getVariableRef(graph, graph.adapt(r, Variable.class)); + } + + private String getVariableRef(ReadGraph graph, Variable r) throws DatabaseException { + String ret = "" //$NON-NLS-1$ //$NON-NLS-2$ + + getVariableName(graph, r) + + ""; //$NON-NLS-1$ +// if (graph.isInstanceOf(r, L0.Literal)) { +// ret += " " +// + "(edit value)" +// + ""; +// } + return ret; + } + + private String getLinkString(ReadGraph graph, Variable t) throws DatabaseException { + try { + String uri = t.getURI(graph); + //return uri; + String encoded = Base64.encode(uri.getBytes(utf8)); + return encoded; + } catch (Exception e) { + LOGGER.error("Failed to construct link string for variable", e); //$NON-NLS-1$ + return e.getMessage(); + } + } + + private void updateProperty(StringBuilder content, ReadGraph graph, Variable property) throws DatabaseException { +// try { +// System.out.println("update property " + property.getURI(graph)); +// } catch (Exception e) { +// e.printStackTrace(); +// } + content.append(""); //$NON-NLS-1$ + content.append(""); //$NON-NLS-1$ //$NON-NLS-2$ + content.append(""); //$NON-NLS-1$ //$NON-NLS-2$ + content.append(""); //$NON-NLS-1$ //$NON-NLS-2$ + content.append(""); //$NON-NLS-1$ + } + + protected String getRVIString(ReadGraph graph, Variable var) throws DatabaseException { + + try { + return var.getRVI(graph).toString(graph); + } catch (Throwable e) { + return "No RVI"; //$NON-NLS-1$ + } + + } + + protected synchronized String calculateContent(final ReadGraph graph, String... uris) throws DatabaseException { + + L0 = Layer0.getInstance(graph); + + StringBuilder content = new StringBuilder(); + + // Generate HTML -page + content.append("").append(getHead()).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + content.append("\n"); //$NON-NLS-1$ + content.append("
\n"); //$NON-NLS-1$ + for (String uri : uris) { + //System.out.println("URI: " + uri); + Variable var = Variables.getPossibleVariable(graph, uri); + if (var == null) + continue; + + String rviString = getRVIString(graph, var); + Object node = null; + if(var instanceof AbstractChildVariable) { + VariableNode vn = ((AbstractChildVariable)var).node; + if(vn != null) node = vn.node; + } + if(var instanceof AbstractPropertyVariable) { + VariableNode vn = ((AbstractPropertyVariable)var).node; + if(vn != null) node = vn.node; + } + + // Begin #top DIV + content.append("
\n"); //$NON-NLS-1$ + content.append("
Child
").append(getVariableRef(graph, child)).append("
PropertyValueDatatype
").append(getVariableRef(graph, property)).append("").append(getValue(graph, property)).append("").append(getDatatype(graph, property)).append("
\n"); //$NON-NLS-1$ + content.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + content.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + content.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + content.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + content.append("
URI").append(uri).append("
RVI").append(rviString).append("
Class").append(var.getClass().getCanonicalName()).append("
Solver node").append(node).append("
\n"); //$NON-NLS-1$ + content.append("
\n"); //$NON-NLS-1$ + // Close #top DIV + + // Content + TreeMap map = new TreeMap(); + try { + for(Variable child : var.getChildren(graph)) { + String name = getVariableName(graph, child); + map.put(name, child); + } + } catch (DatabaseException e) { + // This may happen if the Variable implementation is broken + ErrorLogger.defaultLogError("Broken variable child retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$ + } + + TreeMap map2 = new TreeMap(); + try { + for(Variable child : var.getProperties(graph)) { + String name = getVariableName(graph, child); + map2.put(name, child); + } + } catch (DatabaseException e) { + // This may happen if the Variable implementation is broken + ErrorLogger.defaultLogError("Broken variable property retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$ + } + + content.append("\n
\n"); //$NON-NLS-1$ + content.append("\n"); //$NON-NLS-1$ + + content.append(""); //$NON-NLS-1$ + for (Variable child : map.values()) { + content.append(""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + content.append(""); //$NON-NLS-1$ + for (Variable property : map2.values()) { + updateProperty(content, graph, property); + } + // Close #data + content.append("\n\n"); //$NON-NLS-1$ + } + + // Close #mainContent + content.append("\n"); //$NON-NLS-1$ + content.append("\n"); //$NON-NLS-1$ + + // Update content + return content.toString(); + } + + private String getHead() { + String result = ""; //$NON-NLS-1$ + if (cssPath != null) { + result = ""; //$NON-NLS-1$ //$NON-NLS-2$ + } + return result; + } + +}
Child
").append(getVariableRef(graph, child)).append("
PropertyValueDatatype