1 /*******************************************************************************
2 * Copyright (c) 2007, 2013 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 * Semantum Oy - issue #4384
12 *******************************************************************************/
13 package org.simantics.ui.workbench;
15 import org.eclipse.core.runtime.IAdaptable;
16 import org.eclipse.ui.IEditorInput;
17 import org.eclipse.ui.IEditorPart;
18 import org.eclipse.ui.PartInitException;
19 import org.eclipse.ui.PlatformUI;
20 import org.simantics.Simantics;
21 import org.simantics.db.ReadGraph;
22 import org.simantics.db.Session;
23 import org.simantics.db.common.procedure.adapter.ListenerAdapter;
24 import org.simantics.db.common.request.ParametrizedRead;
25 import org.simantics.db.common.request.UniqueRead;
26 import org.simantics.db.event.ChangeEvent;
27 import org.simantics.db.event.ChangeListener;
28 import org.simantics.db.exception.DatabaseException;
29 import org.simantics.db.management.ISessionContext;
30 import org.simantics.db.management.ISessionContextProvider;
31 import org.simantics.db.service.GraphChangeListenerSupport;
32 import org.simantics.utils.datastructures.map.Tuple;
33 import org.simantics.utils.ui.ExceptionUtils;
34 import org.simantics.utils.ui.SWTUtils;
35 import org.simantics.utils.ui.workbench.WorkbenchUtils;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * A helper class for easing the attachment of a Simantics database session to
43 * It handles the life-cycle of {@link IResourceEditorInput} inputs. It will
44 * listen to graph database changes through {@link ChangeListener} and calls
45 * {@link IResourceEditorInput#update(org.simantics.db.ReadGraph)}.
47 * Works with any IEditorPart but is only really useful with ones that have
50 * @author Tuukka Lehtonen
52 public class ResourceEditorSupport implements IAdaptable, ChangeListener {
54 private static final Logger LOGGER = LoggerFactory.getLogger(ResourceEditorSupport.class);
55 private static final boolean DEBUG = false;
57 private IEditorPart editorPart;
59 private ChangeListener editorPartChangeListener;
61 private ISessionContext sessionContext;
63 // Just a cache to make sure that getSession doesn't NPE.
64 private Session session;
66 ParametrizedRead<IResourceEditorInput, Boolean> inputValidator;
68 private InputListener inputListener;
70 public ResourceEditorSupport(IEditorPart editorPart) throws PartInitException {
71 this(editorPart, null);
74 public ResourceEditorSupport(IEditorPart editorPart, ParametrizedRead<IResourceEditorInput, Boolean> inputValidator) throws PartInitException {
75 this.editorPart = editorPart;
76 this.editorPartChangeListener = getChangeListener(editorPart);
77 this.inputValidator = inputValidator;
81 IResourceEditorInput input = getResourceInput(editorPart);
83 throw new PartInitException("Editor input must be an IResourceEditorInput, got " + editorPart.getEditorInput());
87 } catch (DatabaseException e) {
88 throw new PartInitException("Failed to initialize " + input, e);
92 private ISessionContext initSession() throws PartInitException {
93 if (sessionContext == null) {
94 ISessionContextProvider provider = Simantics.getSessionContextProvider();
95 ISessionContext sc = provider.getSessionContext();
97 throw new PartInitException("active database session context is null");
100 session = sc.getSession();
102 if (editorPartChangeListener != null) {
103 GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);
104 support.addListener(this);
107 return sessionContext;
110 private ChangeListener getChangeListener(IEditorPart part) {
111 if (part instanceof ChangeListener)
112 return (ChangeListener) part;
113 ChangeListener cl = (ChangeListener) part.getAdapter(ChangeListener.class);
117 private static IResourceEditorInput getResourceInput(IEditorPart part) {
118 IEditorInput input = part.getEditorInput();
119 if (input instanceof IResourceEditorInput)
120 return (IResourceEditorInput) input;
124 public void dispose() {
125 deactivateValidation();
127 // This is special for IResourceInput, has to be done in order not to
128 // leak random access id's for resources.
129 if (!PlatformUI.getWorkbench().isClosing()) {
130 IResourceEditorInput input = getResourceInput(editorPart);
136 sessionContext = null;
137 if (session != null) {
138 if (editorPartChangeListener != null) {
139 GraphChangeListenerSupport support = session.getService(GraphChangeListenerSupport.class);
140 support.removeListener(this);
145 editorPartChangeListener = null;
148 protected boolean isDisposed() {
149 return editorPart == null;
152 public synchronized void activateValidation() {
154 throw new IllegalStateException(this + " is disposed");
155 if (inputListener != null)
158 inputListener = new InputListener();
159 getSession().asyncRequest(new ValidationRequest(), inputListener);
162 public synchronized void deactivateValidation() {
164 throw new IllegalStateException(this + " is disposed");
165 if (inputListener == null)
167 inputListener.dispose();
168 inputListener = null;
171 public ISessionContext getSessionContext() {
172 if (sessionContext == null)
173 throw new IllegalStateException("ResourceEditorSupport is disposed");
174 return sessionContext;
177 public Session getSession() {
179 throw new IllegalStateException("ResourceEditorSupport is disposed");
183 @SuppressWarnings("unchecked")
185 public <T> T getAdapter(Class<T> adapter) {
186 if (adapter == ISessionContext.class)
187 return (T) getSessionContext();
188 if (adapter == Session.class)
189 return (T) getSession();
194 public void graphChanged(ChangeEvent e) throws DatabaseException {
195 // Only forward the update to the editor if the input is still valid and
196 // the editor implements ChangeListener
197 if (editorPart instanceof ChangeListener)
198 ((ChangeListener) editorPart).graphChanged(e);
201 static enum InputState {
206 public static InputState parse(boolean exists, boolean valid) {
209 return valid ? VALID : INVALID;
213 static class Evaluation extends Tuple {
214 public Evaluation(IEditorPart editorPart, IEditorInput input, InputState state, String name, String tooltip) {
215 super(editorPart, input, state, name, tooltip);
218 public IEditorPart getEditorPart() {
219 return (IEditorPart) getField(0);
222 public IEditorInput getEditorInput() {
223 return (IEditorInput) getField(1);
226 public InputState getInputState() {
227 return (InputState) getField(2);
232 * A read request that returns an {@link Evaluation} of the current state of
233 * <code>editorPart</code>.
236 * This request class is not static but has no parameters that could get
237 * stuck in the database client caches. UniqueRead does not need arguments
238 * and without custom hashCode/equals implementations, each instance of this
239 * request is a different one. This is exactly the behaviour we want in this
242 private class ValidationRequest extends UniqueRead<Evaluation> {
244 public Evaluation perform(ReadGraph graph) throws DatabaseException {
245 IEditorPart part = editorPart;
247 return new Evaluation(null, null, InputState.INVALID, "", "");
249 IEditorInput input = part.getEditorInput();
250 IResourceEditorInput resourceInput = getResourceInput(part);
253 LOGGER.trace("ValidationRequest: checking input " + input);
255 boolean exists = true;
256 boolean valid = true;
257 if (resourceInput != null) {
258 exists = resourceInput.exists(graph);
259 if (exists && inputValidator != null) {
260 valid = graph.syncRequest(inputValidator.get(resourceInput));
263 exists = input.exists();
266 InputState state = InputState.parse(exists, valid);
267 if (state == InputState.VALID) {
268 // Make sure any cached data in the editor input is up-to-date.
269 if (resourceInput != null)
270 resourceInput.update(graph);
273 Evaluation eval = new Evaluation(part, input, state, input.getName(), input.getToolTipText());
275 LOGGER.trace("ValidationRequest: evaluation result: " + eval);
280 private static class InputListener extends ListenerAdapter<Evaluation> {
282 private boolean disposed = false;
284 public void dispose() {
289 public void execute(Evaluation evaluation) {
291 LOGGER.trace("InputListener: " + evaluation);
292 switch (evaluation.getInputState()) {
297 scheduleEditorClose(evaluation.getEditorPart());
303 public void exception(Throwable t) {
304 ExceptionUtils.logError("ResourceEditorSupport.InputListener received an unexpected exception.", t);
308 public boolean isDisposed() {
313 private static void scheduleEditorClose(IEditorPart editorPart) {
314 if (editorPart == null)
316 SWTUtils.asyncExec(editorPart.getSite().getWorkbenchWindow().getShell(), () -> {
317 // Don't have to check isDisposed since closeEditor
318 // will ignore already closed editor parts.
319 WorkbenchUtils.closeEditor(editorPart, false);