697a589395dae9eab29af679020ee53780d6b444
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / VariableDebugger.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.debug.ui;
13
14 import java.io.File;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.lang.reflect.Array;
18 import java.net.URL;
19 import java.nio.charset.Charset;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.LinkedList;
23 import java.util.TreeMap;
24 import java.util.concurrent.CopyOnWriteArrayList;
25 import java.util.concurrent.atomic.AtomicReference;
26
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.common.utils.Logger;
69 import org.simantics.db.exception.DatabaseException;
70 import org.simantics.db.layer0.SelectionHints;
71 import org.simantics.db.layer0.request.PossibleURI;
72 import org.simantics.db.layer0.request.ResourceURIToVariable;
73 import org.simantics.db.layer0.request.VariableURI;
74 import org.simantics.db.layer0.variable.AbstractChildVariable;
75 import org.simantics.db.layer0.variable.AbstractPropertyVariable;
76 import org.simantics.db.layer0.variable.Variable;
77 import org.simantics.db.layer0.variable.VariableNode;
78 import org.simantics.db.layer0.variable.Variables;
79 import org.simantics.db.service.SerialisationSupport;
80 import org.simantics.debug.ui.internal.Activator;
81 import org.simantics.layer0.Layer0;
82 import org.simantics.structural2.variables.Connection;
83 import org.simantics.structural2.variables.VariableConnectionPointDescriptor;
84 import org.simantics.ui.dnd.LocalObjectTransfer;
85 import org.simantics.ui.dnd.ResourceReferenceTransfer;
86 import org.simantics.ui.dnd.ResourceTransferUtils;
87 import org.simantics.ui.utils.ResourceAdaptionUtils;
88 import org.simantics.utils.FileUtils;
89 import org.simantics.utils.bytes.Base64;
90 import org.simantics.utils.ui.ErrorLogger;
91 import org.simantics.utils.ui.ISelectionUtils;
92 import org.simantics.utils.ui.PathUtils;
93
94
95 /**
96  * @author Antti Villberg
97  * @author Tuukka Lehtonen
98  */
99 public class VariableDebugger extends Composite {
100
101     public interface HistoryListener {
102         void historyChanged();
103     }
104
105     private final static String                         DEFAULT_DEBUGGER_CSS_FILE = "debugger.css"; //$NON-NLS-1$
106     private final static String                         DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE; //$NON-NLS-1$
107
108     private static int                                  RESOURCE_NAME_MAX_LENGTH  = 1000;
109
110         private final Charset                               utf8 = Charset.forName("UTF-8"); //$NON-NLS-1$
111
112     private final LocalResourceManager                  resourceManager;
113
114     private String                                      cssPath;
115
116     private Text                                        updateTriggerCounter; 
117     private Browser                                     browser;
118     private final ColorDescriptor                       green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));
119
120     private final LinkedList<String>                    backHistory               = new LinkedList<String>();
121     private final LinkedList<String>                    forwardHistory            = new LinkedList<String>();
122     private String                                      currentElement            = null;
123
124     /**
125      * The Session used to access the graph. Received from outside of this
126      * class and therefore it is not disposed here, just used.
127      */
128     private final Session                               session;
129
130     private final CopyOnWriteArrayList<HistoryListener> historyListeners          = new CopyOnWriteArrayList<HistoryListener>();
131
132     protected Layer0                                    L0;
133
134     protected boolean                                   disposed;
135
136     class PageContentListener extends DisposableListener<String> {
137         int triggerCounter;
138         int updateCount;
139         AtomicReference<String> lastResult = new AtomicReference<String>();
140         @Override
141         public void execute(final String content) {
142             ++triggerCounter;
143             //System.out.println("LISTENER TRIGGERED: " + triggerCounter);
144             //System.out.println("LISTENER:\n" + content);
145             if (lastResult.getAndSet(content) == null) {
146                 if (!disposed) {
147                     getDisplay().asyncExec(new Runnable() {
148                         @Override
149                         public void run() {
150                             String content = lastResult.getAndSet(null);
151                             if (content == null)
152                                 return;
153
154                             ++updateCount;
155                             //System.out.println("UPDATE " + updateCount);
156
157                             if (!browser.isDisposed())
158                                 browser.setText(content);
159                             if (!updateTriggerCounter.isDisposed())
160                                 updateTriggerCounter.setText(updateCount + "/" + triggerCounter); //$NON-NLS-1$
161                         }
162                     });
163                 }
164             }
165         }
166
167         @Override
168         public void exception(Throwable t) {
169             Logger.defaultLogError(t);
170         }
171     }
172
173     private PageContentListener pageContentListener;
174
175     /**
176      * @param parent
177      * @param style
178      * @param session
179      * @param resource the initial resource to debug or <code>null</code> for
180      *        initially blank UI.
181      */
182     public VariableDebugger(Composite parent, int style, final Session session, String initialURI) {
183         super(parent, style);
184         Assert.isNotNull(session, "session is null"); //$NON-NLS-1$
185         this.session = session;
186         this.currentElement = initialURI;
187         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
188
189         initializeCSS();
190
191         addDisposeListener(new DisposeListener() {
192             @Override
193             public void widgetDisposed(DisposeEvent e) {
194                 disposed = true;
195                 PageContentListener l = pageContentListener;
196                 if (l != null)
197                     l.dispose();
198             }
199         });
200     }
201
202     public void defaultInitializeUI() {
203         setLayout(new GridLayout(4, false));
204         setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
205
206         createDropLabel(this);
207         createResourceText(this);
208         createUpdateTriggerCounter(this);
209         Browser browser = createBrowser(this);
210         GridDataFactory.fillDefaults().span(4, 1).grab(true, true).applyTo(browser);
211     }
212
213     protected void initializeCSS() {
214         // Extract default css to a temporary location if necessary.
215         try {
216             IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH);
217             if (absolutePath != null) {
218                 cssPath = absolutePath.toFile().toURI().toString();
219             } else {
220                 File tempDir = FileUtils.getOrCreateTemporaryDirectory(false);
221                 File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE);
222                 if (!css.exists()) {
223                     URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null);
224                     if (url == null)
225                         throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
226                     cssPath = FileUtils.copyResource(url, css, true).toURI().toString();
227                 } else {
228                     cssPath = css.toURI().toString();
229                 }
230             }
231         } catch (IOException e) {
232             e.printStackTrace();
233             // CSS extraction failed, let's just live without it then.
234             ErrorLogger.defaultLogWarning(e);
235         }
236     }
237
238     public Label createDropLabel(Composite parent) {
239         final Label label = new Label(parent, SWT.BORDER | SWT.FLAT);
240         label.setAlignment(SWT.CENTER);
241         label.setText(Messages.VariableDebugger_DragResourceToDebugger);
242         label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
243         GridData data = new GridData(SWT.LEFT, SWT.FILL, false, false);
244         label.setLayoutData(data);
245
246         // Add resource id drop support to the drop-area.
247         DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY);
248         dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() });
249         dropTarget.addDropListener(new DropTargetAdapter() {
250             @Override
251             public void dragEnter(DropTargetEvent event) {
252                 event.detail = DND.DROP_LINK;
253                 label.setBackground((Color) resourceManager.get(green));
254                 return;
255             }
256             @Override
257             public void dragLeave(DropTargetEvent event) {
258                 label.setBackground(null);
259             }
260
261             @Override
262             public void drop(DropTargetEvent event) {
263                 label.setBackground(null);
264                 try {
265                     String uri = parseUri(event);
266                     if (uri == null) {
267                         event.detail = DND.DROP_NONE;
268                         return;
269                     }
270                     changeLocation(uri);
271                 } catch (DatabaseException e) {
272                     Logger.defaultLogError(e);
273                 }
274             }
275
276             private String parseUri(DropTargetEvent event) throws DatabaseException {
277                 Variable v = parseVariable(event);
278                 String uri = v != null ? session.sync(new VariableURI(v)) : null;
279                 if (uri == null) {
280                     Resource r = parseResource(event);
281                     uri = r != null ? session.sync(new PossibleURI(r)) : null;
282                 }
283                 return uri;
284             }
285
286             private Variable parseVariable(DropTargetEvent event) {
287                 return ISelectionUtils.getSinglePossibleKey(event.data, SelectionHints.KEY_MAIN, Variable.class);
288             }
289
290             private Resource parseResource(DropTargetEvent event) throws DatabaseException {
291                 ResourceArray[] ra = null;
292                 if (event.data instanceof String) {
293                     try {
294                         SerialisationSupport support = session.getService(SerialisationSupport.class);
295                         ra = ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();
296                     } catch (IllegalArgumentException e) {
297                         e.printStackTrace();
298                     } catch (DatabaseException e) {
299                         e.printStackTrace();
300                     }
301                 } else {
302                     ra = ResourceAdaptionUtils.toResourceArrays(event.data);
303                 }
304                 if (ra != null && ra.length > 0)
305                     return ra[0].resources[ra[0].resources.length - 1];
306                 return null;
307             }
308         });
309
310         return label;
311     }
312
313     public void createResourceText(Composite parent) {
314         final Text text = new Text(parent, SWT.BORDER);
315         GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
316         text.setLayoutData(data);
317
318         Button button = new Button(parent, SWT.NONE);
319         button.setText(Messages.VariableDebugger_Lookup);
320         GridData data2 = new GridData(SWT.FILL, SWT.FILL, false, false);
321         button.setLayoutData(data2);
322
323         button.addSelectionListener(new SelectionListener() {
324
325             @Override
326             public void widgetDefaultSelected(SelectionEvent e) {
327                 widgetSelected(e);
328             }
329
330             @Override
331             public void widgetSelected(SelectionEvent e) {
332
333                 try {
334                     String uri = text.getText();
335                     // Make sure that URI is resolvable to Variable
336                     session.sync(new ResourceURIToVariable(uri));
337                     changeLocation(uri);
338                 } catch (DatabaseException e1) {
339                     Logger.defaultLogError(e1);
340                 }
341
342             }
343
344         });
345     }
346
347     protected Text createUpdateTriggerCounter(Composite parent) {
348         Text label = new Text(parent, SWT.BORDER | SWT.FLAT);
349         label.setEditable(false);
350         label.setToolTipText(Messages.VariableDebugger_TextToolTip);
351         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
352         .grab(false, false).hint(32, SWT.DEFAULT).applyTo(label);
353         updateTriggerCounter = label;
354         return label;
355     }
356
357     public Browser createBrowser(Composite parent) {
358         try {
359             browser = new Browser(parent, SWT.MOZILLA);
360         } catch (SWTError e) {
361             //System.out.println("Could not instantiate Browser: " + e.getMessage());
362             browser = new Browser(parent, SWT.NONE);
363         }
364         browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
365
366         // Left/right arrows for back/forward
367         browser.addKeyListener(new KeyAdapter() {
368             @Override
369             public void keyReleased(KeyEvent e) {
370 //                System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")");
371 //                if (e.keyCode == SWT.BS) {
372 //                    back();
373 //                }
374                 if ((e.stateMask & SWT.ALT) != 0) {
375                     if (e.keyCode == SWT.ARROW_RIGHT)
376                         forward();
377                     if (e.keyCode == SWT.ARROW_LEFT)
378                         back();
379                 }
380             }
381         });
382
383         // Add listener for debugging functionality
384         browser.addLocationListener(new LocationAdapter() {
385             @Override
386             public void changing(LocationEvent event) {
387                 String location = event.location;
388                 if (location.startsWith("simantics:browser")) //$NON-NLS-1$
389                     location = "about:" + location.substring(17); //$NON-NLS-1$
390                 //System.out.println("changing: location=" + location);
391
392                 // Do not follow links that are meant as actions that are
393                 // handled below.
394                 event.doit = false;
395                 if ("about:blank".equals(location)) { //$NON-NLS-1$
396                     // Just changing to the same old blank url is ok since it
397                     // allows the browser to refresh itself.
398                     event.doit = true;
399                 }
400
401                 if (location.startsWith("about:-link")) { //$NON-NLS-1$
402                     String target = location.replace("about:-link", ""); //$NON-NLS-1$ //$NON-NLS-2$
403                     try {
404                         byte[] bytes = Base64.decode(target);
405                         String url = new String(bytes, utf8);
406                         if (url.equals(currentElement)) {
407                             event.doit = false;
408                             return;
409                         }
410                         changeLocation(url);
411                     } catch (IOException e) {
412                         ErrorLogger.defaultLogError(e);
413                     }
414                 } else if (location.startsWith("about:-remove")) { //$NON-NLS-1$
415                 } else if (location.startsWith("about:-edit-value")) { //$NON-NLS-1$
416                 }
417             }
418         });
419
420         // Schedule a request that updates the browser content.
421         refreshBrowser();
422
423         return browser;
424     }
425
426     public void refreshBrowser() {
427         if (currentElement == null)
428             return;
429
430         // Schedule a request that updates the browser content.
431         if (pageContentListener != null)
432             pageContentListener.dispose();
433         pageContentListener = new PageContentListener();
434         session.asyncRequest(new UnaryRead<String, String>(currentElement) {
435             @Override
436             public String perform(ReadGraph graph) throws DatabaseException {
437                 String content = calculateContent(graph, parameter);
438                 //System.out.println("HTML: " + content);
439                 return content;
440             }
441         }, pageContentListener);
442
443     }
444
445     public String getDebuggerLocation() {
446         return currentElement;
447     }
448
449     public void changeLocation(String url) {
450         if (currentElement != null) {
451             backHistory.addLast(currentElement);
452         }
453         currentElement = url;
454         forwardHistory.clear();
455
456         refreshBrowser();
457         fireHistoryChanged();
458     }
459
460     public void addHistoryListener(HistoryListener l) {
461         historyListeners.add(l);
462     }
463
464     public void removeHistoryListener(HistoryListener l) {
465         historyListeners.remove(l);
466     }
467
468     private void fireHistoryChanged() {
469         for (HistoryListener l : historyListeners)
470             l.historyChanged();
471     }
472
473     public boolean hasBackHistory() {
474         return backHistory.isEmpty();
475     }
476
477     public boolean hasForwardHistory() {
478         return forwardHistory.isEmpty();
479     }
480
481     public void back() {
482         if (backHistory.isEmpty())
483             return;
484
485         forwardHistory.addFirst(currentElement);
486         currentElement = backHistory.removeLast();
487
488         refreshBrowser();
489         fireHistoryChanged();
490     }
491
492     public void forward() {
493         if (forwardHistory.isEmpty())
494             return;
495
496         backHistory.addLast(currentElement);
497         currentElement = forwardHistory.removeFirst();
498
499         refreshBrowser();
500         fireHistoryChanged();
501     }
502
503     protected String toName(Object o) {
504         Class<?> clazz = o.getClass();
505         if (clazz.isArray()) {
506             int length = Array.getLength(o);
507             if (length > RESOURCE_NAME_MAX_LENGTH) {
508                 if (o instanceof byte[]) {
509                     byte[] arr = (byte[]) o;
510                     byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
511                     return truncated("byte", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
512                 } else if (o instanceof int[]) {
513                     int[] arr = (int[]) o;
514                     int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
515                     return truncated("int", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
516                 } else if (o instanceof long[]) {
517                     long[] arr = (long[]) o;
518                     long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
519                     return truncated("long", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
520                 } else if (o instanceof float[]) {
521                     float[] arr = (float[]) o;
522                     float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
523                     return truncated("float", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
524                 } else if (o instanceof double[]) {
525                     double[] arr = (double[]) o;
526                     double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
527                     return truncated("double", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
528                 } else if (o instanceof boolean[]) {
529                     boolean[] arr = (boolean[]) o;
530                     boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
531                     return truncated("boolean", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
532                 } else if (o instanceof Object[]) {
533                     Object[] arr = (Object[]) o;
534                     Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);
535                     return truncated("Object", Arrays.toString(arr2), arr.length); //$NON-NLS-1$
536                 } else {
537                     return "Unknown big array " + o.getClass(); //$NON-NLS-1$
538                 }
539             } else {
540                 return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o); //$NON-NLS-1$ //$NON-NLS-2$
541             }
542         }
543         return null;
544     }
545
546     protected String truncated(String type, String string, int originalLength) {
547         return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
548     }
549
550     protected String getVariableName(ReadGraph graph, Variable r) {
551         try {
552             return r.getName(graph);
553         } catch (Exception e) {
554             return e.getMessage();
555         }
556     }
557
558     protected String getValue(ReadGraph graph, Variable base, Object o) throws DatabaseException {
559         Class<?> clazz = o.getClass();
560         if(o instanceof Connection) {
561             Connection c = (Connection)o;
562             ArrayList<String> result = new ArrayList<String>();
563             for(VariableConnectionPointDescriptor v : c.getConnectionPointDescriptors(graph, null)) {
564                 result.add(v.getRelativeRVI(graph, base));
565             }
566             return "c " + result.toString(); //$NON-NLS-1$
567         } else if (clazz.isArray()) {
568             if(int[].class == clazz) {
569                 return Arrays.toString((int[])o);
570             } else if(float[].class == clazz) {
571                 return Arrays.toString((float[])o);
572             } else if(double[].class == clazz) {
573                 return Arrays.toString((double[])o);
574             } else if(long[].class == clazz) {
575                 return Arrays.toString((long[])o);
576             } else if(byte[].class == clazz) {
577                 return Arrays.toString((byte[])o);
578             } else if(boolean[].class == clazz) {
579                 return Arrays.toString((boolean[])o);
580             } else if(char[].class == clazz) {
581                 return Arrays.toString((char[])o);
582             } else {
583                 return Arrays.toString((Object[])o);
584             }
585         }
586         return o.toString();
587     }
588     
589     protected String getValue(ReadGraph graph, Variable r) {
590         try {
591             Object value = r.getValue(graph);
592             if(value instanceof Resource) return getResourceRef(graph, (Resource)value);
593             else if (value instanceof Variable) return getVariableRef(graph, (Variable)value);
594             else return value != null ? getValue(graph, r, value) : "null"; //$NON-NLS-1$
595         } catch (Throwable e) {
596             try {
597                 Logger.defaultLogError("getValue " + r.getURI(graph), e); //$NON-NLS-1$
598             } catch (DatabaseException e1) {
599                 Logger.defaultLogError(e1);
600             }
601             return e.getMessage();
602         }
603     }
604
605     protected String getDatatype(ReadGraph graph, Variable r) {
606         try {
607             Datatype dt = r.getPossibleDatatype(graph);
608             return dt != null ? dt.toSingleLineString() : "undefined"; //$NON-NLS-1$
609         } catch (Exception e) {
610             return e.getMessage();
611         }
612     }
613
614     private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {
615         return getVariableRef(graph, graph.adapt(r, Variable.class));
616     }
617
618     private String getVariableRef(ReadGraph graph, Variable r) throws DatabaseException {
619         String ret = "<a href=\"simantics:browser-link" + getLinkString(graph, r) + "\">" //$NON-NLS-1$ //$NON-NLS-2$
620         + getVariableName(graph, r)
621         + "</a>"; //$NON-NLS-1$
622 //        if (graph.isInstanceOf(r, L0.Literal)) {
623 //            ret += "&nbsp;<a class=\"edit-link\" href=\"simantics:browser-edit-value" + getLinkString(r) + "\">"
624 //            + "(edit value)"
625 //            + "</a>";
626 //        }
627         return ret;
628     }
629
630     private String getLinkString(ReadGraph graph, Variable t) throws DatabaseException {
631         try {
632             String uri = t.getURI(graph);
633             //return uri;
634             String encoded = Base64.encode(uri.getBytes(utf8));
635             return encoded;
636         } catch (Exception e) {
637             Logger.defaultLogError(e);
638             return e.getMessage();
639         }
640     }
641
642     private void updateProperty(StringBuilder content, ReadGraph graph, Variable property) throws DatabaseException {
643 //        try {
644 //            System.out.println("update property " + property.getURI(graph));
645 //        } catch (Exception e) {
646 //            e.printStackTrace();
647 //        }
648         content.append("<tr>"); //$NON-NLS-1$
649         content.append("<td>").append(getVariableRef(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
650         content.append("<td>").append(getValue(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
651         content.append("<td>").append(getDatatype(graph, property)).append("</td>"); //$NON-NLS-1$ //$NON-NLS-2$
652         content.append("</tr>"); //$NON-NLS-1$
653     }
654
655     protected String getRVIString(ReadGraph graph, Variable var) throws DatabaseException {
656         
657         try {
658             return var.getRVI(graph).toString(graph);
659         } catch (Throwable e) {
660             return "No RVI"; //$NON-NLS-1$
661         }
662         
663     }
664     
665     protected synchronized String calculateContent(final ReadGraph graph, String... uris) throws DatabaseException {
666         
667         L0 = Layer0.getInstance(graph);
668
669         StringBuilder content = new StringBuilder();
670
671         // Generate HTML -page
672         content.append("<html><head>").append(getHead()).append("</head>\n"); //$NON-NLS-1$ //$NON-NLS-2$
673         content.append("<body>\n"); //$NON-NLS-1$
674         content.append("<div id=\"mainContent\">\n"); //$NON-NLS-1$
675         for (String uri : uris) {
676             //System.out.println("URI: " + uri);
677             Variable var = Variables.getPossibleVariable(graph, uri);
678             if (var == null)
679                 continue;
680
681             String rviString = getRVIString(graph, var);
682             Object node = null;
683             if(var instanceof AbstractChildVariable) {
684                 VariableNode vn = ((AbstractChildVariable)var).node; 
685                 if(vn != null) node = vn.node;
686             }
687             if(var instanceof AbstractPropertyVariable) {
688                 VariableNode vn = ((AbstractPropertyVariable)var).node;
689                 if(vn != null) node = vn.node;
690             }
691             
692             // Begin #top DIV
693             content.append("<div id=\"top\">\n"); //$NON-NLS-1$
694             content.append("<table class=\"top\">\n"); //$NON-NLS-1$
695             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$
696             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$
697             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$
698             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$
699             content.append("</table>\n"); //$NON-NLS-1$
700             content.append("</div>\n"); //$NON-NLS-1$
701             // Close #top DIV
702
703             // Content
704             TreeMap<String, Variable> map = new TreeMap<String, Variable>();
705             try {
706                 for(Variable child : var.getChildren(graph)) {
707                     String name = getVariableName(graph, child);
708                     map.put(name, child);
709                 }
710             } catch (DatabaseException e) {
711                 // This may happen if the Variable implementation is broken
712                 ErrorLogger.defaultLogError("Broken variable child retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$
713             }
714
715             TreeMap<String, Variable> map2 = new TreeMap<String, Variable>();
716             try {
717                 for(Variable child : var.getProperties(graph)) {
718                     String name = getVariableName(graph, child);
719                     map2.put(name, child);
720                 }
721             } catch (DatabaseException e) {
722                 // This may happen if the Variable implementation is broken
723                 ErrorLogger.defaultLogError("Broken variable property retrieval implementation or serious modelling error encountered. See exception for details.", e); //$NON-NLS-1$
724             }
725
726             content.append("\n<div id=\"data\">\n"); //$NON-NLS-1$
727             content.append("<table>\n"); //$NON-NLS-1$
728
729             content.append("<tr><th>Child</th></tr>"); //$NON-NLS-1$
730             for (Variable child : map.values()) {
731                 content.append("<tr><td>").append(getVariableRef(graph, child)).append("</td></tr>"); //$NON-NLS-1$ //$NON-NLS-2$
732             }
733
734             content.append("<tr><th>Property</th><th>Value</th><th>Datatype</th></tr>"); //$NON-NLS-1$
735             for (Variable property : map2.values()) {
736                 updateProperty(content, graph, property);
737             }
738             // Close #data
739             content.append("</div>\n\n"); //$NON-NLS-1$
740         }
741
742         // Close #mainContent
743         content.append("</div>\n"); //$NON-NLS-1$
744         content.append("</body></html>\n"); //$NON-NLS-1$
745
746         // Update content
747         return content.toString();
748     }
749
750     private String getHead() {
751         String result = ""; //$NON-NLS-1$
752         if (cssPath != null) {
753             result = "<link href=\"" + cssPath + "\" rel=\"stylesheet\" type=\"text/css\">"; //$NON-NLS-1$ //$NON-NLS-2$
754         }
755         return result;
756     }
757
758 }