/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.debug.ui; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Array; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.resource.ColorDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.LocationAdapter; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbenchSite; import org.simantics.databoard.Bindings; import org.simantics.databoard.Databoard; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.type.ArrayType; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.StringType; import org.simantics.databoard.util.ObjectUtils; import org.simantics.db.AsyncRequestProcessor; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.Statement; import org.simantics.db.VirtualGraph; import org.simantics.db.WriteGraph; import org.simantics.db.common.ResourceArray; import org.simantics.db.common.procedure.adapter.ProcedureAdapter; import org.simantics.db.common.request.Queries; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.uri.ResourceToPossibleURI; import org.simantics.db.common.utils.ListUtils; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.event.ChangeEvent; import org.simantics.db.event.ChangeListener; import org.simantics.db.exception.AdaptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ResourceNotFoundException; import org.simantics.db.exception.ValidationException; import org.simantics.db.layer0.adapter.StringModifier; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.service.ClusteringSupport; import org.simantics.db.service.GraphChangeListenerSupport; import org.simantics.db.service.SerialisationSupport; import org.simantics.db.service.VirtualGraphSupport; import org.simantics.db.service.XSupport; import org.simantics.debug.ui.internal.Activator; import org.simantics.debug.ui.internal.DebugUtils; import org.simantics.debug.ui.internal.HashMultiMap; import org.simantics.layer0.Layer0; import org.simantics.ui.dnd.LocalObjectTransfer; import org.simantics.ui.dnd.ResourceReferenceTransfer; import org.simantics.ui.dnd.ResourceTransferUtils; import org.simantics.ui.utils.ResourceAdaptionUtils; import org.simantics.utils.Container; import org.simantics.utils.FileUtils; import org.simantics.utils.datastructures.BijectionMap; import org.simantics.utils.datastructures.Callback; import org.simantics.utils.strings.AlphanumComparator; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.PathUtils; import org.simantics.utils.ui.workbench.WorkbenchUtils; public class GraphDebugger extends Composite { public interface HistoryListener { void historyChanged(); } private static final String STATEMENT_PART_SEPARATOR = ","; private final static String DEFAULT_DEBUGGER_CSS_FILE = "debugger.css"; private final static String DEFAULT_DEBUGGER_CSS_PATH = "css/" + DEFAULT_DEBUGGER_CSS_FILE; private static int RESOURCE_NAME_MAX_LENGTH = 1000; private static int RESOURCE_VALUE_MAX_SIZE = 16384; private final LocalResourceManager resourceManager; private String cssPath; private Browser browser; private final ColorDescriptor green = ColorDescriptor.createFrom(new RGB(0x57, 0xbc, 0x95)); private final boolean displayClusters = true; private final BijectionMap links = new BijectionMap(); private final LinkedList backHistory = new LinkedList(); private final LinkedList forwardHistory = new LinkedList(); private Resource currentElement = null; /** * The Session used to access the graph. Received from outside of this * class and therefore it is not disposed here, just used. */ private final Session session; private final CopyOnWriteArrayList historyListeners = new CopyOnWriteArrayList(); private final AsyncRequestProcessor updater; protected Layer0 L0; protected IWorkbenchSite site; private final ChangeListener changeListener = new ChangeListener() { @Override public void graphChanged(ChangeEvent e) { // This makes sure that the transaction for updating this // GraphDebugger get executed in a serialized fashion. updater.asyncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { updateContent(graph, currentElement); } }); } }; /** * @param parent * @param style * @param session * @param resource the initial resource to debug or null for * initially blank UI. * @param site the workbench site that contains this debugger, for workbench * service access */ public GraphDebugger(Composite parent, int style, final Session session, Resource resource, IWorkbenchSite site) { this(parent, style, session, resource); this.site = site; } /** * @param parent * @param style * @param session * @param resource the initial resource to debug or null for * initially blank UI. */ public GraphDebugger(Composite parent, int style, final Session session, Resource resource) { super(parent, style); Assert.isNotNull(session, "session is null"); this.session = session; this.currentElement = resource; this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); updater = session;//.getService(MergingGraphRequestProcessor.class); initializeCSS(); addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class); support.removeListener(changeListener); } }); } /** * When given to setStatus, indicates that the message shouldn't be touched * since null has a different meaning. */ private static final String DONT_TOUCH = "DONT_TOUCH"; protected void setStatus(String message, String error) { IStatusLineManager status = WorkbenchUtils.getStatusLine(site); if (status != null) { if (message != DONT_TOUCH) status.setMessage(message); if (error != DONT_TOUCH) status.setErrorMessage(error); } } public void defaultInitializeUI() { setLayout(new GridLayout(2, false)); setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); createResourceText(this); createDropLabel(this); createBrowser(this); } protected void initializeCSS() { // Extract default css to a temporary location if necessary. try { IPath absolutePath = PathUtils.getAbsolutePath(Activator.PLUGIN_ID, DEFAULT_DEBUGGER_CSS_PATH); if (absolutePath != null) { cssPath = absolutePath.toFile().toURI().toString(); } else { File tempDir = FileUtils.getOrCreateTemporaryDirectory(false); File css = new File(tempDir, DEFAULT_DEBUGGER_CSS_FILE); if (!css.exists()) { URL url = FileLocator.find(Activator.getDefault().getBundle(), new Path(DEFAULT_DEBUGGER_CSS_PATH), null); if (url == null) throw new FileNotFoundException("Could not find '" + DEFAULT_DEBUGGER_CSS_PATH + "' in bundle '" + Activator.PLUGIN_ID + "'"); cssPath = FileUtils.copyResource(url, css, true).toURI().toString(); } else { cssPath = css.toURI().toString(); } } } catch (IOException e) { // CSS extraction failed, let's just live without it then. ErrorLogger.defaultLogWarning(e); } } private static final String PROMPT_TEXT = "Enter resource ID (RID) or URI"; public void createResourceText(final Composite parent) { final Text text = new Text(parent, SWT.BORDER); text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); text.setText(PROMPT_TEXT); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(text); text.addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { if (text.getText().trim().equals("")) { text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); text.setText(PROMPT_TEXT); } } @Override public void focusGained(FocusEvent e) { if (text.getText().trim().equals(PROMPT_TEXT)) { text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_BLACK)); text.setText(""); } text.selectAll(); } }); text.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.CR) { String input = text.getText(); setLookupInput(input); } } }); final Button button = new Button(parent, SWT.FLAT); button.setText("&Lookup"); button.setEnabled(false); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false).applyTo(button); text.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.keyCode == 13) { String input = text.getText(); setLookupInput(input); button.setFocus(); } } }); button.addSelectionListener(new SelectionListener() { @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } @Override public void widgetSelected(SelectionEvent e) { String input = text.getText(); setLookupInput(input); } }); text.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { String input = text.getText().trim(); if (!input.equals(PROMPT_TEXT) && !input.equals("")) button.setEnabled(true); else button.setEnabled(false); } }); } public void setLookupInput(String input) { // There's no harm in trimming out spaces from both ends of the input. input = input.trim(); SerialisationSupport support = session.getService(SerialisationSupport.class); if (input.startsWith("$")) { try { Resource r = support.getResource(Long.parseLong(input.substring(1))); changeLocation(r); } catch (NumberFormatException e1) { // Ignore, may happen for crap input setStatus(DONT_TOUCH, "Invalid '$'-prefixed input, expected resource ID"); } catch (Exception e1) { ErrorLogger.defaultLogError(e1); setStatus(DONT_TOUCH, "Resource ID lookup failed. See Error Log."); } return; } String[] parts = input.split("-"); if (parts.length == 1) { try { int resourceKey = Integer.parseInt(parts[0].trim()); Resource r = support.getResource(resourceKey); // Some validation, not enough though ClusteringSupport cs = session.getService(ClusteringSupport.class); long cluster = cs.getCluster(r); if(cluster > 0) { changeLocation(r); } } catch (NumberFormatException e1) { // Ignore, may happen for crap input setStatus(DONT_TOUCH, "Invalid input, expected transient resource ID"); } catch (Exception e1) { ErrorLogger.defaultLogError(e1); setStatus(DONT_TOUCH, "Transient resource ID lookup failed. See Error Log."); } } else if (parts.length == 2) { try { int resourceIndex = Integer.parseInt(parts[1]); long clusterId = Long.parseLong(parts[0]); ClusteringSupport cs = session.getService(ClusteringSupport.class); Resource r = cs.getResourceByIndexAndCluster(resourceIndex, clusterId); changeLocation(r); } catch (NumberFormatException e1) { // Ignore, may happen for crap input setStatus(DONT_TOUCH, "Invalid input, expected index & cluster IDs"); } catch (Exception e1) { ErrorLogger.defaultLogError(e1); setStatus(DONT_TOUCH, "Index & cluster -based lookup failed. See Error Log."); } } // Try to see if the input data is an URI reference try { // First check that the input really is a proper URI. String uri = input; if (!input.equals("http:/") && input.endsWith("/")) uri = input.substring(0, input.length() - 1); new URI(uri); Resource r = session.syncRequest( Queries.resource( uri ) ); changeLocation(r); return; } catch (URISyntaxException e) { // Ignore, this is not a proper URI at all. } catch (ResourceNotFoundException e1) { // Ok, this was an URI, but no resource was found. setStatus(DONT_TOUCH, "Resource for URI '" + input + "' not found"); return; } catch (DatabaseException e1) { setStatus(DONT_TOUCH, "URI lookup failed. See Error Log."); ErrorLogger.defaultLogError(e1); } setStatus(DONT_TOUCH, "Invalid input, resource ID or URI expected"); } public Label createDropLabel(Composite parent) { final Label label = new Label(parent, SWT.BORDER); label.setAlignment(SWT.CENTER); label.setText("Drag a resource here to examine it in this debugger!"); label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).hint(SWT.DEFAULT, 20).span(2, 1).grab(true, false).applyTo(label); // Add resource id drop support to the drop-area. DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY); dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() }); dropTarget.addDropListener(new DropTargetAdapter() { @Override public void dragEnter(DropTargetEvent event) { event.detail = DND.DROP_LINK; label.setBackground((Color) resourceManager.get(green)); return; } @Override public void dragLeave(DropTargetEvent event) { label.setBackground(null); } @Override public void drop(DropTargetEvent event) { label.setBackground(null); ResourceArray[] data = parseEventData(event); if (data == null || data.length != 1) { event.detail = DND.DROP_NONE; return; } final ResourceArray array = data[0]; final Resource r = array.resources[array.resources.length - 1]; changeLocation(r); } private ResourceArray[] parseEventData(DropTargetEvent event) { //System.out.println("DATA: " + event.data); if (event.data instanceof String) { try { SerialisationSupport support = session.getService(SerialisationSupport.class); return ResourceTransferUtils.readStringTransferable(support, (String) event.data).toResourceArrayArray(); } catch (IllegalArgumentException e) { ErrorLogger.defaultLogError(e); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); } } ResourceArray[] ret = ResourceAdaptionUtils.toResourceArrays(event.data); if (ret.length > 0) return ret; return null; } }); return label; } public Browser createBrowser(Composite parent) { try { browser = new Browser(parent, SWT.MOZILLA); } catch (SWTError e) { //System.out.println("Could not instantiate Browser: " + e.getMessage()); browser = new Browser(parent, SWT.NONE); } GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(browser); // Left/right arrows for back/forward browser.addKeyListener(new KeyAdapter() { // // @Override // public void keyPressed(KeyEvent e) { // if (e.keyCode == SWT.F5) { // refreshBrowser(); // } // } // } @Override public void keyReleased(KeyEvent e) { // System.out.println("key, char: " + e.keyCode + ", " + (int) e.character + " (" + e.character + ")"); if (e.keyCode == SWT.BS) { back(); } if (e.keyCode == SWT.F5) { refreshBrowser(); } if ((e.stateMask & SWT.ALT) != 0) { if (e.keyCode == SWT.ARROW_RIGHT) forward(); if (e.keyCode == SWT.ARROW_LEFT) back(); } } }); // Add listener for debugging functionality browser.addLocationListener(new LocationAdapter() { @Override public void changing(LocationEvent event) { String location = event.location; if (location.startsWith("simantics:browser")) location = "about:" + location.substring(17); //System.out.println("changing: location=" + location); // Do not follow links that are meant as actions that are // handled below. event.doit = false; if ("about:blank".equals(location)) { // Just changing to the same old blank url is ok since it // allows the browser to refresh itself. event.doit = true; } if (location.startsWith("about:-link")) { String target = location.replace("about:-link", ""); Resource element = links.getRight(target); if (element == currentElement) { event.doit = false; return; } changeLocation(element); } else if (location.startsWith("about:-remove")) { String target = location.replace("about:-remove", ""); String n[] = target.split(STATEMENT_PART_SEPARATOR); if (n.length != 3) return; final Resource s = links.getRight(n[0]); final Resource p = links.getRight(n[1]); final Resource o = links.getRight(n[2]); // Make sure this is what the use wants. MessageDialog md = new MessageDialog( getShell(), "Confirm action...", null, "This action will remove the selected statement.\nAre you sure you want to proceed with this action?", MessageDialog.QUESTION, new String[] { "Cancel", "Continue" }, 0); if (md.open() != 1) { return; } session.asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph g) throws DatabaseException { try { List ls = OrderedSetUtils.toList(g, s); if(ls.contains(o)) OrderedSetUtils.remove(g, s, o); } catch (DatabaseException e) { } try { List ls = ListUtils.toList(g, s); if(ls.contains(o)) ListUtils.removeElement(g, s, o); } catch (DatabaseException e) { } g.denyStatement(s, p, o); } }, new Callback() { @Override public void run(DatabaseException parameter) { refreshBrowser(); } }); } else if (location.startsWith("about:-edit-value")) { String target = location.replace("about:-edit-value", ""); final Resource o = links.getRight(target); session.asyncRequest(new ReadRequest() { String previousValue; @Override public void run(ReadGraph graph) throws DatabaseException { previousValue = getResourceName(graph, o); final StringModifier modifier = graph.adapt(o, StringModifier.class); getDisplay().asyncExec(new Runnable() { @Override public void run() { InputDialog dialog = new InputDialog( getShell(), "Edit Value", null, previousValue, new IInputValidator() { @Override public String isValid(String newText) { return modifier.isValid(newText); } }) { private static final String DIALOG = "DebuggerEditValueDialog"; //$NON-NLS-1$ private IDialogSettings dialogBoundsSettings; @Override protected IDialogSettings getDialogBoundsSettings() { if (dialogBoundsSettings == null) { IDialogSettings settings = Activator.getDefault().getDialogSettings(); dialogBoundsSettings = settings.getSection(DIALOG); if (dialogBoundsSettings == null) dialogBoundsSettings = settings.addNewSection(DIALOG); } return dialogBoundsSettings; } @Override protected Point getInitialSize() { Point defaultSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true); Point result = super.getInitialSize(); if (defaultSize.equals(result)) return new Point(600, 400); return result; } protected int getShellStyle() { return super.getShellStyle() | SWT.RESIZE; } protected org.eclipse.swt.widgets.Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); getText().setLayoutData( new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL | GridData.HORIZONTAL_ALIGN_FILL)); { Label label = new Label(composite, SWT.NONE); label.moveAbove(getText()); label.setText("Input new property value. For numeric vector values, separate numbers with comma (',')."); GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); label.setLayoutData(data); label.setFont(parent.getFont()); } return composite; } protected int getInputTextStyle() { return SWT.MULTI | SWT.BORDER; } }; int ok = dialog.open(); if (ok != Dialog.OK) return; final String value = dialog.getValue(); session.asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph g) throws DatabaseException { //modifier.modify( g, htmlEscape( value ) ); modifier.modify( g, value ); } }, new Callback() { @Override public void run(DatabaseException parameter) { if (parameter != null) ErrorLogger.defaultLogError(parameter); refreshBrowser(); } }); return; } }); } }, new ProcedureAdapter() { @Override public void exception(Throwable t) { ErrorLogger.defaultLogError(t); } }); } } }); // Schedule a request that updates the browser content. refreshBrowser(); GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class); support.removeListener(changeListener); return browser; } public void refreshBrowser() { if (currentElement == null) return; // Schedule a request that updates the browser content. updater.asyncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { updateContent(graph, currentElement); } }); } public Resource getDebuggerLocation() { return currentElement; } public void changeLocation(Resource element) { if (currentElement != null) { backHistory.addLast(currentElement); } currentElement = element; forwardHistory.clear(); refreshBrowser(); setStatus(DONT_TOUCH, null); fireHistoryChanged(); } public void addHistoryListener(HistoryListener l) { historyListeners.add(l); } public void removeHistoryListener(HistoryListener l) { historyListeners.remove(l); } private void fireHistoryChanged() { for (HistoryListener l : historyListeners) l.historyChanged(); } public boolean hasBackHistory() { return backHistory.isEmpty(); } public boolean hasForwardHistory() { return forwardHistory.isEmpty(); } public void back() { if (backHistory.isEmpty()) return; forwardHistory.addFirst(currentElement); currentElement = backHistory.removeLast(); refreshBrowser(); fireHistoryChanged(); } public void forward() { if (forwardHistory.isEmpty()) return; backHistory.addLast(currentElement); currentElement = forwardHistory.removeFirst(); refreshBrowser(); fireHistoryChanged(); } protected String toName(Object o) { Class clazz = o.getClass(); if (clazz.isArray()) { int length = Array.getLength(o); if (length > RESOURCE_NAME_MAX_LENGTH) { if (o instanceof byte[]) { byte[] arr = (byte[]) o; byte[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("byte", Arrays.toString(arr2), arr.length); } else if (o instanceof int[]) { int[] arr = (int[]) o; int[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("int", Arrays.toString(arr2), arr.length); } else if (o instanceof long[]) { long[] arr = (long[]) o; long[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("long", Arrays.toString(arr2), arr.length); } else if (o instanceof float[]) { float[] arr = (float[]) o; float[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("float", Arrays.toString(arr2), arr.length); } else if (o instanceof double[]) { double[] arr = (double[]) o; double[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("double", Arrays.toString(arr2), arr.length); } else if (o instanceof boolean[]) { boolean[] arr = (boolean[]) o; boolean[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("boolean", Arrays.toString(arr2), arr.length); } else if (o instanceof Object[]) { Object[] arr = (Object[]) o; Object[] arr2 = Arrays.copyOf(arr, RESOURCE_NAME_MAX_LENGTH); return truncated("Object", Arrays.toString(arr2), arr.length); } else { return "Unknown big array " + o.getClass(); } } else { return o.getClass().getComponentType() + "[" + length + "] = " + ObjectUtils.toString(o); } } return null; } protected String truncated(String type, String string, int originalLength) { return type + "[" + RESOURCE_NAME_MAX_LENGTH + "/" + originalLength + "] = " + string; } public static String htmlEscape(String s) { return s.replace("&", "&").replace("<", "<").replace(">", ">").replace("\n", "
"); } /** * Get resource name(?) * * @param graph * @param r * @return */ protected String getResourceName(ReadGraph graph, Resource r) { try { String name = null; //System.out.println("hasValue(" + NameUtils.getSafeName(graph, r, true)); if (graph.hasValue(r)) { // too large array may cause application to run out of memory. //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true)); Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class)); if (type!=null) { Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class ); if (type.equals( rviBinding.type() )) { RVI rvi = graph.getValue(r, rviBinding); try { Variable v = Variables.getConfigurationContext( graph, currentElement ); name = rvi.asString(graph, v); // name = rvi.resolve(graph, v).getURI(graph); } catch (DatabaseException dbe ) { name = rvi.toString( graph ); } } else { long valueSize = NameUtils.getPossibleValueSize(graph, r); if (valueSize > RESOURCE_NAME_MAX_LENGTH) { // Binding b = Bindings.getBinding(type); // Object v = graph.getValue(r, b); // Serializer s = Bindings.getSerializerUnchecked(b); // int size = s.getSize(v); name = "Approx. " + valueSize + " byte literal of type " + type.toSingleLineString(); } else { Binding b = Bindings.getBinding(type); Object v = graph.getValue(r, b); if (b.type() instanceof StringType) { name = (String) graph.getValue(r, b); } else { name = b.toString(v, false); } if (type instanceof ArrayType){ name = name.substring(1, name.length()-1); } } } } else { Object o = graph.getValue(r); name = toName(o); } if(name.isEmpty()) { name = ""; } } // Does resource have a file ?? if (name == null) { // try { // Accessor accessor = graph.getAccessor(r); // name = "File of type " + accessor.type().toSingleLineString(); // } catch (DatabaseException e) { // // No file, try next alternative. // } } if (name == null) { //name = graph.adapt(r, String.class); //if(name.isEmpty()) name = DebugUtils.getSafeLabel(graph, r); if (name.isEmpty()) name = ""; } // ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class); // if(name == null) // return "[" + r.getResourceId() + " - " + support.getCluster(r) + "]"; if(displayClusters) { // SessionDebug debug = graph.getSession().getDebug(); // name += " (" + debug.getCluster(r) + ")"; } return name; } catch (AdaptionException e) { // e.printStackTrace(); String name = safeReadableString(graph, r); // try { // MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_adaption_problem, MessageUtil.resource(session, r, "this resource")), e)); // } catch (ReferenceSerializationException e1) { // e1.printStackTrace(); // ErrorLogger.defaultLogWarning(e1); // } return name; } catch (Exception e) { ErrorLogger.defaultLogError(e); String name = safeReadableString(graph, r); // try { // MessageService.defaultLog(new DetailStatus(IDetailStatus.DEBUG, Activator.PLUGIN_ID, 0, NLS.bind(Messages.Name_formulation_problem, MessageUtil.resource(session, r, "this resource")), e)); // } catch (ReferenceSerializationException e1) { // e1.printStackTrace(); // ErrorLogger.defaultLogWarning(e1); // } return name; } } private String getResourceRef(ReadGraph graph, Resource r) throws DatabaseException { String name; try { Layer0 L0 = Layer0.getInstance(graph); if (graph.isInstanceOf(r, L0.Assertion)) { Resource pred = graph.getSingleObject(r, L0.HasPredicate); // Don't know how I encountered this but it seems to be possible in some cases.. // Resource obj = graph.getSingleObject(r, L0.HasObject); Resource obj = graph.getPossibleObject(r, L0.HasObject); String tmp = htmlEscape( getResourceName(graph, pred) + " -> " + (obj == null ? "No object ?" : getResourceName(graph, obj)) + " (Assertion)" ); name = tmp.substring(0, Math.min(80, tmp.length())); } else { String resourceName = getResourceName(graph, r); if(resourceName.equals("Inverse")) { Resource inverse = graph.getPossibleInverse(r); if(inverse != null && graph.hasStatement(inverse, L0.ConsistsOf, r)) resourceName = getResourceName(graph, inverse) + "/Inverse"; } String tmp = htmlEscape( resourceName ); name = tmp.substring(0, Math.min(80, tmp.length())); } } catch (OutOfMemoryError e) { name = "OutOfMemoryError"; } String ret = "" + name + ""; if (graph.isInstanceOf(r, L0.Literal)) { ret += " " + "(edit)" + ""; } return ret; } private String getStatementRemoveRef(Resource s, Resource p, Resource o) { return "X"; } private void updatePred(StringBuffer content, ReadGraph graph, Resource subj, Resource pred, List stats) throws DatabaseException { // Generate output content from statements String[][] objects = new String[stats.size()][]; for (int i = 0; i < stats.size(); ++i) { Resource stmSubject = stats.get(i)[0]; Resource object = stats.get(i)[1]; objects[i] = new String[4]; objects[i][0] = getLinkString(object); objects[i][1] = htmlEscape( getResourceName(graph, object) ); objects[i][2] = getResourceRef(graph, object); // Make a note if the statement was acquired. if(!stmSubject.equals(subj)) { objects[i][3] = " (in " + getResourceRef(graph, stmSubject) + ")"; } } // Sort statements by object name Arrays.sort(objects, new Comparator() { @Override public int compare(String[] o1, String[] o2) { return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1[1], o2[1]); } }); // Output table rows for (int i = 0; i < objects.length; ++i) { content.append(""); // Predicate column if (i == 0) content.append("").append(getResourceRef(graph, pred)).append(""); // Object column if (objects[i][3] == null) content.append(""); else content.append(""); content.append(objects[i][2]); if (objects[i][3] != null) content.append(objects[i][3]); content.append(""); VirtualGraphSupport vgs = graph.getService(VirtualGraphSupport.class); VirtualGraph vg = vgs.getGraph(graph, subj, pred, links.getRight(objects[i][0])); if(vg != null) { content.append("").append(vg.toString()).append(""); } else { content.append("DB"); } // Statement remove -link column // Only allowed for non-acquired statements. if (objects[i][3] == null) { content.append(""); content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0]))); content.append(""); } content.append(""); } } private void updateTag(StringBuffer content, ReadGraph graph, Resource subj, Resource tag) throws DatabaseException { // Generate output content from statements String ref = getResourceRef(graph, tag); content.append(""); content.append("").append(ref).append(""); //content.append("" + name + ""); content.append(""); content.append(getStatementRemoveRef(subj, tag, subj)); content.append(""); content.append(""); } private void updateOrderedSet(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException { //List list = OrderedSetUtils.toList(graph, subj); /* // Generate output content from statements String[][] objects = new String[stats.size()][]; for (int i = 0; i < stats.size(); ++i) { Resource stmSubject = stats.get(i)[0]; Resource object = stats.get(i)[1]; objects[i] = new String[4]; objects[i][0] = getLinkString(object); objects[i][1] = getResourceName(graph, object); objects[i][2] = getResourceRef(graph, object); // Make a note if the statement was acquired. if(!stmSubject.equals(subj)) { objects[i][3] = " (acquired from " + getResourceRef(graph, stmSubject) + ")"; } } // Sort statements by object name Arrays.sort(objects, new Comparator() { @Override public int compare(String[] o1, String[] o2) { return o1[1].compareTo(o2[1]); } });*/ List list = new ArrayList(); Resource cur = subj; while(true) { try { cur = OrderedSetUtils.next(graph, subj, cur); } catch(DatabaseException e) { list.add("BROKEN ORDERED SET:
" + e.getMessage() + ""); Resource inv = graph.getPossibleInverse(subj); for(Statement stat : graph.getStatements(cur, L0.IsRelatedTo)) { if(stat.getSubject().equals(cur)) { if(stat.getPredicate().equals(subj)) { list.add("next " + getResourceRef(graph, stat.getObject())); } else if(stat.getPredicate().equals(inv)) { list.add("prev " + getResourceRef(graph, stat.getObject())); } } } break; } if(cur.equals(subj)) break; list.add(getResourceRef(graph, cur)); } // Output table rows for (int i = 0; i < list.size() ; ++i) { content.append(""); // Predicate column if (i == 0) content.append("Ordered Set Elements"); // Object column content.append(""); content.append(list.get(i)); content.append(""); // Statement remove -link column // Only allowed for non-acquired statements. /*if (objects[i][3] == null) { content.append(""); content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0]))); content.append(""); }*/ content.append(""); } } private void updateLinkedList(StringBuffer content, ReadGraph graph, Resource subj) throws DatabaseException { List list = new ArrayList(); try { List resources = ListUtils.toList(graph, subj); for(Resource element : resources) { list.add(getResourceRef(graph, element)); } } catch (DatabaseException e) { throw new ValidationException(e); } // Output table rows for (int i = 0; i < list.size() ; ++i) { content.append(""); // Predicate column if (i == 0) content.append("Linked List Elements"); // Object column content.append(""); content.append(list.get(i)); content.append("DB"); // Statement remove -link column // Only allowed for non-acquired statements. /*if (objects[i][3] == null) { content.append(""); content.append(getStatementRemoveRef(subj, pred, links.getRight(objects[i][0]))); content.append(""); }*/ content.append(""); } } protected synchronized void updateContent(final ReadGraph graph, Resource... resources) throws DatabaseException { L0 = Layer0.getInstance(graph); links.clear(); StringBuffer content = new StringBuffer(); // Generate HTML -page content.append("\n\n") .append(getHead()) .append("\n\n") .append("\n") .append("
\n\n"); for (Resource r : resources) { if (r == null) continue; String uri = null; try { uri = graph.syncRequest(new ResourceToPossibleURI(r)); } catch (Exception e) { ErrorLogger.defaultLogError(e); uri = "Cannot get URI: " + e.getMessage(); } // Top DIV content.append("
\n"); content.append("\n"); if (uri != null) { content.append("\n"); } XSupport xs = graph.getService(XSupport.class); boolean immutable = xs.getImmutable(r); Collection statements = graph.getStatements(r, L0.IsWeaklyRelatedTo); HashMultiMap map = new HashMultiMap(); for(org.simantics.db.Statement statement : statements) { Resource predicate = null; Resource subject = null; Resource obj = null; try { predicate = statement.getPredicate(); subject = statement.getSubject(); obj = statement.getObject(); map.add(predicate, new Resource[] {subject, obj}); } catch (Throwable e) { ErrorLogger.defaultLogError("Cannot find statement " + subject + " " + predicate + " " + obj, e); } } SerialisationSupport ss = graph.getSession().getService(SerialisationSupport.class); ClusteringSupport support = graph.getSession().getService(ClusteringSupport.class); content.append(""); if (immutable) content.append(""); content.append("\n"); boolean isClusterSet = support.isClusterSet(r); Resource parentSet = support.getClusterSetOfCluster(r); String parentSetURI = parentSet != null ? graph.getPossibleURI(parentSet) : null; content.append(""); if (isClusterSet) content.append(""); content.append("\n"); // If the resource has a value, show it. String resourceValue = getResourceValue(graph, r); if (resourceValue != null) { content .append("\n"); } // Close #top content.append("
URI").append(uri).append("
Identifiers"); content.append("") .append(" RID = $").append(r.getResourceId()) .append(" Resource Key = ").append(ss.getTransientId(r)) .append(" CID = ").append(support.getCluster(r)); content.append("[IMMUTABLE]
Clustering"); content.append(""); if(parentSetURI != null) content.append(" Containing cluster set = ").append(parentSetURI); else if (parentSet != null) content.append(" Containing cluster set = ").append(parentSet.toString()); else content.append(" Not in any cluster set "); content.append("[CLUSTER SET]
Attached value") .append(htmlEscape(resourceValue)) .append("
\n"); content.append("
\n"); content.append("\n
\n"); content.append("\n") .append("") .append(""); boolean isOrderedSet = graph.isInstanceOf(r, L0.OrderedSet); boolean isLinkedList = graph.isInstanceOf(r, L0.List); // map.remove(r); // BASIC INFORMATION: for (Resource pred : new Resource[] {L0.HasName, L0.InstanceOf, L0.Inherits, L0.SubrelationOf, L0.PartOf, L0.ConsistsOf}) if (map.containsKey(pred)) updatePred(content, graph, r, pred, map.remove(pred)); // TAGS content.append(""); for(Statement stm : statements) { if(stm.getSubject().equals(stm.getObject())) { updateTag(content, graph, r, stm.getPredicate()); map.remove(stm.getPredicate()); } } // ORDERED SETS content.append(""); for(Statement stm : statements) { Resource predicate = stm.getPredicate(); if(graph.isInstanceOf(stm.getPredicate(), L0.OrderedSet)) { updateTag(content, graph, r, stm.getPredicate()); if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1) map.remove(stm.getPredicate()); } Resource inverse = graph.getPossibleInverse(predicate); if (inverse != null) { if(graph.isInstanceOf(inverse, L0.OrderedSet)) { if(map.get(stm.getPredicate()) != null && map.get(stm.getPredicate()).size() == 1) map.remove(stm.getPredicate()); } } else { // FIXME : should we infor missing inverse } } // IS RELATED TO content.append(""); // ELEMENTS OF ORDERED SET if(isOrderedSet) { //content.append(""); try { updateOrderedSet(content, graph, r); } catch (ValidationException e) { content.append(""); } } // ELEMENTS OF LINKED LIST if(isLinkedList) { //content.append(""); try { updateLinkedList(content, graph, r); } catch (ValidationException e) { content.append(""); } } // IS RELATED TO (other) Resource[] preds = map.keySet().toArray(new Resource[0]); final Map strmap = new HashMap(preds.length); for(Resource pred : preds) { String str = htmlEscape( getResourceName(graph, pred) ); if(str == null) str = ""; strmap.put(pred, str); } Arrays.sort(preds, new Comparator() { @Override public int compare(Resource o1, Resource o2) { return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(strmap.get(o1), strmap.get(o2)); } }); for(Resource pred : preds) if(graph.isSubrelationOf(pred, L0.IsRelatedTo)) updatePred(content, graph, r, pred, map.get(pred)); // OTHER STATEMENTS content.append(""); for(Resource pred : preds) if(!graph.isSubrelationOf(pred, L0.IsRelatedTo)) updatePred(content, graph, r, pred, map.get(pred)); content.append("
PredicateObjectGraph
Basic information
Tags
Ordered Sets
Is Related To
Ordered set
BROKEN ORDERED SET:
").append(e.getMessage()).append("
Ordered set
BROKEN LINKED LIST:
").append(e.getMessage()).append("
Other statements
\n"); } // Close #data content.append("
\n\n"); // Close #mainContent content.append("
\n"); content.append("\n\n"); // Update content final String finalContent = content.toString(); if (!isDisposed()) { getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!browser.isDisposed()) browser.setText(finalContent); } }); } } private String getResourceValue(ReadGraph graph, Resource r) { try { if (graph.hasValue(r)) { // too large array may cause application to run out of memory. //System.out.println("getValue(" + NameUtils.getSafeName(graph, r, true)); Datatype type = graph.getPossibleRelatedValue(r, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class)); if (type != null) { Binding rviBinding = graph.getService(Databoard.class).getBindingUnchecked( RVI.class ); if (type.equals( rviBinding.type() )) { RVI rvi = graph.getValue(r, rviBinding); try { Variable v = Variables.getConfigurationContext( graph, r ); return rvi.asString(graph, v); } catch (DatabaseException dbe ) { return rvi.toString( graph ); } } else { Binding b = Bindings.getBinding(type); Object v = graph.getValue(r, b); Serializer s = Bindings.getSerializerUnchecked(b); int size = s.getSize(v); if (size > RESOURCE_VALUE_MAX_SIZE) { return "Approx. " + size + " byte literal of type " + type.toSingleLineString(); } else { return b.toString(v, false); } } } else { Object o = graph.getValue(r); return toName(o); } } return null; } catch (DatabaseException e) { return e.getMessage(); } catch (IOException e) { return e.getMessage(); } catch (BindingException e) { return e.getMessage(); } } private static String safeReadableString(ReadGraph g, Resource r) { try { return NameUtils.getSafeName(g, r); } catch(Throwable throwable) { ErrorLogger.defaultLogError(throwable); return ""+throwable.getClass().getName()+" "+throwable.getMessage()+""; } } // private static String safeReadableString(IEntity t) { // try { // return ResourceDebugUtils.getReadableNameForEntity(t); // } catch(Throwable throwable) { // throwable.printStackTrace(); // return ""+throwable.getClass().getName()+" "+throwable.getMessage()+""; // } // } private String getLinkString(Container t) { String link = links.getLeft(t.get()); if(link == null) { link = UUID.randomUUID().toString(); links.map(link, t.get()); } return link; } // private String getPropertyEditString(Container t) { // String link = links.getLeft(t.get()); // if(link == null) { // link = UUID.randomUUID().toString(); // links.map(link, t.get()); // } // return link; // } private String getStatementString(Resource _s, Resource _p, Resource _o) { String s = getLinkString(_s); String p = getLinkString(_p); String o = getLinkString(_o); return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o; } // private String getStatementString(Statement stm) { // String s = getLinkString(stm.getSubject()); // String p = getLinkString(stm.getPredicate()); // String o = getLinkString(stm.getObject()); // return s + STATEMENT_PART_SEPARATOR + p + STATEMENT_PART_SEPARATOR + o; // } // private String toOutgoingTableRow(IEntity subject, Statement stm) { // boolean isAcquired = !subject.equals(stm.getSubject()); // // String plainCell = "%s"; // String hrefCell = "%s"; // String formatTemplate = isAcquired // ? "\n{0}\n{1}\n{1}\n{1}\n" // : "\n{1}\n{1}\n{1}\n{1}\n"; // String format = NLS.bind(formatTemplate, plainCell, hrefCell); //// System.out.println("format: " + format); // // IEntity s = stm.getSubject(); // IEntity p = stm.getPredicate(); // IEntity o = stm.getObject(); // //// String timePart = "[" + timeToString(t.getBegin()) + ", " + timeToString(t.getEnd()) + "]"; // // try { // return !isAcquired // ? String.format(format, // "about:blank-remove" + getStatementString(stm), "Remove", // "about:blank-link" + getLinkString(s), safeReadableString(s), // "about:blank-link" + getLinkString(p), safeReadableString(p), // "about:blank-link" + getLinkString(o), safeReadableString(o)) // : String.format(format, // "Acquired", // "about:blank-link" + getLinkString(s), safeReadableString(s), // "about:blank-link" + getLinkString(p), safeReadableString(p), // "about:blank-link" + getLinkString(o), safeReadableString(o) // ); // } catch (Throwable throwable) { // return ""+throwable.getClass().getName()+" "+throwable.getMessage()+""; // } // } // private String intervalToString(Interval time) { // return timeToString(time.getBegin()) + ", " + timeToString(time.getEnd()); // } // // DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); // // private String timeToString(long time) { // if (time == Long.MIN_VALUE) // return "-∞"; // if (time == Long.MAX_VALUE) // return "+∞"; // //return String.valueOf(time); // Date d = new Date(time); // return dateTimeFormat.format(d); // } private String getHead() { String result = ""; if (cssPath != null) { result = ""; } return result; } }