--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2013 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ * Semantum Oy - issue #4384\r
+ *******************************************************************************/\r
+package org.simantics.ui.workbench;\r
+\r
+import org.eclipse.core.runtime.IAdaptable;\r
+import org.eclipse.ui.IEditorInput;\r
+import org.eclipse.ui.IEditorPart;\r
+import org.eclipse.ui.PartInitException;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.common.procedure.adapter.ListenerAdapter;\r
+import org.simantics.db.common.request.ParametrizedRead;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.event.ChangeEvent;\r
+import org.simantics.db.event.ChangeListener;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.management.ISessionContext;\r
+import org.simantics.db.management.ISessionContextProvider;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.db.service.GraphChangeListenerSupport;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.utils.datastructures.map.Tuple;\r
+import org.simantics.utils.ui.ExceptionUtils;\r
+import org.simantics.utils.ui.SWTUtils;\r
+import org.simantics.utils.ui.workbench.WorkbenchUtils;\r
+\r
+/**\r
+ * A helper class for easing the attachment of a Simantics database session to\r
+ * an editor part.\r
+ * \r
+ * It handles the life-cycle of {@link IResourceEditorInput} inputs. It will\r
+ * listen to graph database changes through {@link ChangeListener} and calls\r
+ * {@link IResourceEditorInput#update(org.simantics.db.ReadGraph)}.\r
+ * \r
+ * Works with any IEditorPart but is only really useful with ones that have\r
+ * resource inputs.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class ResourceEditorSupport implements IAdaptable, ChangeListener {\r
+\r
+ private IEditorPart editorPart;\r
+\r
+ private ChangeListener editorPartChangeListener;\r
+\r
+ private ISessionContext sessionContext;\r
+\r
+ // Just a cache to make sure that getSession doesn't NPE.\r
+ private Session session;\r
+\r
+ ParametrizedRead<IResourceEditorInput, Boolean> inputValidator;\r
+\r
+ private InputListener inputListener;\r
+\r
+ public ResourceEditorSupport(IEditorPart editorPart) throws PartInitException {\r
+ this(editorPart, null);\r
+ }\r
+\r
+ public ResourceEditorSupport(IEditorPart editorPart, ParametrizedRead<IResourceEditorInput, Boolean> inputValidator) throws PartInitException {\r
+ this.editorPart = editorPart;\r
+ this.editorPartChangeListener = getChangeListener(editorPart);\r
+ this.inputValidator = inputValidator;\r
+\r
+ initSession();\r
+\r
+ IResourceEditorInput input = getResourceInput(editorPart);\r
+ if (input == null)\r
+ throw new PartInitException("Editor input must be an IResourceEditorInput, got " + editorPart.getEditorInput());\r
+\r
+ try {\r
+ input.init(this);\r
+ } catch (DatabaseException e) {\r
+ throw new PartInitException("Failed to initialize " + input, e);\r
+ }\r
+ }\r
+\r
+ private ISessionContext initSession() throws PartInitException {\r
+ if (sessionContext == null) {\r
+ ISessionContextProvider provider = SimanticsUI.getSessionContextProvider();\r
+ ISessionContext sc = provider.getSessionContext();\r
+ if (sc == null)\r
+ throw new PartInitException("active database session context is null");\r
+\r
+ sessionContext = sc;\r
+ session = sc.getSession();\r
+\r
+ if (editorPartChangeListener != null) {\r
+ GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
+ support.addListener(this);\r
+ }\r
+ }\r
+ return sessionContext;\r
+ }\r
+\r
+ private ChangeListener getChangeListener(IEditorPart part) {\r
+ if (part instanceof ChangeListener)\r
+ return (ChangeListener) part;\r
+ ChangeListener cl = (ChangeListener) part.getAdapter(ChangeListener.class);\r
+ return cl;\r
+ }\r
+\r
+ private static IResourceEditorInput getResourceInput(IEditorPart part) {\r
+ IEditorInput input = part.getEditorInput();\r
+ if (input instanceof IResourceEditorInput)\r
+ return (IResourceEditorInput) input;\r
+ return null;\r
+ }\r
+\r
+ public void dispose() {\r
+ deactivateValidation();\r
+\r
+ // This is special for IResourceInput, has to be done in order not to\r
+ // leak random access id's for resources.\r
+ if (!PlatformUI.getWorkbench().isClosing()) {\r
+ IResourceEditorInput input = getResourceInput(editorPart);\r
+ if (input != null)\r
+ input.dispose();\r
+ }\r
+ editorPart = null;\r
+\r
+ sessionContext = null;\r
+ if (session != null) {\r
+ if (editorPartChangeListener != null) {\r
+ GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);\r
+ support.removeListener(this);\r
+ }\r
+ session = null;\r
+ }\r
+\r
+ editorPartChangeListener = null;\r
+ }\r
+\r
+ protected boolean isDisposed() {\r
+ return editorPart == null;\r
+ }\r
+\r
+ public synchronized void activateValidation() {\r
+ if (isDisposed())\r
+ throw new IllegalStateException(this + " is disposed");\r
+ if (inputListener != null)\r
+ return;\r
+\r
+ inputListener = new InputListener();\r
+ getSession().asyncRequest(validationRequest(editorPart), inputListener);\r
+ }\r
+\r
+ public synchronized void deactivateValidation() {\r
+ if (isDisposed())\r
+ throw new IllegalStateException(this + " is disposed");\r
+ if (inputListener == null)\r
+ return;\r
+ inputListener.dispose();\r
+ }\r
+\r
+ public ISessionContext getSessionContext() {\r
+ if (sessionContext == null)\r
+ throw new IllegalStateException("ResourceEditorSupport is disposed");\r
+ return sessionContext;\r
+ }\r
+\r
+ public Session getSession() {\r
+ if (session == null)\r
+ throw new IllegalStateException("ResourceEditorSupport is disposed");\r
+ return session;\r
+ }\r
+\r
+ @SuppressWarnings("rawtypes")\r
+ @Override\r
+ public Object getAdapter(Class adapter) {\r
+ if (adapter == ISessionContext.class)\r
+ return getSessionContext();\r
+ if (adapter == Session.class)\r
+ return getSession();\r
+ return null;\r
+ }\r
+\r
+ @Override\r
+ public void graphChanged(ChangeEvent e) throws DatabaseException {\r
+ //System.out.println(this + ": graph change: " + e);\r
+ // Only forward the update to the editor if the input is still valid and\r
+ // the editor implements ChangeListener\r
+ if (editorPart instanceof ChangeListener)\r
+ ((ChangeListener) editorPart).graphChanged(e);\r
+ }\r
+\r
+ static enum InputState {\r
+ VALID,\r
+ INVALID,\r
+ NON_EXISTENT;\r
+\r
+ public static InputState parse(boolean exists, boolean valid) {\r
+ if (!exists)\r
+ return NON_EXISTENT;\r
+ return valid ? VALID : INVALID;\r
+ }\r
+ }\r
+\r
+ static class Evaluation extends Tuple {\r
+ public Evaluation(IEditorPart editorPart, IEditorInput input, InputState state, String name, String tooltip) {\r
+ super(editorPart, input, state, name, tooltip);\r
+ }\r
+\r
+ public IEditorPart getEditorPart() {\r
+ return (IEditorPart) getField(0);\r
+ }\r
+\r
+ public IEditorInput getEditorInput() {\r
+ return (IEditorInput) getField(1);\r
+ }\r
+\r
+ public InputState getInputState() {\r
+ return (InputState) getField(2);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * @param input\r
+ * @return a read request that returns <code>true</code> for valid inputs\r
+ * and <code>false</code> for non-existent or invalid inputs.\r
+ */\r
+ private Read<Evaluation> validationRequest(IEditorPart editorPart) {\r
+ return new UnaryRead<IEditorPart, Evaluation>(editorPart) {\r
+ @Override\r
+ public Evaluation perform(ReadGraph graph) throws DatabaseException {\r
+ IEditorInput input = parameter.getEditorInput();\r
+ IResourceEditorInput resourceInput = getResourceInput(parameter);\r
+\r
+ //System.out.println(ResourceEditorSupport.this + ": checking input " + input);\r
+\r
+ boolean exists = true;\r
+ boolean valid = true;\r
+ if (resourceInput != null) {\r
+ exists = resourceInput.exists(graph);\r
+ if (exists && inputValidator != null) {\r
+ valid = graph.syncRequest(inputValidator.get(resourceInput));\r
+ }\r
+ } else {\r
+ exists = input.exists();\r
+ }\r
+\r
+ InputState state = InputState.parse(exists, valid);\r
+ if (state == InputState.VALID) {\r
+ // Make sure any cached data in the editor input is up-to-date.\r
+ resourceInput.update(graph);\r
+ }\r
+\r
+ Evaluation eval = new Evaluation(parameter, input, state, input.getName(), input.getToolTipText());\r
+ //System.out.println(ResourceEditorSupport.this + ": validation evaluation: " + eval);\r
+ return eval;\r
+ }\r
+ };\r
+ }\r
+\r
+ private class InputListener extends ListenerAdapter<Evaluation> {\r
+\r
+ private boolean disposed = false;\r
+\r
+ public void dispose() {\r
+ disposed = true;\r
+ }\r
+\r
+ @Override\r
+ public void execute(Evaluation evaluation) {\r
+ //System.out.println("InputListener: " + evaluation);\r
+ switch (evaluation.getInputState()) {\r
+ case VALID:\r
+ break;\r
+\r
+ case INVALID:\r
+ case NON_EXISTENT:\r
+ scheduleEditorClose(evaluation.getEditorPart());\r
+ break;\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ ExceptionUtils.logError("ResourceEditorSupport.InputListener received an unexpected exception.", t);\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ return disposed || ResourceEditorSupport.this.isDisposed();\r
+ }\r
+ }\r
+\r
+ private void scheduleEditorClose(final IEditorPart editorPart) {\r
+ SWTUtils.asyncExec(editorPart.getSite().getShell(), new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ // Don't have to check isDisposed since closeEditor\r
+ // will ignore already closed editor parts.\r
+ WorkbenchUtils.closeEditor(editorPart, false);\r
+ }\r
+ });\r
+ }\r
+\r
+}\r