]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.debug.ui/src/org/simantics/debug/ui/GraphDebugger.java
merged svn revision 33114 and added desktop and help plugins
[simantics/platform.git] / bundles / org.simantics.debug.ui / src / org / simantics / debug / ui / GraphDebugger.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.debug.ui;\r
13 \r
14 import java.io.File;\r
15 import java.io.FileNotFoundException;\r
16 import java.io.IOException;\r
17 import java.lang.reflect.Array;\r
18 import java.net.URI;\r
19 import java.net.URISyntaxException;\r
20 import java.net.URL;\r
21 import java.util.ArrayList;\r
22 import java.util.Arrays;\r
23 import java.util.Collection;\r
24 import java.util.Comparator;\r
25 import java.util.HashMap;\r
26 import java.util.LinkedList;\r
27 import java.util.List;\r
28 import java.util.Map;\r
29 import java.util.UUID;\r
30 import java.util.concurrent.CopyOnWriteArrayList;\r
31 \r
32 import org.eclipse.core.runtime.Assert;\r
33 import org.eclipse.core.runtime.FileLocator;\r
34 import org.eclipse.core.runtime.IPath;\r
35 import org.eclipse.core.runtime.Path;\r
36 import org.eclipse.jface.action.IStatusLineManager;\r
37 import org.eclipse.jface.dialogs.Dialog;\r
38 import org.eclipse.jface.dialogs.IDialogConstants;\r
39 import org.eclipse.jface.dialogs.IDialogSettings;\r
40 import org.eclipse.jface.dialogs.IInputValidator;\r
41 import org.eclipse.jface.dialogs.InputDialog;\r
42 import org.eclipse.jface.dialogs.MessageDialog;\r
43 import org.eclipse.jface.layout.GridDataFactory;\r
44 import org.eclipse.jface.resource.ColorDescriptor;\r
45 import org.eclipse.jface.resource.JFaceResources;\r
46 import org.eclipse.jface.resource.LocalResourceManager;\r
47 import org.eclipse.swt.SWT;\r
48 import org.eclipse.swt.SWTError;\r
49 import org.eclipse.swt.browser.Browser;\r
50 import org.eclipse.swt.browser.LocationAdapter;\r
51 import org.eclipse.swt.browser.LocationEvent;\r
52 import org.eclipse.swt.dnd.DND;\r
53 import org.eclipse.swt.dnd.DropTarget;\r
54 import org.eclipse.swt.dnd.DropTargetAdapter;\r
55 import org.eclipse.swt.dnd.DropTargetEvent;\r
56 import org.eclipse.swt.dnd.TextTransfer;\r
57 import org.eclipse.swt.dnd.Transfer;\r
58 import org.eclipse.swt.events.DisposeEvent;\r
59 import org.eclipse.swt.events.DisposeListener;\r
60 import org.eclipse.swt.events.FocusEvent;\r
61 import org.eclipse.swt.events.FocusListener;\r
62 import org.eclipse.swt.events.KeyAdapter;\r
63 import org.eclipse.swt.events.KeyEvent;\r
64 import org.eclipse.swt.events.ModifyEvent;\r
65 import org.eclipse.swt.events.ModifyListener;\r
66 import org.eclipse.swt.events.SelectionEvent;\r
67 import org.eclipse.swt.events.SelectionListener;\r
68 import org.eclipse.swt.graphics.Color;\r
69 import org.eclipse.swt.graphics.Point;\r
70 import org.eclipse.swt.graphics.RGB;\r
71 import org.eclipse.swt.layout.GridData;\r
72 import org.eclipse.swt.layout.GridLayout;\r
73 import org.eclipse.swt.widgets.Button;\r
74 import org.eclipse.swt.widgets.Composite;\r
75 import org.eclipse.swt.widgets.Label;\r
76 import org.eclipse.swt.widgets.Text;\r
77 import org.eclipse.ui.IWorkbenchSite;\r
78 import org.simantics.databoard.Bindings;\r
79 import org.simantics.databoard.Databoard;\r
80 import org.simantics.databoard.binding.Binding;\r
81 import org.simantics.databoard.binding.error.BindingException;\r
82 import org.simantics.databoard.serialization.Serializer;\r
83 import org.simantics.databoard.type.ArrayType;\r
84 import org.simantics.databoard.type.Datatype;\r
85 import org.simantics.databoard.type.StringType;\r
86 import org.simantics.databoard.util.ObjectUtils;\r
87 import org.simantics.db.AsyncRequestProcessor;\r
88 import org.simantics.db.ReadGraph;\r
89 import org.simantics.db.Resource;\r
90 import org.simantics.db.Session;\r
91 import org.simantics.db.Statement;\r
92 import org.simantics.db.VirtualGraph;\r
93 import org.simantics.db.WriteGraph;\r
94 import org.simantics.db.common.ResourceArray;\r
95 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
96 import org.simantics.db.common.request.Queries;\r
97 import org.simantics.db.common.request.ReadRequest;\r
98 import org.simantics.db.common.request.WriteRequest;\r
99 import org.simantics.db.common.uri.ResourceToPossibleURI;\r
100 import org.simantics.db.common.utils.ListUtils;\r
101 import org.simantics.db.common.utils.NameUtils;\r
102 import org.simantics.db.common.utils.OrderedSetUtils;\r
103 import org.simantics.db.event.ChangeEvent;\r
104 import org.simantics.db.event.ChangeListener;\r
105 import org.simantics.db.exception.AdaptionException;\r
106 import org.simantics.db.exception.DatabaseException;\r
107 import org.simantics.db.exception.ResourceNotFoundException;\r
108 import org.simantics.db.exception.ValidationException;\r
109 import org.simantics.db.layer0.adapter.StringModifier;\r
110 import org.simantics.db.layer0.variable.RVI;\r
111 import org.simantics.db.layer0.variable.Variable;\r
112 import org.simantics.db.layer0.variable.Variables;\r
113 import org.simantics.db.service.ClusteringSupport;\r
114 import org.simantics.db.service.GraphChangeListenerSupport;\r
115 import org.simantics.db.service.SerialisationSupport;\r
116 import org.simantics.db.service.VirtualGraphSupport;\r
117 import org.simantics.db.service.XSupport;\r
118 import org.simantics.debug.ui.internal.Activator;\r
119 import org.simantics.debug.ui.internal.DebugUtils;\r
120 import org.simantics.debug.ui.internal.HashMultiMap;\r
121 import org.simantics.layer0.Layer0;\r
122 import org.simantics.ui.dnd.LocalObjectTransfer;\r
123 import org.simantics.ui.dnd.ResourceReferenceTransfer;\r
124 import org.simantics.ui.dnd.ResourceTransferUtils;\r
125 import org.simantics.ui.utils.ResourceAdaptionUtils;\r
126 import org.simantics.utils.Container;\r
127 import org.simantics.utils.FileUtils;\r
128 import org.simantics.utils.datastructures.BijectionMap;\r
129 import org.simantics.utils.datastructures.Callback;\r
130 import org.simantics.utils.strings.AlphanumComparator;\r
131 import org.simantics.utils.ui.ErrorLogger;\r
132 import org.simantics.utils.ui.PathUtils;\r
133 import org.simantics.utils.ui.workbench.WorkbenchUtils;\r
134 \r
135 \r
136 public class GraphDebugger extends Composite {\r
137 \r
138     public interface HistoryListener {\r
139         void historyChanged();\r
140     }\r
141 \r
142     private static final String                         STATEMENT_PART_SEPARATOR  = ",";\r
143     private final static String                         DEFAULT_DEBUGGER_CSS_FILE = "debugger.css";\r
144     private final static String                         DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE;\r
145 \r
146     private static int                                  RESOURCE_NAME_MAX_LENGTH  = 1000;\r
147     private static int                                  RESOURCE_VALUE_MAX_SIZE = 16384;\r
148 \r
149     private final LocalResourceManager                  resourceManager;\r
150 \r
151     private String                                      cssPath;\r
152 \r
153     private Browser                                     browser;\r
154     private final ColorDescriptor                       green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95));\r
155 \r
156     private final boolean                               displayClusters           = true;\r
157 \r
158     private final BijectionMap<String, Resource>        links                     = new BijectionMap<String, Resource>();\r
159     private final LinkedList<Resource>                  backHistory               = new LinkedList<Resource>();\r
160     private final LinkedList<Resource>                  forwardHistory            = new LinkedList<Resource>();\r
161     private Resource                                    currentElement            = null;\r
162 \r
163     /**\r
164      * The Session used to access the graph. Received from outside of this\r
165      * class and therefore it is not disposed here, just used.\r
166      */\r
167     private final Session                               session;\r
168 \r
169     private final CopyOnWriteArrayList<HistoryListener> historyListeners          = new CopyOnWriteArrayList<HistoryListener>();\r
170 \r
171     private final AsyncRequestProcessor                 updater;\r
172 \r
173     protected Layer0                                    L0;\r
174 \r
175     protected IWorkbenchSite                            site;\r
176 \r
177     private final ChangeListener changeListener = new ChangeListener() {\r
178         @Override\r
179         public void graphChanged(ChangeEvent e) {\r
180             // This makes sure that the transaction for updating this\r
181             // GraphDebugger get executed in a serialized fashion.\r
182             updater.asyncRequest(new ReadRequest() {\r
183 \r
184                 @Override\r
185                 public void run(ReadGraph graph) throws DatabaseException {\r
186                     updateContent(graph, currentElement);\r
187                 }\r
188 \r
189             });\r
190 \r
191         }\r
192     };\r
193 \r
194     /**\r
195      * @param parent\r
196      * @param style\r
197      * @param session\r
198      * @param resource the initial resource to debug or <code>null</code> for\r
199      *        initially blank UI.\r
200      * @param site the workbench site that contains this debugger, for workbench\r
201      *        service access\r
202      */\r
203     public GraphDebugger(Composite parent, int style, final Session session, Resource resource, IWorkbenchSite site) {\r
204         this(parent, style, session, resource);\r
205         this.site = site;\r
206     }\r
207 \r
208     /**\r
209      * @param parent\r
210      * @param style\r
211      * @param session\r
212      * @param resource the initial resource to debug or <code>null</code> for\r
213      *        initially blank UI.\r
214      */\r
215     public GraphDebugger(Composite parent, int style, final Session session, Resource resource) {\r
216         super(parent, style);\r
217         Assert.isNotNull(session, "session is null");\r
218         this.session = session;\r
219         this.currentElement = resource;\r
220         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);\r
221 \r
222         updater = session;//.getService(MergingGraphRequestProcessor.class);\r
223 \r
224         initializeCSS();\r
225 \r
226         addDisposeListener(new DisposeListener() {\r
227             @Override\r
228             public void widgetDisposed(DisposeEvent e) {\r
229                 GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
230                 support.removeListener(changeListener);\r
231             }\r
232         });\r
233     }\r
234 \r
235     /**\r
236      * When given to setStatus, indicates that the message shouldn't be touched\r
237      * since <code>null</code> has a different meaning.\r
238      */\r
239     private static final String DONT_TOUCH = "DONT_TOUCH";\r
240 \r
241     protected void setStatus(String message, String error) {\r
242         IStatusLineManager status = WorkbenchUtils.getStatusLine(site);\r
243         if (status != null) {\r
244             if (message != DONT_TOUCH)\r
245                 status.setMessage(message);\r
246             if (error != DONT_TOUCH)\r
247                 status.setErrorMessage(error);\r
248         }\r
249     }\r
250 \r
251     public void defaultInitializeUI() {\r
252         setLayout(new GridLayout(2, false));\r
253         setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));\r
254 \r
255         createResourceText(this);\r
256         createDropLabel(this);\r
257         createBrowser(this);\r
258 \r
259     }\r
260 \r
261     protected void initializeCSS() {\r
262         // Extract default css to a temporary location if necessary.\r
263         try {\r
264             IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH);\r
265             if (absolutePath != null) {\r
266                 cssPath = absolutePath.toFile().toURI().toString();\r
267             } else {\r
268                 File tempDir = FileUtils.getOrCreateTemporaryDirectory(false);\r
269                 File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE);\r
270                 if (!css.exists()) {\r
271                     URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null);\r
272                     if (url == null)\r
273                         throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'");\r
274                     cssPath = FileUtils.copyResource(url, css, true).toURI().toString();\r
275                 } else {\r
276                     cssPath = css.toURI().toString();\r
277                 }\r
278             }\r
279         } catch (IOException e) {\r
280             // CSS extraction failed, let's just live without it then.\r
281             ErrorLogger.defaultLogWarning(e);\r
282         }\r
283     }\r
284 \r
285     private static final String PROMPT_TEXT = "Enter resource ID (RID) or URI";\r
286 \r
287     public void createResourceText(final Composite parent) {\r
288         final Text text = new Text(parent, SWT.BORDER);\r
289         text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
290         text.setText(PROMPT_TEXT);\r
291         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(text);\r
292         \r
293         text.addFocusListener(new FocusListener() {    \r
294             @Override\r
295             public void focusLost(FocusEvent e) {\r
296                 if (text.getText().trim().equals("")) {\r
297                     text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
298                     text.setText(PROMPT_TEXT);\r
299                 }\r
300             }\r
301             @Override\r
302             public void focusGained(FocusEvent e) {\r
303                 if (text.getText().trim().equals(PROMPT_TEXT)) {\r
304                     text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK));\r
305                     text.setText("");\r
306                 }\r
307                 text.selectAll();\r
308             }\r
309         });\r
310         text.addKeyListener(new KeyAdapter() {\r
311             @Override\r
312             public void keyPressed(KeyEvent e) {\r
313                 if (e.keyCode == SWT.CR) {\r
314                     String input = text.getText();\r
315                     setLookupInput(input);\r
316                 }\r
317             }\r
318         });\r
319 \r
320         final Button button = new Button(parent, SWT.FLAT);\r
321         button.setText("&Lookup");\r
322         button.setEnabled(false);\r
323         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false).applyTo(button);\r
324 \r
325         text.addKeyListener(new KeyAdapter() {\r
326             \r
327             @Override\r
328             public void keyPressed(KeyEvent e) {\r
329                 if (e.keyCode == 13) {\r
330                     String input = text.getText();\r
331                     setLookupInput(input);\r
332                     button.setFocus();\r
333                 }\r
334             }\r
335         });\r
336         \r
337         button.addSelectionListener(new SelectionListener() {\r
338             @Override\r
339             public void widgetDefaultSelected(SelectionEvent e) {\r
340                 widgetSelected(e);\r
341             }\r
342             @Override\r
343             public void widgetSelected(SelectionEvent e) {\r
344                 String input = text.getText();\r
345                 setLookupInput(input);\r
346             }\r
347         });\r
348         \r
349         text.addModifyListener(new ModifyListener() {\r
350             @Override\r
351             public void modifyText(ModifyEvent e) {\r
352                 String input = text.getText().trim();\r
353                 if (!input.equals(PROMPT_TEXT) && !input.equals(""))\r
354                     button.setEnabled(true);\r
355                 else\r
356                     button.setEnabled(false);\r
357             }\r
358         });\r
359     }\r
360 \r
361     public void setLookupInput(String input) {\r
362         // There's no harm in trimming out spaces from both ends of the input.\r
363         input = input.trim();\r
364 \r
365         SerialisationSupport support = session.getService(SerialisationSupport.class);\r
366         if (input.startsWith("$")) {\r
367             try {\r
368                 Resource r = support.getResource(Long.parseLong(input.substring(1)));\r
369                 changeLocation(r);\r
370             } catch (NumberFormatException e1) {\r
371                 // Ignore, may happen for crap input\r
372                 setStatus(DONT_TOUCH, "Invalid '$'-prefixed input, expected resource ID");\r
373             } catch (Exception e1) {\r
374                 ErrorLogger.defaultLogError(e1);\r
375                 setStatus(DONT_TOUCH, "Resource ID lookup failed. See Error Log.");\r
376             }\r
377             return;\r
378         }\r
379 \r
380         String[] parts = input.split("-");\r
381 \r
382         if (parts.length == 1) {\r
383             try {\r
384                 int resourceKey = Integer.parseInt(parts[0].trim());\r
385                 Resource r = support.getResource(resourceKey);\r
386                 // Some validation, not enough though\r
387                 ClusteringSupport cs = session.getService(ClusteringSupport.class);\r
388                 long cluster = cs.getCluster(r);\r
389                 if(cluster > 0) {\r
390                     changeLocation(r);\r
391                 }\r
392             } catch (NumberFormatException e1) {\r
393                 // Ignore, may happen for crap input\r
394                 setStatus(DONT_TOUCH, "Invalid input, expected transient resource ID");\r
395             } catch (Exception e1) {\r
396                 ErrorLogger.defaultLogError(e1);\r
397                 setStatus(DONT_TOUCH, "Transient resource ID lookup failed. See Error Log.");\r
398             }\r
399         } else if (parts.length == 2) {\r
400             try {\r
401                 int resourceIndex = Integer.parseInt(parts[1]);\r
402                 long clusterId = Long.parseLong(parts[0]);\r
403                 ClusteringSupport cs = session.getService(ClusteringSupport.class);\r
404                 Resource r = cs.getResourceByIndexAndCluster(resourceIndex, clusterId);\r
405                 changeLocation(r);\r
406             } catch (NumberFormatException e1) {\r
407                 // Ignore, may happen for crap input\r
408                 setStatus(DONT_TOUCH, "Invalid input, expected index & cluster IDs");\r
409             } catch (Exception e1) {\r
410                 ErrorLogger.defaultLogError(e1);\r
411                 setStatus(DONT_TOUCH, "Index & cluster -based lookup failed. See Error Log.");\r
412             }\r
413         }\r
414 \r
415         // Try to see if the input data is an URI reference\r
416         try {\r
417             // First check that the input really is a proper URI.\r
418             String uri = input;\r
419             if (!input.equals("http:/") && input.endsWith("/"))\r
420                 uri = input.substring(0, input.length() - 1);\r
421             new URI(uri);\r
422             Resource r = session.syncRequest( Queries.resource( uri ) );\r
423             changeLocation(r);\r
424             return;\r
425         } catch (URISyntaxException e) {\r
426             // Ignore, this is not a proper URI at all.\r
427         } catch (ResourceNotFoundException e1) {\r
428             // Ok, this was an URI, but no resource was found.\r
429             setStatus(DONT_TOUCH, "Resource for URI '" + input + "' not found");\r
430             return;\r
431         } catch (DatabaseException e1) {\r
432             setStatus(DONT_TOUCH, "URI lookup failed. See Error Log.");\r
433             ErrorLogger.defaultLogError(e1);\r
434         }\r
435 \r
436         setStatus(DONT_TOUCH, "Invalid input, resource ID or URI expected");\r
437     }\r
438 \r
439     public Label createDropLabel(Composite parent) {\r
440         final Label label = new Label(parent, SWT.BORDER);\r
441         label.setAlignment(SWT.CENTER);\r
442         label.setText("Drag a resource here to examine it in this debugger!");\r
443         label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));\r
444         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).hint(SWT.DEFAULT, 20).span(2, 1).grab(true, false).applyTo(label);\r
445 \r
446         // Add resource id drop support to the drop-area.\r
447         DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY);\r
448         dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() });\r
449         dropTarget.addDropListener(new DropTargetAdapter() {\r
450             @Override\r
451             public void dragEnter(DropTargetEvent event) {\r
452                 event.detail = DND.DROP_LINK;\r
453                 label.setBackground((Color) resourceManager.get(green));\r
454                 return;\r
455             }\r
456             @Override\r
457             public void dragLeave(DropTargetEvent event) {\r
458                 label.setBackground(null);\r
459             }\r
460 \r
461             @Override\r
462             public void drop(DropTargetEvent event) {\r
463                 label.setBackground(null);\r
464                 ResourceArray[] data = parseEventData(event);\r
465                 if (data == null || data.length != 1) {\r
466                     event.detail = DND.DROP_NONE;\r
467                     return;\r
468                 }\r
469                 final ResourceArray array = data[0];\r
470                 final Resource r = array.resources[array.resources.length - 1];\r
471 \r
472                 changeLocation(r);\r
473             }\r
474 \r
475             private ResourceArray[] parseEventData(DropTargetEvent event) {\r
476                 //System.out.println("DATA: " + event.data);\r
477                 if (event.data instanceof String) {\r
478                     try {\r
479                         SerialisationSupport support = session.getService(SerialisationSupport.class);\r
480                         return ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray();\r
481                     } catch (IllegalArgumentException e) {\r
482                         ErrorLogger.defaultLogError(e);\r
483                     } catch (DatabaseException e) {\r
484                         ErrorLogger.defaultLogError(e);\r
485                     }\r
486                 }\r
487                 ResourceArray[] ret = ResourceAdaptionUtils.toResourceArrays(event.data);\r
488                 if (ret.length > 0)\r
489                     return ret;\r
490                 return null;\r
491             }\r
492         });\r
493 \r
494         return label;\r
495     }\r
496 \r
497     public Browser createBrowser(Composite parent) {\r
498         try {\r
499             browser = new Browser(parent, SWT.MOZILLA);\r
500         } catch (SWTError e) {\r
501             //System.out.println("Could not instantiate Browser: " + e.getMessage());\r
502             browser = new Browser(parent, SWT.NONE);\r
503         }\r
504         GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(browser);\r
505 \r
506         // Left/right arrows for back/forward\r
507         browser.addKeyListener(new KeyAdapter() {\r
508 //              \r
509 //              @Override\r
510 //            public void keyPressed(KeyEvent e) {\r
511 //                      if (e.keyCode == SWT.F5) {\r
512 //                      refreshBrowser();\r
513 //                      }\r
514 //              }\r
515 //          }\r
516             @Override\r
517             public void keyReleased(KeyEvent e) {\r
518 //                System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")");\r
519                 if (e.keyCode == SWT.BS) {\r
520                     back();\r
521                 }\r
522                 \r
523                 if (e.keyCode == SWT.F5) {\r
524                         refreshBrowser();\r
525                 }\r
526                 \r
527                 if ((e.stateMask & SWT.ALT) != 0) {\r
528                     if (e.keyCode == SWT.ARROW_RIGHT)\r
529                         forward();\r
530                     if (e.keyCode == SWT.ARROW_LEFT)\r
531                         back();\r
532                 }\r
533             }\r
534         });\r
535 \r
536         // Add listener for debugging functionality\r
537         browser.addLocationListener(new LocationAdapter() {\r
538             @Override\r
539             public void changing(LocationEvent event) {\r
540                 String location = event.location;\r
541                 if (location.startsWith("simantics:browser"))\r
542                     location = "about:" + location.substring(17);\r
543                 //System.out.println("changing: location=" + location);\r
544 \r
545                 // Do not follow links that are meant as actions that are\r
546                 // handled below.\r
547                 event.doit = false;\r
548                 if ("about:blank".equals(location)) {\r
549                     // Just changing to the same old blank url is ok since it\r
550                     // allows the browser to refresh itself.\r
551                     event.doit = true;\r
552                 }\r
553 \r
554                 if (location.startsWith("about:-link")) {\r
555                     String target = location.replace("about:-link", "");\r
556                     Resource element = links.getRight(target);\r
557                     if (element == currentElement) {\r
558                         event.doit = false;\r
559                         return;\r
560                     }\r
561                     changeLocation(element);\r
562                 } else if (location.startsWith("about:-remove")) {\r
563                     String target = location.replace("about:-remove", "");\r
564                     String n[] = target.split(STATEMENT_PART_SEPARATOR);\r
565                     if (n.length != 3)\r
566                         return;\r
567 \r
568                     final Resource s = links.getRight(n[0]);\r
569                     final Resource p = links.getRight(n[1]);\r
570                     final Resource o = links.getRight(n[2]);\r
571 \r
572                     // Make sure this is what the use wants.\r
573                     MessageDialog md = new MessageDialog(\r
574                             getShell(),\r
575                             "Confirm action...",\r
576                             null,\r
577                             "This action will remove the selected statement.\nAre you sure you want to proceed with this action?",\r
578                             MessageDialog.QUESTION, new String[] { "Cancel", "Continue" }, 0);\r
579                     if (md.open() != 1) {\r
580                         return;\r
581                     }\r
582 \r
583                     session.asyncRequest(new WriteRequest() {\r
584 \r
585                         @Override\r
586                         public void perform(WriteGraph g) throws DatabaseException {\r
587                                 try {\r
588                                         List<Resource> ls = OrderedSetUtils.toList(g, s);\r
589                                         if(ls.contains(o))\r
590                                                 OrderedSetUtils.remove(g, s, o);\r
591                                 } catch (DatabaseException e) {\r
592                                         \r
593                                 }\r
594                                 try {\r
595                                         List<Resource> ls = ListUtils.toList(g, s);\r
596                                         if(ls.contains(o))\r
597                                                 ListUtils.removeElement(g, s, o);\r
598                                 } catch (DatabaseException e) {\r
599                                         \r
600                                 }\r
601                             g.denyStatement(s, p, o);\r
602                         }\r
603 \r
604                     }, new Callback<DatabaseException>() {\r
605 \r
606                         @Override\r
607                         public void run(DatabaseException parameter) {\r
608                             refreshBrowser();\r
609                         }\r
610 \r
611                     });\r
612                 } else if (location.startsWith("about:-edit-value")) {\r
613                     String target = location.replace("about:-edit-value", "");\r
614                     final Resource o = links.getRight(target);\r
615 \r
616                     session.asyncRequest(new ReadRequest() {\r
617 \r
618                         String previousValue;\r
619 \r
620                         @Override\r
621                         public void run(ReadGraph graph) throws DatabaseException {\r
622 \r
623                             previousValue = getResourceName(graph, o);\r
624                             final StringModifier modifier = graph.adapt(o, StringModifier.class);\r
625                             getDisplay().asyncExec(new Runnable() {\r
626                                 @Override\r
627                                 public void run() {\r
628                                     InputDialog dialog = new InputDialog(\r
629                                             getShell(),\r
630                                             "Edit Value",\r
631                                             null,\r
632                                             previousValue,\r
633                                             new IInputValidator() {\r
634                                                 @Override\r
635                                                 public String isValid(String newText) {\r
636                                                     return modifier.isValid(newText);\r
637                                                 }\r
638                                             }) {\r
639                                         private static final String DIALOG = "DebuggerEditValueDialog"; //$NON-NLS-1$\r
640                                         private IDialogSettings dialogBoundsSettings;\r
641                                         @Override\r
642                                         protected IDialogSettings getDialogBoundsSettings() {\r
643                                             if (dialogBoundsSettings == null) {\r
644                                                 IDialogSettings settings = Activator.getDefault().getDialogSettings();\r
645                                                 dialogBoundsSettings = settings.getSection(DIALOG);\r
646                                                 if (dialogBoundsSettings == null)\r
647                                                     dialogBoundsSettings = settings.addNewSection(DIALOG);\r
648                                             }\r
649                                             return dialogBoundsSettings;\r
650                                         }\r
651                                         @Override\r
652                                         protected Point getInitialSize() {\r
653                                             Point defaultSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);\r
654                                             Point result = super.getInitialSize();\r
655                                             if (defaultSize.equals(result))\r
656                                                 return new Point(600, 400);\r
657                                             return result;\r
658                                         }\r
659                                         protected int getShellStyle() {\r
660                                             return super.getShellStyle() | SWT.RESIZE;\r
661                                         }\r
662                                         protected org.eclipse.swt.widgets.Control createDialogArea(Composite parent) {\r
663                                             Composite composite = (Composite) super.createDialogArea(parent);\r
664                                             getText().setLayoutData(\r
665                                                     new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL\r
666                                                             | GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL));\r
667                                             {\r
668                                                 Label label = new Label(composite, SWT.NONE);\r
669                                                 label.moveAbove(getText());\r
670                                                 label.setText("Input new property value. For numeric vector values, separate numbers with comma (',').");\r
671                                                 GridData data = new GridData(GridData.GRAB_HORIZONTAL\r
672                                                         | GridData.HORIZONTAL_ALIGN_FILL\r
673                                                         | GridData.VERTICAL_ALIGN_CENTER);\r
674                                                 data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);\r
675                                                 label.setLayoutData(data);\r
676                                                 label.setFont(parent.getFont());\r
677                                             }\r
678                                             return composite;\r
679                                         }\r
680                                         protected int getInputTextStyle() {\r
681                                             return SWT.MULTI | SWT.BORDER;\r
682                                         }\r
683                                     };\r
684                                     int ok = dialog.open();\r
685                                     if (ok != Dialog.OK)\r
686                                         return;\r
687 \r
688                                     final String value = dialog.getValue();\r
689                                     session.asyncRequest(new WriteRequest() {\r
690                                         @Override\r
691                                         public void perform(WriteGraph g) throws DatabaseException {\r
692                                             //modifier.modify( g, htmlEscape( value ) );\r
693                                             modifier.modify( g, value );\r
694                                         }\r
695                                     }, new Callback<DatabaseException>() {\r
696                                         @Override\r
697                                         public void run(DatabaseException parameter) {\r
698                                             if (parameter != null)\r
699                                                 ErrorLogger.defaultLogError(parameter);\r
700                                             refreshBrowser();\r
701                                         }\r
702                                     });\r
703                                     return;\r
704                                 }\r
705                             });\r
706                         }\r
707 \r
708                     }, new ProcedureAdapter<Object>() {\r
709                         @Override\r
710                         public void exception(Throwable t) {\r
711                             ErrorLogger.defaultLogError(t);\r
712                         }\r
713                     });\r
714 \r
715                 }\r
716             }\r
717         });\r
718 \r
719         // Schedule a request that updates the browser content.\r
720         refreshBrowser();\r
721         GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
722         support.removeListener(changeListener);\r
723 \r
724         return browser;\r
725     }\r
726 \r
727     public void refreshBrowser() {\r
728         if (currentElement == null)\r
729             return;\r
730 \r
731         // Schedule a request that updates the browser content.\r
732         updater.asyncRequest(new ReadRequest() {\r
733 \r
734             @Override\r
735             public void run(ReadGraph graph) throws DatabaseException {\r
736                 updateContent(graph, currentElement);\r
737             }\r
738 \r
739         });\r
740 \r
741     }\r
742 \r
743     public Resource getDebuggerLocation() {\r
744         return currentElement;\r
745     }\r
746 \r
747     public void changeLocation(Resource element) {\r
748         if (currentElement != null) {\r
749             backHistory.addLast(currentElement);\r
750         }\r
751         currentElement = element;\r
752         forwardHistory.clear();\r
753 \r
754         refreshBrowser();\r
755         setStatus(DONT_TOUCH, null);\r
756         fireHistoryChanged();\r
757     }\r
758 \r
759     public void addHistoryListener(HistoryListener l) {\r
760         historyListeners.add(l);\r
761     }\r
762 \r
763     public void removeHistoryListener(HistoryListener l) {\r
764         historyListeners.remove(l);\r
765     }\r
766 \r
767     private void fireHistoryChanged() {\r
768         for (HistoryListener l : historyListeners)\r
769             l.historyChanged();\r
770     }\r
771 \r
772     public boolean hasBackHistory() {\r
773         return backHistory.isEmpty();\r
774     }\r
775 \r
776     public boolean hasForwardHistory() {\r
777         return forwardHistory.isEmpty();\r
778     }\r
779 \r
780     public void back() {\r
781         if (backHistory.isEmpty())\r
782             return;\r
783 \r
784         forwardHistory.addFirst(currentElement);\r
785         currentElement = backHistory.removeLast();\r
786 \r
787         refreshBrowser();\r
788         fireHistoryChanged();\r
789     }\r
790 \r
791     public void forward() {\r
792         if (forwardHistory.isEmpty())\r
793             return;\r
794 \r
795         backHistory.addLast(currentElement);\r
796         currentElement = forwardHistory.removeFirst();\r
797 \r
798         refreshBrowser();\r
799         fireHistoryChanged();\r
800     }\r
801 \r
802     protected String toName(Object o) {\r
803         Class<?> clazz = o.getClass();\r
804         if (clazz.isArray()) {\r
805             int length = Array.getLength(o);\r
806             if (length > RESOURCE_NAME_MAX_LENGTH) {\r
807                 if (o instanceof byte[]) {\r
808                     byte[] arr = (byte[]) o;\r
809                     byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
810                     return truncated("byte", Arrays.toString(arr2), arr.length);\r
811                 } else if (o instanceof int[]) {\r
812                     int[] arr = (int[]) o;\r
813                     int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
814                     return truncated("int", Arrays.toString(arr2), arr.length);\r
815                 } else if (o instanceof long[]) {\r
816                     long[] arr = (long[]) o;\r
817                     long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
818                     return truncated("long", Arrays.toString(arr2), arr.length);\r
819                 } else if (o instanceof float[]) {\r
820                     float[] arr = (float[]) o;\r
821                     float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
822                     return truncated("float", Arrays.toString(arr2), arr.length);\r
823                 } else if (o instanceof double[]) {\r
824                     double[] arr = (double[]) o;\r
825                     double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
826                     return truncated("double", Arrays.toString(arr2), arr.length);\r
827                 } else if (o instanceof boolean[]) {\r
828                     boolean[] arr = (boolean[]) o;\r
829                     boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
830                     return truncated("boolean", Arrays.toString(arr2), arr.length);\r
831                 } else if (o instanceof Object[]) {\r
832                     Object[] arr = (Object[]) o;\r
833                     Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH);\r
834                     return truncated("Object", Arrays.toString(arr2), arr.length);\r
835                 } else {\r
836                     return "Unknown big array " + o.getClass();\r
837                 }\r
838             } else {\r
839                 return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o);\r
840             }\r
841         }\r
842         return null;\r
843     }\r
844 \r
845     protected String truncated(String type, String string, int originalLength) {\r
846         return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string;\r
847     }\r
848     \r
849     public static String htmlEscape(String s)\r
850     {\r
851         return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br/>");\r
852     }\r
853 \r
854     /**\r
855      * Get resource name(?) \r
856      * \r
857      * @param graph\r
858      * @param r\r
859      * @return\r
860      */\r
861     protected String getResourceName(ReadGraph graph, Resource r) {\r
862         try {\r
863 \r
864             String name = null;\r
865             //System.out.println("hasValue(" + NameUtils.getSafeName(graph, r, true));\r
866             if (graph.hasValue(r)) {\r
867                 // too large array may cause application to run out of memory.\r
868                 //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));\r
869                 Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
870                 if (type!=null) {\r
871                         Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );\r
872                         if (type.equals( rviBinding.type() )) {\r
873                                 RVI rvi = graph.getValue(r, rviBinding);\r
874                                                                 \r
875                                 try {\r
876                                         Variable v = Variables.getConfigurationContext( graph, currentElement );\r
877                                         name = rvi.asString(graph, v);\r
878 //                                      name = rvi.resolve(graph, v).getURI(graph);\r
879                                 } catch (DatabaseException dbe ) {\r
880                                         name = rvi.toString( graph );\r
881                                 }\r
882                         } else {\r
883                                 long valueSize = NameUtils.getPossibleValueSize(graph, r);\r
884                                 if (valueSize > RESOURCE_NAME_MAX_LENGTH) {\r
885 //                                      Binding b = Bindings.getBinding(type);\r
886 //                                      Object v = graph.getValue(r, b);\r
887 //                                      Serializer s = Bindings.getSerializerUnchecked(b);\r
888 //                                      int size = s.getSize(v);\r
889                                     name = "Approx. " + valueSize + " byte literal of type " + type.toSingleLineString();\r
890                                 } else {                                        \r
891                                         Binding b = Bindings.getBinding(type);\r
892                                         Object v = graph.getValue(r, b);\r
893                                         if (b.type() instanceof StringType) {\r
894                                                 name = (String) graph.getValue(r, b);\r
895                                         } else {\r
896                                             name = b.toString(v, false);\r
897                                         }\r
898                                     if (type instanceof ArrayType){\r
899                                         name = name.substring(1, name.length()-1);\r
900                                     }\r
901                                 }\r
902                         }\r
903                 } else {\r
904                     Object o = graph.getValue(r);\r
905                     name = toName(o);\r
906                 }\r
907                 \r
908                 if(name.isEmpty()) {\r
909                         name = "<empty value>";\r
910                 }\r
911                 \r
912             }\r
913             // Does resource have a file ??\r
914             if (name == null) {\r
915 //                try {\r
916 //                    Accessor accessor = graph.getAccessor(r);\r
917 //                    name = "File of type " + accessor.type().toSingleLineString();\r
918 //                } catch (DatabaseException e) {\r
919 //                    // No file, try next alternative.\r
920 //                }\r
921             }\r
922             if (name == null) {\r
923                 //name = graph.adapt(r, String.class);\r
924                 //if(name.isEmpty())\r
925                 name = DebugUtils.getSafeLabel(graph, r);\r
926                 if (name.isEmpty())\r
927                     name = "<empty name>";\r
928             }\r
929 //            ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);\r
930 //            if(name == null)\r
931 //                return "[" + r.getResourceId() + " - " + support.getCluster(r) + "]";\r
932             if(displayClusters) {\r
933 //                SessionDebug debug = graph.getSession().getDebug();\r
934 //                name += " (" + debug.getCluster(r) + ")";\r
935             }\r
936             return name;\r
937         } catch (AdaptionException e) {\r
938 //            e.printStackTrace();\r
939             String name = safeReadableString(graph, r);\r
940 //          try {\r
941 //                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_adaption_problem, MessageUtil.resource(session, r, "this resource")), e));\r
942 //            } catch (ReferenceSerializationException e1) {\r
943 //                e1.printStackTrace();\r
944 //                ErrorLogger.defaultLogWarning(e1);\r
945 //            }\r
946             return name;\r
947         } catch (Exception e) {\r
948             ErrorLogger.defaultLogError(e);\r
949             String name = safeReadableString(graph, r);\r
950 //          try {\r
951 //                MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_formulation_problem, MessageUtil.resource(session, r, "this resource")), e));\r
952 //            } catch (ReferenceSerializationException e1) {\r
953 //                e1.printStackTrace();\r
954 //                ErrorLogger.defaultLogWarning(e1);\r
955 //            }\r
956             return name;\r
957         }\r
958     }\r
959 \r
960     private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException {\r
961         String name;\r
962         try {\r
963             Layer0 L0 = Layer0.getInstance(graph);\r
964             if (graph.isInstanceOf(r, L0.Assertion)) {\r
965                 Resource pred = graph.getSingleObject(r, L0.HasPredicate);\r
966                 // Don't know how I encountered this but it seems to be possible in some cases..\r
967                 // Resource obj = graph.getSingleObject(r, L0.HasObject);\r
968                 Resource obj = graph.getPossibleObject(r, L0.HasObject);\r
969                 String tmp = htmlEscape( getResourceName(graph, pred) + " -> " + (obj == null ? "No object ?" : getResourceName(graph, obj)) + " (Assertion)" );\r
970                 name = tmp.substring(0, Math.min(80, tmp.length()));\r
971             } else {\r
972                 String resourceName = getResourceName(graph, r);\r
973                 if(resourceName.equals("Inverse")) {\r
974                     Resource inverse = graph.getPossibleInverse(r);\r
975                     if(inverse != null && graph.hasStatement(inverse, L0.ConsistsOf, r))\r
976                         resourceName = getResourceName(graph, inverse) + "/Inverse";\r
977                 }\r
978                 String tmp = htmlEscape( resourceName );\r
979                 name = tmp.substring(0, Math.min(80, tmp.length()));\r
980             }\r
981             \r
982         } catch (OutOfMemoryError e) {\r
983             name = "OutOfMemoryError";\r
984         }\r
985         String ret = "<a href=\"simantics:browser-link" + getLinkString(r) + "\">"\r
986         + name\r
987         + "</a>";\r
988         if (graph.isInstanceOf(r, L0.Literal)) {\r
989             ret += "&nbsp;<a class=\"edit-link\" href=\"simantics:browser-edit-value" + getLinkString(r) + "\">"\r
990             + "(edit)"\r
991             + "</a>";\r
992         }\r
993         return ret;\r
994     }\r
995 \r
996     private String getStatementRemoveRef(Resource s, Resource p, Resource o) {\r
997         return "<a href=\"simantics:browser-remove" + getStatementString(s, p, o)\r
998         + "\" title=\"Remove this statement\">X</a>";\r
999     }\r
1000 \r
1001     private void updatePred(StringBuffer content, ReadGraph graph, Resource subj, Resource pred, List<Resource[]> stats) throws DatabaseException {\r
1002         // Generate output content from statements\r
1003         String[][] objects = new String[stats.size()][];\r
1004         for (int i = 0; i < stats.size(); ++i) {\r
1005             Resource stmSubject = stats.get(i)[0];\r
1006             Resource object = stats.get(i)[1];\r
1007 \r
1008             objects[i] = new String[4];\r
1009             objects[i][0] = getLinkString(object);\r
1010             objects[i][1] = htmlEscape( getResourceName(graph, object) );\r
1011             objects[i][2] = getResourceRef(graph, object);\r
1012 \r
1013             // Make a note if the statement was acquired.\r
1014             if(!stmSubject.equals(subj)) {\r
1015                 objects[i][3] = " (in " + getResourceRef(graph, stmSubject) + ")";\r
1016             }\r
1017         }\r
1018 \r
1019         // Sort statements by object name\r
1020         Arrays.sort(objects, new Comparator<String[]>() {\r
1021             @Override\r
1022             public int compare(String[] o1, String[] o2) {\r
1023                 return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1[1], o2[1]);\r
1024             }\r
1025         });\r
1026 \r
1027         // Output table rows\r
1028         for (int i = 0; i < objects.length; ++i) {\r
1029             content.append("<tr>");\r
1030             // Predicate column\r
1031             if (i == 0)\r
1032                 content.append("<td rowspan=\"").append(objects.length).append("\" valign=\"top\">").append(getResourceRef(graph, pred)).append("</td>");\r
1033 \r
1034             // Object column\r
1035             if (objects[i][3] == null) content.append("<td>");\r
1036             else content.append("<td class=\"acquired\">");\r
1037 \r
1038             content.append(objects[i][2]);\r
1039             if (objects[i][3] != null)\r
1040                 content.append(objects[i][3]);\r
1041 \r
1042             content.append("</td>");\r
1043             \r
1044             VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class);\r
1045             VirtualGraph vg = vgs.getGraph(graph, subj, pred, links.getRight(objects[i][0]));\r
1046             \r
1047             if(vg != null) {\r
1048                 content.append("<td>").append(vg.toString()).append("</td>");\r
1049             } else {\r
1050                 content.append("<td>DB</td>");\r
1051             }\r
1052             \r
1053 \r
1054             // Statement remove -link column\r
1055             // Only allowed for non-acquired statements.\r
1056             if (objects[i][3] == null) {\r
1057                 content.append("<td class=\"remove\">");\r
1058                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
1059                 content.append("</td>");\r
1060             }\r
1061             content.append("</tr>");\r
1062         }\r
1063     }\r
1064 \r
1065     private void updateTag(StringBuffer content, ReadGraph graph, Resource subj, Resource tag) throws DatabaseException {\r
1066 \r
1067         // Generate output content from statements\r
1068         String ref = getResourceRef(graph, tag);\r
1069 \r
1070         content.append("<tr>");\r
1071         content.append("<td rowspan=\"1\" colspan=\"3\" valign=\"top\">").append(ref).append("</td>");\r
1072         //content.append("<td>" + name + "</td>");\r
1073         content.append("<td class=\"remove\">");\r
1074         content.append(getStatementRemoveRef(subj, tag, subj));\r
1075         content.append("</td>");\r
1076         content.append("</tr>");\r
1077 \r
1078     }\r
1079 \r
1080     private void updateOrderedSet(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {\r
1081         //List<Resource> list = OrderedSetUtils.toList(graph, subj);\r
1082         /*\r
1083         // Generate output content from statements\r
1084         String[][] objects = new String[stats.size()][];\r
1085         for (int i = 0; i < stats.size(); ++i) {\r
1086             Resource stmSubject = stats.get(i)[0];\r
1087             Resource object = stats.get(i)[1];\r
1088 \r
1089             objects[i] = new String[4];\r
1090             objects[i][0] = getLinkString(object);\r
1091             objects[i][1] = getResourceName(graph, object);\r
1092             objects[i][2] = getResourceRef(graph, object);\r
1093 \r
1094             // Make a note if the statement was acquired.\r
1095             if(!stmSubject.equals(subj)) {\r
1096                 objects[i][3] = " (acquired from " + getResourceRef(graph, stmSubject) + ")";\r
1097             }\r
1098         }\r
1099 \r
1100         // Sort statements by object name\r
1101         Arrays.sort(objects, new Comparator<String[]>() {\r
1102             @Override\r
1103             public int compare(String[] o1, String[] o2) {\r
1104                 return o1[1].compareTo(o2[1]);\r
1105             }\r
1106         });*/\r
1107 \r
1108         List<String> list = new ArrayList<String>();\r
1109         Resource cur = subj;\r
1110         while(true) {\r
1111             try {\r
1112                 cur = OrderedSetUtils.next(graph, subj, cur);\r
1113             } catch(DatabaseException e) {\r
1114                 list.add("<span style=\"color:red;font-weight:bold\">BROKEN ORDERED SET:<br/></span><span style=\"color:red\">" + e.getMessage() + "</span>");\r
1115                 Resource inv = graph.getPossibleInverse(subj);\r
1116                 for(Statement stat : graph.getStatements(cur, L0.IsRelatedTo)) {\r
1117                     if(stat.getSubject().equals(cur)) {\r
1118                         if(stat.getPredicate().equals(subj)) {\r
1119                             list.add("next " + getResourceRef(graph, stat.getObject()));\r
1120                         }\r
1121                         else if(stat.getPredicate().equals(inv)) {\r
1122                             list.add("prev " + getResourceRef(graph, stat.getObject()));\r
1123                         }\r
1124                     }\r
1125                 }\r
1126                 break;\r
1127             }\r
1128             if(cur.equals(subj))\r
1129                 break;\r
1130             list.add(getResourceRef(graph, cur));\r
1131         }\r
1132 \r
1133         // Output table rows\r
1134         for (int i = 0; i < list.size() ; ++i) {\r
1135             content.append("<tr>");\r
1136             // Predicate column\r
1137             if (i == 0)\r
1138                 content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Ordered Set Elements</td>");\r
1139 \r
1140             // Object column\r
1141             content.append("<td>");\r
1142             content.append(list.get(i));\r
1143             content.append("</td>");\r
1144 \r
1145             // Statement remove -link column\r
1146             // Only allowed for non-acquired statements.\r
1147             /*if (objects[i][3] == null) {\r
1148                 content.append("<td class=\"remove\">");\r
1149                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
1150                 content.append("</td>");\r
1151             }*/\r
1152             content.append("</tr>");\r
1153         }\r
1154     }\r
1155 \r
1156     private void updateLinkedList(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException {\r
1157 \r
1158         List<String> list = new ArrayList<String>();\r
1159         \r
1160         try {\r
1161                 List<Resource> resources = ListUtils.toList(graph, subj);\r
1162                 for(Resource element : resources) {\r
1163                     list.add(getResourceRef(graph, element));\r
1164                 }\r
1165         } catch (DatabaseException e) {\r
1166                 throw new ValidationException(e);\r
1167         }\r
1168 \r
1169         // Output table rows\r
1170         for (int i = 0; i < list.size() ; ++i) {\r
1171             content.append("<tr>");\r
1172             // Predicate column\r
1173             if (i == 0)\r
1174                 content.append("<td rowspan=\"").append(list.size()).append("\" valign=\"top\">Linked List Elements</td>");\r
1175 \r
1176             // Object column\r
1177             content.append("<td>");\r
1178             content.append(list.get(i));\r
1179             content.append("</td><td>DB</td>");\r
1180 \r
1181             // Statement remove -link column\r
1182             // Only allowed for non-acquired statements.\r
1183             /*if (objects[i][3] == null) {\r
1184                 content.append("<td class=\"remove\">");\r
1185                 content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0])));\r
1186                 content.append("</td>");\r
1187             }*/\r
1188             content.append("</tr>");\r
1189         }\r
1190     }\r
1191 \r
1192     protected synchronized void updateContent(final ReadGraph graph, Resource... resources) throws DatabaseException {\r
1193         L0 = Layer0.getInstance(graph);\r
1194 \r
1195         links.clear();\r
1196         StringBuffer content = new StringBuffer();\r
1197 \r
1198         // Generate HTML -page\r
1199         content.append("<html>\n<head>\n")\r
1200         .append(getHead())\r
1201         .append("\n</head>\n")\r
1202         .append("<body>\n")\r
1203         .append("<div id=\"mainContent\">\n\n");\r
1204 \r
1205         for (Resource r : resources) {\r
1206             if (r == null)\r
1207                 continue;\r
1208 \r
1209             String uri = null;\r
1210             try {\r
1211                 uri = graph.syncRequest(new ResourceToPossibleURI(r));\r
1212             } catch (Exception e) {\r
1213                 ErrorLogger.defaultLogError(e);\r
1214                 uri = "Cannot get URI: " + e.getMessage();\r
1215             }\r
1216 \r
1217             // Top DIV\r
1218             content.append("<div id=\"top\">\n");\r
1219             content.append("<table class=\"top\">\n");\r
1220             if (uri != null) {\r
1221                 content.append("<tr><td class=\"top_key\">URI</td><td class=\"top_value\"><span id=\"uri\">").append(uri).append("</span></td></tr>\n");\r
1222             }\r
1223 \r
1224             XSupport xs = graph.getService(XSupport.class);\r
1225             boolean immutable = xs.getImmutable(r);\r
1226 \r
1227             Collection<Statement> statements = graph.getStatements(r, L0.IsWeaklyRelatedTo);\r
1228             HashMultiMap<Resource, Resource[]> map = new HashMultiMap<Resource, Resource[]>();\r
1229             for(org.simantics.db.Statement statement : statements) {\r
1230                 Resource predicate = null;\r
1231                 Resource subject = null;\r
1232                 Resource obj = null;\r
1233                 try {\r
1234                     predicate = statement.getPredicate();\r
1235                     subject = statement.getSubject();\r
1236                     obj = statement.getObject();\r
1237                     map.add(predicate, new Resource[] {subject, obj});\r
1238                 } catch (Throwable e) {\r
1239                     ErrorLogger.defaultLogError("Cannot find statement " + subject + " " + predicate + " " + obj, e);\r
1240                 }\r
1241             }\r
1242             SerialisationSupport ss = graph.getSession().getService(SerialisationSupport.class);\r
1243             ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class);\r
1244             content.append("<tr><td class=\"top_key\">Identifiers</td><td class=\"top_value\">");\r
1245             content.append("<span id=\"resource_id\">")\r
1246             .append(" RID = $").append(r.getResourceId())\r
1247             .append(" Resource Key = ").append(ss.getTransientId(r))\r
1248             .append(" CID = ").append(support.getCluster(r));\r
1249             content.append("</span></td>");\r
1250             if (immutable)\r
1251                 content.append("<td class=\"remove\">[IMMUTABLE]</td>");\r
1252             content.append("</tr>\n");\r
1253  \r
1254             boolean isClusterSet = support.isClusterSet(r);\r
1255             Resource parentSet = support.getClusterSetOfCluster(r);\r
1256             String parentSetURI = parentSet != null ? graph.getPossibleURI(parentSet) : null;\r
1257             \r
1258             content.append("<tr><td class=\"top_key\">Clustering</td><td class=\"top_value\">");\r
1259             content.append("<span id=\"resource_id\">");\r
1260             \r
1261             if(parentSetURI != null)\r
1262                 content.append(" Containing cluster set = ").append(parentSetURI);\r
1263             else if (parentSet != null)\r
1264                 content.append(" Containing cluster set = ").append(parentSet.toString());\r
1265             else \r
1266                 content.append(" Not in any cluster set ");\r
1267             \r
1268             content.append("</span></td>");\r
1269             if (isClusterSet)\r
1270                 content.append("<td class=\"remove\">[CLUSTER SET]</td>");\r
1271             content.append("</tr>\n");\r
1272             \r
1273             // If the resource has a value, show it.\r
1274             String resourceValue = getResourceValue(graph, r);\r
1275             if (resourceValue != null) {\r
1276                 content\r
1277                 .append("<tr><td class=\"top_key\">Attached value</td><td class=\"top_value\">")\r
1278                 .append(htmlEscape(resourceValue))\r
1279                 .append("</td></tr>\n");\r
1280             }\r
1281 \r
1282             // Close #top\r
1283             content.append("</table>\n");\r
1284             content.append("</div>\n");\r
1285 \r
1286             content.append("\n<div id=\"data\">\n");\r
1287             content.append("<table>\n")\r
1288             .append("<tr><th>Predicate</th><th>Object</th><th>Graph</th></tr>")\r
1289             .append("<tr><td class=\"subtitle\" colspan=\"3\">Basic information</td></tr>");\r
1290 \r
1291             boolean isOrderedSet = graph.isInstanceOf(r, L0.OrderedSet);\r
1292             boolean isLinkedList = graph.isInstanceOf(r, L0.List);\r
1293 //            map.remove(r);\r
1294 \r
1295             // BASIC INFORMATION:\r
1296             for (Resource pred :\r
1297                 new Resource[] {L0.HasName, L0.InstanceOf,\r
1298                     L0.Inherits, L0.SubrelationOf,\r
1299                     L0.PartOf, L0.ConsistsOf})\r
1300                 if (map.containsKey(pred))\r
1301                     updatePred(content, graph, r, pred, map.remove(pred));\r
1302 \r
1303             // TAGS\r
1304             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Tags</td></tr>");\r
1305             for(Statement stm : statements) {\r
1306                 if(stm.getSubject().equals(stm.getObject())) {\r
1307                     updateTag(content, graph, r, stm.getPredicate());\r
1308                     map.remove(stm.getPredicate());\r
1309                 }\r
1310             }\r
1311 \r
1312             // ORDERED SETS\r
1313             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Ordered Sets</td></tr>");\r
1314             for(Statement stm : statements) {\r
1315                 Resource predicate = stm.getPredicate();\r
1316                 if(graph.isInstanceOf(stm.getPredicate(), L0.OrderedSet)) {\r
1317                     updateTag(content, graph, r, stm.getPredicate());\r
1318                     if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)\r
1319                         map.remove(stm.getPredicate());\r
1320                 }\r
1321                 Resource inverse = graph.getPossibleInverse(predicate);\r
1322                 if (inverse != null) {\r
1323                     if(graph.isInstanceOf(inverse, L0.OrderedSet)) {\r
1324                         if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1)\r
1325                             map.remove(stm.getPredicate());\r
1326                     }\r
1327                 } else {\r
1328                     // FIXME : should we infor missing inverse\r
1329                 }\r
1330             }\r
1331 \r
1332             // IS RELATED TO\r
1333             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Is Related To</td></tr>");\r
1334 \r
1335             // ELEMENTS OF ORDERED SET\r
1336             if(isOrderedSet) {\r
1337                 //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");\r
1338                 try {\r
1339                     updateOrderedSet(content, graph, r);\r
1340                 } catch (ValidationException e) {\r
1341                     content.append("<td colspan=\"3\"><span style=\"color:red;font-weight:bold\">BROKEN ORDERED SET:<br/></span><span style=\"color:red\">").append(e.getMessage()).append("</span></td>");\r
1342                 }\r
1343             }\r
1344 \r
1345             // ELEMENTS OF LINKED LIST\r
1346             if(isLinkedList) {\r
1347                 //content.append("<tr><td class=\"subtitle\" colspan=\"2\">Ordered set</td></tr>");\r
1348                 try {\r
1349                     updateLinkedList(content, graph, r);\r
1350                 } catch (ValidationException e) {\r
1351                     content.append("<td colspan=\"3\"><span style=\"color:red;font-weight:bold\">BROKEN LINKED LIST:<br/></span><span style=\"color:red\">").append(e.getMessage()).append("</span></td>");\r
1352                 }\r
1353             }\r
1354 \r
1355             // IS RELATED TO (other)\r
1356             Resource[] preds = map.keySet().toArray(new Resource[0]);\r
1357             final Map<Resource, String> strmap = new HashMap<Resource, String>(preds.length);\r
1358             for(Resource pred : preds) {\r
1359                 String str = htmlEscape( getResourceName(graph, pred) );\r
1360                 if(str == null)\r
1361                     str = "<null>";\r
1362                 strmap.put(pred, str);\r
1363             }\r
1364             Arrays.sort(preds, new Comparator<Resource>() {\r
1365                 @Override\r
1366                 public int compare(Resource o1, Resource o2) {\r
1367                     return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(strmap.get(o1), strmap.get(o2));\r
1368                 }\r
1369             });\r
1370             for(Resource pred : preds)\r
1371                 if(graph.isSubrelationOf(pred, L0.IsRelatedTo))\r
1372                     updatePred(content, graph, r, pred, map.get(pred));\r
1373 \r
1374             // OTHER STATEMENTS\r
1375             content.append("<tr><td class=\"subtitle\" colspan=\"3\">Other statements</td></tr>");\r
1376             for(Resource pred : preds)\r
1377                 if(!graph.isSubrelationOf(pred, L0.IsRelatedTo))\r
1378                     updatePred(content, graph, r, pred, map.get(pred));\r
1379             content.append("</table>\n");\r
1380         }\r
1381         // Close #data\r
1382         content.append("</div>\n\n");\r
1383         // Close #mainContent\r
1384         content.append("</div>\n");\r
1385         content.append("</body>\n</html>\n");\r
1386 \r
1387         // Update content\r
1388         final String finalContent = content.toString();\r
1389         if (!isDisposed()) {\r
1390             getDisplay().asyncExec(new Runnable() {\r
1391                 @Override\r
1392                 public void run() {\r
1393                     if (!browser.isDisposed())\r
1394                         browser.setText(finalContent);\r
1395                 }\r
1396             });\r
1397         }\r
1398     }\r
1399 \r
1400     private String getResourceValue(ReadGraph graph, Resource r) {\r
1401         try {\r
1402             if (graph.hasValue(r)) {\r
1403                 // too large array may cause application to run out of memory.\r
1404                 //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true));\r
1405                 Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
1406                 if (type != null) {\r
1407                     Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class );\r
1408                     if (type.equals( rviBinding.type() )) {\r
1409                         RVI rvi = graph.getValue(r, rviBinding);\r
1410                         try {\r
1411                             Variable v = Variables.getConfigurationContext( graph, r );\r
1412                             return rvi.asString(graph, v);\r
1413                         } catch (DatabaseException dbe ) {\r
1414                             return rvi.toString( graph );\r
1415                         }\r
1416                     } else {\r
1417                         Binding b = Bindings.getBinding(type);\r
1418                         Object v = graph.getValue(r, b);\r
1419                         Serializer s = Bindings.getSerializerUnchecked(b);\r
1420                         int size = s.getSize(v);\r
1421                         if (size > RESOURCE_VALUE_MAX_SIZE) {\r
1422                             return "Approx. " + size + " byte literal of type " + type.toSingleLineString();\r
1423                         } else {\r
1424                             return b.toString(v, false);\r
1425                         }\r
1426                     }\r
1427                 } else {\r
1428                     Object o = graph.getValue(r);\r
1429                     return toName(o);\r
1430                 }\r
1431             }\r
1432             return null;\r
1433         } catch (DatabaseException e) {\r
1434             return e.getMessage();\r
1435         } catch (IOException e) {\r
1436             return e.getMessage();\r
1437         } catch (BindingException e) {\r
1438             return e.getMessage();\r
1439         }\r
1440     }\r
1441 \r
1442     private static String safeReadableString(ReadGraph g, Resource r) {\r
1443         try {\r
1444             return NameUtils.getSafeName(g, r);\r
1445         } catch(Throwable throwable) {\r
1446             ErrorLogger.defaultLogError(throwable);\r
1447             return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";\r
1448         }\r
1449     }\r
1450 \r
1451 //    private static String safeReadableString(IEntity t) {\r
1452 //        try {\r
1453 //            return ResourceDebugUtils.getReadableNameForEntity(t);\r
1454 //        } catch(Throwable throwable) {\r
1455 //            throwable.printStackTrace();\r
1456 //            return "<font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font>";\r
1457 //        }\r
1458 //    }\r
1459 \r
1460     private String getLinkString(Container<Resource> t) {\r
1461         String link = links.getLeft(t.get());\r
1462         if(link == null) {\r
1463             link = UUID.randomUUID().toString();\r
1464             links.map(link, t.get());\r
1465         }\r
1466         return link;\r
1467     }\r
1468 \r
1469 //    private String getPropertyEditString(Container<Resource> t) {\r
1470 //        String link = links.getLeft(t.get());\r
1471 //        if(link == null) {\r
1472 //            link = UUID.randomUUID().toString();\r
1473 //            links.map(link, t.get());\r
1474 //        }\r
1475 //        return link;\r
1476 //    }\r
1477 \r
1478     private String getStatementString(Resource _s, Resource _p, Resource _o) {\r
1479         String s = getLinkString(_s);\r
1480         String p = getLinkString(_p);\r
1481         String o = getLinkString(_o);\r
1482         return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;\r
1483     }\r
1484 \r
1485 //    private String getStatementString(Statement stm) {\r
1486 //        String s = getLinkString(stm.getSubject());\r
1487 //        String p = getLinkString(stm.getPredicate());\r
1488 //        String o = getLinkString(stm.getObject());\r
1489 //        return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o;\r
1490 //    }\r
1491 \r
1492 //    private String toOutgoingTableRow(IEntity subject, Statement stm) {\r
1493 //        boolean isAcquired = !subject.equals(stm.getSubject());\r
1494 //\r
1495 //        String plainCell = "%s";\r
1496 //        String hrefCell = "<a href=\"%s\">%s</a>";\r
1497 //        String formatTemplate = isAcquired\r
1498 //                ? "<tr>\n<td class=\"acquired\">{0}</td>\n<td class=\"acquired\">{1}</td>\n<td class=\"acquired\">{1}</td>\n<td class=\"acquired\">{1}</td>\n</tr>"\r
1499 //                : "<tr>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n<td>{1}</td>\n</tr>";\r
1500 //        String format = NLS.bind(formatTemplate, plainCell, hrefCell);\r
1501 ////        System.out.println("format: " + format);\r
1502 //\r
1503 //        IEntity s = stm.getSubject();\r
1504 //        IEntity p = stm.getPredicate();\r
1505 //        IEntity o = stm.getObject();\r
1506 //\r
1507 ////        String timePart = "[" + timeToString(t.getBegin()) + ", " + timeToString(t.getEnd()) + "]";\r
1508 //\r
1509 //        try {\r
1510 //            return !isAcquired\r
1511 //                ? String.format(format,\r
1512 //                    "about:blank-remove" + getStatementString(stm), "Remove",\r
1513 //                    "about:blank-link" + getLinkString(s), safeReadableString(s),\r
1514 //                    "about:blank-link" + getLinkString(p), safeReadableString(p),\r
1515 //                    "about:blank-link" + getLinkString(o), safeReadableString(o))\r
1516 //                : String.format(format,\r
1517 //                    "Acquired",\r
1518 //                    "about:blank-link" + getLinkString(s), safeReadableString(s),\r
1519 //                    "about:blank-link" + getLinkString(p), safeReadableString(p),\r
1520 //                    "about:blank-link" + getLinkString(o), safeReadableString(o)\r
1521 //            );\r
1522 //        } catch (Throwable throwable) {\r
1523 //            return "<tr><td colspan=\"4\"><font color=\"red\"><i>"+throwable.getClass().getName()+"</i> "+throwable.getMessage()+"</font></td></tr>";\r
1524 //        }\r
1525 //    }\r
1526 \r
1527 //    private String intervalToString(Interval time) {\r
1528 //        return timeToString(time.getBegin()) + ", " + timeToString(time.getEnd());\r
1529 //    }\r
1530 //\r
1531 //    DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);\r
1532 //\r
1533 //    private String timeToString(long time) {\r
1534 //        if (time == Long.MIN_VALUE)\r
1535 //            return "-&infin;";\r
1536 //        if (time == Long.MAX_VALUE)\r
1537 //            return "+&infin;";\r
1538 //        //return String.valueOf(time);\r
1539 //        Date d = new Date(time);\r
1540 //        return dateTimeFormat.format(d);\r
1541 //    }\r
1542 \r
1543     private String getHead() {\r
1544         String result = "";\r
1545         if (cssPath != null) {\r
1546             result = "<link href=\"" + cssPath + "\" rel=\"stylesheet\" type=\"text/css\">";\r
1547         }\r
1548         return result;\r
1549     }\r
1550 \r
1551 }\r