/******************************************************************************* * Copyright (c) 2007, 2013 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 * Semantum Oy - issue #4384 *******************************************************************************/ package org.simantics.ui.workbench; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Session; import org.simantics.db.common.procedure.adapter.ListenerAdapter; import org.simantics.db.common.request.ParametrizedRead; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.event.ChangeEvent; import org.simantics.db.event.ChangeListener; import org.simantics.db.exception.DatabaseException; import org.simantics.db.management.ISessionContext; import org.simantics.db.management.ISessionContextProvider; import org.simantics.db.service.GraphChangeListenerSupport; import org.simantics.utils.datastructures.map.Tuple; import org.simantics.utils.ui.ExceptionUtils; import org.simantics.utils.ui.SWTUtils; import org.simantics.utils.ui.workbench.WorkbenchUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A helper class for easing the attachment of a Simantics database session to * an editor part. * * It handles the life-cycle of {@link IResourceEditorInput} inputs. It will * listen to graph database changes through {@link ChangeListener} and calls * {@link IResourceEditorInput#update(org.simantics.db.ReadGraph)}. * * Works with any IEditorPart but is only really useful with ones that have * resource inputs. * * @author Tuukka Lehtonen */ public class ResourceEditorSupport implements IAdaptable, ChangeListener { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceEditorSupport.class); private static final boolean DEBUG = false; private IEditorPart editorPart; private ChangeListener editorPartChangeListener; private ISessionContext sessionContext; // Just a cache to make sure that getSession doesn't NPE. private Session session; ParametrizedRead inputValidator; private InputListener inputListener; public ResourceEditorSupport(IEditorPart editorPart) throws PartInitException { this(editorPart, null); } public ResourceEditorSupport(IEditorPart editorPart, ParametrizedRead inputValidator) throws PartInitException { this.editorPart = editorPart; this.editorPartChangeListener = getChangeListener(editorPart); this.inputValidator = inputValidator; initSession(); IResourceEditorInput input = getResourceInput(editorPart); if (input == null) throw new PartInitException("Editor input must be an IResourceEditorInput, got " + editorPart.getEditorInput()); try { input.init(this); } catch (DatabaseException e) { throw new PartInitException("Failed to initialize " + input, e); } } private ISessionContext initSession() throws PartInitException { if (sessionContext == null) { ISessionContextProvider provider = Simantics.getSessionContextProvider(); ISessionContext sc = provider.getSessionContext(); if (sc == null) throw new PartInitException("active database session context is null"); sessionContext = sc; session = sc.getSession(); if (editorPartChangeListener != null) { GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class); support.addListener(this); } } return sessionContext; } private ChangeListener getChangeListener(IEditorPart part) { if (part instanceof ChangeListener) return (ChangeListener) part; ChangeListener cl = (ChangeListener) part.getAdapter(ChangeListener.class); return cl; } private static IResourceEditorInput getResourceInput(IEditorPart part) { IEditorInput input = part.getEditorInput(); if (input instanceof IResourceEditorInput) return (IResourceEditorInput) input; return null; } public void dispose() { deactivateValidation(); // This is special for IResourceInput, has to be done in order not to // leak random access id's for resources. if (!PlatformUI.getWorkbench().isClosing()) { IResourceEditorInput input = getResourceInput(editorPart); if (input != null) input.dispose(); } editorPart = null; sessionContext = null; if (session != null) { if (editorPartChangeListener != null) { GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class); support.removeListener(this); } session = null; } editorPartChangeListener = null; } protected boolean isDisposed() { return editorPart == null; } public synchronized void activateValidation() { if (isDisposed()) throw new IllegalStateException(this + " is disposed"); if (inputListener != null) return; inputListener = new InputListener(); getSession().asyncRequest(new ValidationRequest(), inputListener); } public synchronized void deactivateValidation() { if (isDisposed()) throw new IllegalStateException(this + " is disposed"); if (inputListener == null) return; inputListener.dispose(); inputListener = null; } public ISessionContext getSessionContext() { if (sessionContext == null) throw new IllegalStateException("ResourceEditorSupport is disposed"); return sessionContext; } public Session getSession() { if (session == null) throw new IllegalStateException("ResourceEditorSupport is disposed"); return session; } @SuppressWarnings("unchecked") @Override public T getAdapter(Class adapter) { if (adapter == ISessionContext.class) return (T) getSessionContext(); if (adapter == Session.class) return (T) getSession(); return null; } @Override public void graphChanged(ChangeEvent e) throws DatabaseException { // Only forward the update to the editor if the input is still valid and // the editor implements ChangeListener if (editorPart instanceof ChangeListener) ((ChangeListener) editorPart).graphChanged(e); } static enum InputState { VALID, INVALID, NON_EXISTENT; public static InputState parse(boolean exists, boolean valid) { if (!exists) return NON_EXISTENT; return valid ? VALID : INVALID; } } static class Evaluation extends Tuple { public Evaluation(IEditorPart editorPart, IEditorInput input, InputState state, String name, String tooltip) { super(editorPart, input, state, name, tooltip); } public IEditorPart getEditorPart() { return (IEditorPart) getField(0); } public IEditorInput getEditorInput() { return (IEditorInput) getField(1); } public InputState getInputState() { return (InputState) getField(2); } } /** * A read request that returns an {@link Evaluation} of the current state of * editorPart. * *

* This request class is not static but has no parameters that could get * stuck in the database client caches. UniqueRead does not need arguments * and without custom hashCode/equals implementations, each instance of this * request is a different one. This is exactly the behaviour we want in this * case. */ private class ValidationRequest extends UniqueRead { @Override public Evaluation perform(ReadGraph graph) throws DatabaseException { IEditorPart part = editorPart; if (part == null) return new Evaluation(null, null, InputState.INVALID, "", ""); IEditorInput input = part.getEditorInput(); IResourceEditorInput resourceInput = getResourceInput(part); if (DEBUG) LOGGER.trace("ValidationRequest: checking input " + input); boolean exists = true; boolean valid = true; if (resourceInput != null) { exists = resourceInput.exists(graph); if (exists && inputValidator != null) { valid = graph.syncRequest(inputValidator.get(resourceInput)); } } else { exists = input.exists(); } InputState state = InputState.parse(exists, valid); if (state == InputState.VALID) { // Make sure any cached data in the editor input is up-to-date. if (resourceInput != null) resourceInput.update(graph); } Evaluation eval = new Evaluation(part, input, state, input.getName(), input.getToolTipText()); if (DEBUG) LOGGER.trace("ValidationRequest: evaluation result: " + eval); return eval; } } private static class InputListener extends ListenerAdapter { private boolean disposed = false; public void dispose() { disposed = true; } @Override public void execute(Evaluation evaluation) { if (DEBUG) LOGGER.trace("InputListener: " + evaluation); switch (evaluation.getInputState()) { case VALID: break; case INVALID: case NON_EXISTENT: scheduleEditorClose(evaluation.getEditorPart()); break; } } @Override public void exception(Throwable t) { ExceptionUtils.logError("ResourceEditorSupport.InputListener received an unexpected exception.", t); } @Override public boolean isDisposed() { return disposed; } } private static void scheduleEditorClose(IEditorPart editorPart) { if (editorPart == null) return; SWTUtils.asyncExec(editorPart.getSite().getWorkbenchWindow().getShell(), () -> { // Don't have to check isDisposed since closeEditor // will ignore already closed editor parts. WorkbenchUtils.closeEditor(editorPart, false); }); } }