1 /*******************************************************************************
2 * Copyright (c) 2007, 2020 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.debug.ui;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.lang.reflect.Array;
19 import java.nio.charset.Charset;
20 import java.util.Arrays;
21 import java.util.LinkedList;
22 import java.util.TreeMap;
23 import java.util.TreeSet;
24 import java.util.concurrent.CopyOnWriteArrayList;
25 import java.util.concurrent.atomic.AtomicReference;
27 import org.eclipse.core.runtime.Assert;
28 import org.eclipse.core.runtime.FileLocator;
29 import org.eclipse.core.runtime.IPath;
30 import org.eclipse.core.runtime.Path;
31 import org.eclipse.jface.layout.GridDataFactory;
32 import org.eclipse.jface.resource.ColorDescriptor;
33 import org.eclipse.jface.resource.JFaceResources;
34 import org.eclipse.jface.resource.LocalResourceManager;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.SWTError;
37 import org.eclipse.swt.browser.Browser;
38 import org.eclipse.swt.browser.LocationAdapter;
39 import org.eclipse.swt.browser.LocationEvent;
40 import org.eclipse.swt.dnd.DND;
41 import org.eclipse.swt.dnd.DropTarget;
42 import org.eclipse.swt.dnd.DropTargetAdapter;
43 import org.eclipse.swt.dnd.DropTargetEvent;
44 import org.eclipse.swt.dnd.TextTransfer;
45 import org.eclipse.swt.dnd.Transfer;
46 import org.eclipse.swt.events.DisposeEvent;
47 import org.eclipse.swt.events.DisposeListener;
48 import org.eclipse.swt.events.KeyAdapter;
49 import org.eclipse.swt.events.KeyEvent;
50 import org.eclipse.swt.events.SelectionEvent;
51 import org.eclipse.swt.events.SelectionListener;
52 import org.eclipse.swt.graphics.Color;
53 import org.eclipse.swt.graphics.RGB;
54 import org.eclipse.swt.layout.GridData;
55 import org.eclipse.swt.layout.GridLayout;
56 import org.eclipse.swt.widgets.Button;
57 import org.eclipse.swt.widgets.Composite;
58 import org.eclipse.swt.widgets.Label;
59 import org.eclipse.swt.widgets.Text;
60 import org.simantics.databoard.type.Datatype;
61 import org.simantics.databoard.util.ObjectUtils;
62 import org.simantics.db.ReadGraph;
63 import org.simantics.db.Resource;
64 import org.simantics.db.Session;
65 import org.simantics.db.common.ResourceArray;
66 import org.simantics.db.common.procedure.adapter.DisposableListener;
67 import org.simantics.db.common.request.UnaryRead;
68 import org.simantics.db.exception.DatabaseException;
69 import org.simantics.db.layer0.SelectionHints;
70 import org.simantics.db.layer0.request.PossibleURI;
71 import org.simantics.db.layer0.request.ResourceURIToVariable;
72 import org.simantics.db.layer0.request.VariableURI;
73 import org.simantics.db.layer0.variable.AbstractChildVariable;
74 import org.simantics.db.layer0.variable.AbstractPropertyVariable;
75 import org.simantics.db.layer0.variable.Variable;
76 import org.simantics.db.layer0.variable.VariableNode;
77 import org.simantics.db.layer0.variable.Variables;
78 import org.simantics.db.service.SerialisationSupport;
79 import org.simantics.debug.ui.internal.Activator;
80 import org.simantics.layer0.Layer0;
81 import org.simantics.structural2.variables.Connection;
82 import org.simantics.structural2.variables.VariableConnectionPointDescriptor;
83 import org.simantics.ui.dnd.LocalObjectTransfer;
84 import org.simantics.ui.dnd.ResourceReferenceTransfer;
85 import org.simantics.ui.dnd.ResourceTransferUtils;
86 import org.simantics.ui.utils.ResourceAdaptionUtils;
87 import org.simantics.utils.FileUtils;
88 import org.simantics.utils.bytes.Base64;
89 import org.simantics.utils.ui.ErrorLogger;
90 import org.simantics.utils.ui.ISelectionUtils;
91 import org.simantics.utils.ui.PathUtils;
92 import org.slf4j.Logger;
93 import org.slf4j.LoggerFactory;
97 * @author Antti Villberg
98 * @author Tuukka Lehtonen
100 public class VariableDebugger extends Composite {
102 private static final Logger LOGGER = LoggerFactory.getLogger(VariableDebugger.class);
104 public interface HistoryListener {
105 void historyChanged();
108 private final static String DEFAULT_DEBUGGER_CSS_FILE = "debugger.css"; //$NON-NLS-1$
109 private final static String DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE; //$NON-NLS-1$
111 private static int RESOURCE_NAME_MAX_LENGTH = 1000;
113 private final Charset utf8 = Charset.forName("UTF-8"); //$NON-NLS-1$
115 private final LocalResourceManager resourceManager;
117 private String cssPath;
119 private Text updateTriggerCounter;
120 private Browser browser;
121 private final ColorDescriptor green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));
123 private final LinkedList<String> backHistory = new LinkedList<String>();
124 private final LinkedList<String> forwardHistory = new LinkedList<String>();
125 private String currentElement = null;
128 * The Session used to access the graph. Received from outside of this
129 * class and therefore it is not disposed here, just used.
131 private final Session session;
133 private final CopyOnWriteArrayList<HistoryListener> historyListeners = new CopyOnWriteArrayList<HistoryListener>();
137 protected boolean disposed;
139 class PageContentListener extends DisposableListener<String> {
142 AtomicReference<String> lastResult = new AtomicReference<String>();
144 public void execute(final String content) {
146 //System.out.println("LISTENER TRIGGERED: " + triggerCounter);
147 //System.out.println("LISTENER:\n" + content);
148 if (lastResult.getAndSet(content) == null) {
150 getDisplay().asyncExec(new Runnable() {
153 String content = lastResult.getAndSet(null);
158 //System.out.println("UPDATE " + updateCount);
160 if (!browser.isDisposed())
161 browser.setText(content);
162 if (!updateTriggerCounter.isDisposed())
163 updateTriggerCounter.setText(updateCount + "/" + triggerCounter); //$NON-NLS-1$
171 public void exception(Throwable t) {
172 LOGGER.error("Page content listener failed unexpectedly", t);
176 private PageContentListener pageContentListener;
182 * @param resource the initial resource to debug or <code>null</code> for
183 * initially blank UI.
185 public VariableDebugger(Composite parent, int style, final Session session, String initialURI) {
186 super(parent, style);
187 Assert.isNotNull(session, "session is null"); //$NON-NLS-1$
188 this.session = session;
189 this.currentElement = initialURI;
190 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
194 addDisposeListener(new DisposeListener() {
196 public void widgetDisposed(DisposeEvent e) {
198 PageContentListener l = pageContentListener;
205 public void defaultInitializeUI() {
206 setLayout(new GridLayout(4, false));
207 setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
209 createDropLabel(this);
210 createResourceText(this);
211 createUpdateTriggerCounter(this);
212 Browser browser = createBrowser(this);
213 GridDataFactory.fillDefaults().span(4, 1).grab(true, true).applyTo(browser);
216 protected void initializeCSS() {
217 // Extract default css to a temporary location if necessary.
219 IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH);
220 if (absolutePath != null) {
221 cssPath = absolutePath.toFile().toURI().toString();
223 File tempDir = FileUtils.getOrCreateTemporaryDirectory(false);
224 File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE);
226 URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null);
228 throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
229 cssPath = FileUtils.copyResource(url, css, true).toURI().toString();
231 cssPath = css.toURI().toString();
234 } catch (IOException e) {
236 // CSS extraction failed, let's just live without it then.
237 ErrorLogger.defaultLogWarning(e);
241 public Label createDropLabel(Composite parent) {
242 final Label label = new Label(parent, SWT.BORDER | SWT.FLAT);
243 label.setAlignment(SWT.CENTER);
244 label.setText(Messages.VariableDebugger_DragResourceToDebugger);
245 label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
246 GridData data = new GridData(SWT.LEFT, SWT.FILL, false, false);
247 label.setLayoutData(data);
249 // Add resource id drop support to the drop-area.
250 DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY);
251 dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() });
252 dropTarget.addDropListener(new DropTargetAdapter() {
254 public void dragEnter(DropTargetEvent event) {
255 event.detail = DND.DROP_LINK;
256 label.setBackground((Color) resourceManager.get(green));
260 public void dragLeave(DropTargetEvent event) {
261 label.setBackground(null);
265 public void drop(DropTargetEvent event) {
266 label.setBackground(null);
269 uri = parseUri(event);
271 event.detail = DND.DROP_NONE;
275 } catch (DatabaseException e) {
276 LOGGER.error("Changing location to URI {} failed", uri, e);
280 private String parseUri(DropTargetEvent event) throws DatabaseException {
281 Variable v = parseVariable(event);
282 String uri = v != null ? session.sync(new VariableURI(v)) : null;
284 Resource r = parseResource(event);
285 uri = r != null ? session.sync(new PossibleURI(r)) : null;
290 private Variable parseVariable(DropTargetEvent event) {
291 return ISelectionUtils.getSinglePossibleKey(event.data, SelectionHints.KEY_MAIN, Variable.class);
294 private Resource parseResource(DropTargetEvent event) throws DatabaseException {
295 ResourceArray[] ra = null;
296 if (event.data instanceof String) {
298 SerialisationSupport support = session.getService(SerialisationSupport.class);
299 ra = ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();
300 } catch (IllegalArgumentException e) {
302 } catch (DatabaseException e) {
306 ra = ResourceAdaptionUtils.toResourceArrays(event.data);
308 if (ra != null && ra.length > 0)
309 return ra[0].resources[ra[0].resources.length - 1];
317 public void createResourceText(Composite parent) {
318 final Text text = new Text(parent, SWT.BORDER);
319 GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
320 text.setLayoutData(data);
322 Button button = new Button(parent, SWT.NONE);
323 button.setText(Messages.VariableDebugger_Lookup);
324 GridData data2 = new GridData(SWT.FILL, SWT.FILL, false, false);
325 button.setLayoutData(data2);
327 button.addSelectionListener(new SelectionListener() {
330 public void widgetDefaultSelected(SelectionEvent e) {
335 public void widgetSelected(SelectionEvent e) {
339 uri = text.getText();
340 // Make sure that URI is resolvable to Variable
341 session.sync(new ResourceURIToVariable(uri));
343 } catch (DatabaseException e1) {
344 LOGGER.error("Lookup failed for URI {}", uri, e1);
352 protected Text createUpdateTriggerCounter(Composite parent) {
353 Text label = new Text(parent, SWT.BORDER | SWT.FLAT);
354 label.setEditable(false);
355 label.setToolTipText(Messages.VariableDebugger_TextToolTip);
356 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
357 .grab(false, false).hint(32, SWT.DEFAULT).applyTo(label);
358 updateTriggerCounter = label;
362 public Browser createBrowser(Composite parent) {
364 browser = new Browser(parent, SWT.NONE);
365 } catch (SWTError e) {
366 //System.out.println("Could not instantiate Browser: " + e.getMessage());
367 browser = new Browser(parent, SWT.NONE);
369 browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
371 // Left/right arrows for back/forward
372 browser.addKeyListener(new KeyAdapter() {
374 public void keyReleased(KeyEvent e) {
375 // System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")");
376 // if (e.keyCode == SWT.BS) {
379 if ((e.stateMask & SWT.ALT) != 0) {
380 if (e.keyCode == SWT.ARROW_RIGHT)
382 if (e.keyCode == SWT.ARROW_LEFT)
388 // Add listener for debugging functionality
389 browser.addLocationListener(new LocationAdapter() {
391 public void changing(LocationEvent event) {
392 String location = event.location;
393 if (location.startsWith("simantics:browser")) //$NON-NLS-1$
394 location = "about:" + location.substring(17); //$NON-NLS-1$
395 //System.out.println("changing: location=" + location);
397 // Do not follow links that are meant as actions that are
400 if ("about:blank".equals(location)) { //$NON-NLS-1$
401 // Just changing to the same old blank url is ok since it
402 // allows the browser to refresh itself.
406 if (location.startsWith("about:-link")) { //$NON-NLS-1$
407 String target = location.replace("about:-link", ""); //$NON-NLS-1$ //$NON-NLS-2$
409 byte[] bytes = Base64.decode(target);
410 String url = new String(bytes, utf8);
411 if (url.equals(currentElement)) {
416 } catch (IOException e) {
417 ErrorLogger.defaultLogError(e);
419 } else if (location.startsWith("about:-remove")) { //$NON-NLS-1$
420 } else if (location.startsWith("about:-edit-value")) { //$NON-NLS-1$
425 // Schedule a request that updates the browser content.
431 public void refreshBrowser() {
432 if (currentElement == null)
435 // Schedule a request that updates the browser content.
436 if (pageContentListener != null)
437 pageContentListener.dispose();
438 pageContentListener = new PageContentListener();
439 session.asyncRequest(new UnaryRead<String, String>(currentElement) {
441 public String perform(ReadGraph graph) throws DatabaseException {
442 String content = calculateContent(graph, parameter);
443 //System.out.println("HTML: " + content);
446 }, pageContentListener);
450 public String getDebuggerLocation() {
451 return currentElement;
454 public void changeLocation(String url) {
455 if (currentElement != null) {
456 backHistory.addLast(currentElement);
458 currentElement = url;
459 forwardHistory.clear();
462 fireHistoryChanged();
465 public void addHistoryListener(HistoryListener l) {
466 historyListeners.add(l);
469 public void removeHistoryListener(HistoryListener l) {
470 historyListeners.remove(l);
473 private void fireHistoryChanged() {
474 for (HistoryListener l : historyListeners)
478 public boolean hasBackHistory() {
479 return backHistory.isEmpty();
482 public boolean hasForwardHistory() {
483 return forwardHistory.isEmpty();
487 if (backHistory.isEmpty())
490 forwardHistory.addFirst(currentElement);
491 currentElement = backHistory.removeLast();
494 fireHistoryChanged();
497 public void forward() {
498 if (forwardHistory.isEmpty())
501 backHistory.addLast(currentElement);
502 currentElement = forwardHistory.removeFirst();
505 fireHistoryChanged();
508 protected String toName(Object o) {
509 Class<?> clazz = o.getClass();
510 if (clazz.isArray()) {
511 int length = Array.getLength(o);
512 if (length > RESOURCE_NAME_MAX_LENGTH) {
513 if (o instanceof byte[]) {
514 byte[] arr = (byte[]) o;
515 byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
516 return truncated("byte", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
517 } else if (o instanceof int[]) {
518 int[] arr = (int[]) o;
519 int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
520 return truncated("int", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
521 } else if (o instanceof long[]) {
522 long[] arr = (long[]) o;
523 long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
524 return truncated("long", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
525 } else if (o instanceof float[]) {
526 float[] arr = (float[]) o;
527 float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
528 return truncated("float", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
529 } else if (o instanceof double[]) {
530 double[] arr = (double[]) o;
531 double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
532 return truncated("double", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
533 } else if (o instanceof boolean[]) {
534 boolean[] arr = (boolean[]) o;
535 boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
536 return truncated("boolean", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
537 } else if (o instanceof Object[]) {
538 Object[] arr = (Object[]) o;
539 Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
540 return truncated("Object", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
542 return "Unknown big array " + o.getClass(); //$NON-NLS-1$
545 return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o); //$NON-NLS-1$ //$NON-NLS-2$
551 protected String truncated(String type, String string, int originalLength) {
552 return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
555 protected String getVariableName(ReadGraph graph, Variable r) {
557 return r.getName(graph);
558 } catch (Exception e) {
559 return e.getMessage();
563 protected String getValue(ReadGraph graph, Variable base, Object o) throws DatabaseException {
564 Class<?> clazz = o.getClass();
565 if(o instanceof Connection) {
566 Connection c = (Connection)o;
567 TreeSet<String> rvis = new TreeSet<>();
568 for(VariableConnectionPointDescriptor v : c.getConnectionPointDescriptors(graph, null)) {
569 rvis.add(v.getRelativeRVI(graph, base));
571 return "c " + rvis.toString(); //$NON-NLS-1$
572 } else if (clazz.isArray()) {
573 if(int[].class == clazz) {
574 return Arrays.toString((int[])o);
575 } else if(float[].class == clazz) {
576 return Arrays.toString((float[])o);
577 } else if(double[].class == clazz) {
578 return Arrays.toString((double[])o);
579 } else if(long[].class == clazz) {
580 return Arrays.toString((long[])o);
581 } else if(byte[].class == clazz) {
582 return Arrays.toString((byte[])o);
583 } else if(boolean[].class == clazz) {
584 return Arrays.toString((boolean[])o);
585 } else if(char[].class == clazz) {
586 return Arrays.toString((char[])o);
588 return Arrays.toString((Object[])o);
594 protected String getValue(ReadGraph graph, Variable r) {
596 Object value = r.getValue(graph);
597 if(value instanceof Resource) return getResourceRef(graph, (Resource)value);
598 else if (value instanceof Variable) return getVariableRef(graph, (Variable)value);
599 else return value != null ? getValue(graph, r, value) : "null"; //$NON-NLS-1$
600 } catch (Throwable e) {
602 LOGGER.error("getValue({})", r.getURI(graph), e); //$NON-NLS-1$
603 } catch (DatabaseException e1) {
604 LOGGER.error("Failed to get URI for problematic value variable", e1);
606 return e.getMessage();
610 protected String getDatatype(ReadGraph graph, Variable r) {
612 Datatype dt = r.getPossibleDatatype(graph);
613 return dt != null ? dt.toSingleLineString() : "undefined"; //$NON-NLS-1$
614 } catch (Exception e) {
615 return e.getMessage();
619 private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {
620 return getVariableRef(graph, graph.adapt(r, Variable.class));
623 private String getVariableRef(ReadGraph graph, Variable r) throws DatabaseException {
624 String ret = "<a href=\"simantics:browser-link" + getLinkString(graph, r) + "\">" //$NON-NLS-1$ //$NON-NLS-2$
625 + getVariableName(graph, r)
626 + "</a>"; //$NON-NLS-1$
627 // if (graph.isInstanceOf(r, L0.Literal)) {
628 // ret += " <a class=\"edit-link\" href=\"simantics:browser-edit-value" + getLinkString(r) + "\">"
635 private String getLinkString(ReadGraph graph, Variable t) throws DatabaseException {
637 String uri = t.getURI(graph);
639 String encoded = Base64.encode(uri.getBytes(utf8));
641 } catch (Exception e) {
642 LOGGER.error("Failed to construct link string for variable", e); //$NON-NLS-1$
643 return e.getMessage();
647 private void updateProperty(StringBuilder content, ReadGraph graph, Variable property) throws DatabaseException {
649 // System.out.println("update property " + property.getURI(graph));
650 // } catch (Exception e) {
651 // e.printStackTrace();
653 content.append("<tr>"); //$NON-NLS-1$
654 content.append("<td>").append(getVariableRef(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
655 content.append("<td>").append(getValue(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
656 content.append("<td>").append(getDatatype(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
657 content.append("</tr>"); //$NON-NLS-1$
660 protected String getRVIString(ReadGraph graph, Variable var) throws DatabaseException {
663 return var.getRVI(graph).toString(graph);
664 } catch (Throwable e) {
665 return "No RVI"; //$NON-NLS-1$
670 protected synchronized String calculateContent(final ReadGraph graph, String... uris) throws DatabaseException {
672 L0 = Layer0.getInstance(graph);
674 StringBuilder content = new StringBuilder();
676 // Generate HTML -page
677 content.append("<html><head>").append(getHead()).append("</head>\n"); //$NON-NLS-1$ //$NON-NLS-2$
678 content.append("<body>\n"); //$NON-NLS-1$
679 content.append("<div id=\"mainContent\">\n"); //$NON-NLS-1$
680 for (String uri : uris) {
681 //System.out.println("URI: " + uri);
682 Variable var = Variables.getPossibleVariable(graph, uri);
686 String rviString = getRVIString(graph, var);
688 if(var instanceof AbstractChildVariable) {
689 VariableNode<?> vn = ((AbstractChildVariable)var).node;
690 if(vn != null) node = vn.node;
692 if(var instanceof AbstractPropertyVariable) {
693 VariableNode<?> vn = ((AbstractPropertyVariable)var).node;
694 if(vn != null) node = vn.node;
698 content.append("<div id=\"top\">\n"); //$NON-NLS-1$
699 content.append("<table class=\"top\">\n"); //$NON-NLS-1$
700 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$
701 content.append("<tr><td class=\"top_key\">RVI</td><td class=\"top_value\"><span id=\"uri\">").append(rviString).append("</span></td></tr>\n"); //$NON-NLS-1$ //$NON-NLS-2$
702 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"); //$NON-NLS-1$ //$NON-NLS-2$
703 content.append("<tr><td class=\"top_key\">Solver node</td><td class=\"top_value\"><span id=\"class\">").append(node).append("</span></td></tr>\n"); //$NON-NLS-1$ //$NON-NLS-2$
704 content.append("</table>\n"); //$NON-NLS-1$
705 content.append("</div>\n"); //$NON-NLS-1$
709 TreeMap<String, Variable> map = new TreeMap<String, Variable>();
711 for(Variable child : var.getChildren(graph)) {
712 String name = getVariableName(graph, child);
713 map.put(name, child);
715 } catch (DatabaseException e) {
716 // This may happen if the Variable implementation is broken
717 ErrorLogger.defaultLogError("Broken variable child retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$
720 TreeMap<String, Variable> map2 = new TreeMap<String, Variable>();
722 for(Variable child : var.getProperties(graph)) {
723 String name = getVariableName(graph, child);
724 map2.put(name, child);
726 } catch (DatabaseException e) {
727 // This may happen if the Variable implementation is broken
728 ErrorLogger.defaultLogError("Broken variable property retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$
731 content.append("\n<div id=\"data\">\n"); //$NON-NLS-1$
732 content.append("<table>\n"); //$NON-NLS-1$
734 content.append("<tr><th>Child</th></tr>"); //$NON-NLS-1$
735 for (Variable child : map.values()) {
736 content.append("<tr><td>").append(getVariableRef(graph, child)).append("</td></tr>"); //$NON-NLS-1$ //$NON-NLS-2$
739 content.append("<tr><th>Property</th><th>Value</th><th>Datatype</th></tr>"); //$NON-NLS-1$
740 for (Variable property : map2.values()) {
741 updateProperty(content, graph, property);
744 content.append("</div>\n\n"); //$NON-NLS-1$
747 // Close #mainContent
748 content.append("</div>\n"); //$NON-NLS-1$
749 content.append("</body></html>\n"); //$NON-NLS-1$
752 return content.toString();
755 private String getHead() {
756 String result = ""; //$NON-NLS-1$
757 if (cssPath != null) {
758 result = "<link href=\"" + cssPath + "\" rel=\"stylesheet\" type=\"text/css\">"; //$NON-NLS-1$ //$NON-NLS-2$