/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.ui.core.uiprocess; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobManager; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.riena.core.exception.IExceptionHandlerManager; import org.eclipse.riena.core.service.Service; import org.eclipse.riena.internal.ui.core.Activator; public class UIProcess extends PlatformObject implements IUIMonitor { public final static QualifiedName PROPERTY_CONTEXT = new QualifiedName("uiProcess", "context"); //$NON-NLS-1$//$NON-NLS-2$ private final UICallbackDispatcher callbackDispatcher; private final Job job; private final ListenerWrapper listenerWrapper = new ListenerWrapper(); /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param name * the name of the job * * @throws some_kind_of_runtime_exception * if no configuration point is found or if an object of the * class given in the extension point can not be instantiated. * * @see UISynchronizer */ public UIProcess(final String name) { this(name, false); } /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param name * the name of the job * * @param user * sets whether or not the process should be displayed as a * window * * @throws some_kind_of_runtime_exception * if no configuration point is found or if an object of the * class given in the extension point can not be instantiated. * * @see UISynchronizer */ public UIProcess(final String name, final boolean user) { this(name, user, new Object()); } /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param name * the name of the job * * @param user * sets whether or not the process should be displayed as a * window * * @param context * an object representing the context of this {@link UIProcess} * * @throws some_kind_of_runtime_exception * if no configuration point is found or if an object of the * class given in the extension point can not be instantiated. * */ public UIProcess(final String name, final boolean user, final Object context) { this(name, UISynchronizer.createSynchronizer(), user, context); } /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param name * the name of the job * * @param syncher * the synchronizer which serializes callbacks to the UI- * {@link Thread} of the Widget Toolkit * * @param user * sets whether or not the process should be displayed as a * window * * @param context * an object representing the context of this {@link UIProcess} * * @throws some_kind_of_runtime_exception * if no configuration point is found or if an object of the * class given in the extension point can not be instantiated. * */ public UIProcess(final String name, final IUISynchronizer syncher, final boolean user, final Object context) { this(name, new UICallbackDispatcher(syncher), user, context); } /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param name * the name of the job * * @param dispatcher * the {@link UICallbackDispatcher} which is responsible for * Thread-switches and {@link IUIMonitor}-delegation (Note: this * is very low level) * * @param user * sets whether or not the process should be displayed as a * window * * @param context * an object representing the context of this {@link UIProcess} * * @throws some_kind_of_runtime_exception * if no configuration point is found or if an object of the * class given in the extension point can not be instantiated. * */ private UIProcess(final String name, final UICallbackDispatcher dispatcher, final boolean user, final Object context) { this.callbackDispatcher = dispatcher; this.job = createJob(name, user, context); updateProcessConfiguration(); } /** * Creates a new UIProcess. * <p> * For executing processes the {@code UIProcess) uses the * * @code UISynchronizer} to create {@code IUISynchronizer} instances which * perform the actual execution of the process. * * @param job * the job which is wrapped by the {@link UIProcess} */ public UIProcess(final Job job) { this.callbackDispatcher = new UICallbackDispatcher(UISynchronizer.createSynchronizer()); this.job = job; updateProcessConfiguration(); } private void updateProcessConfiguration() { configureProcessInfo(); } private void configureProcessInfo() { if (callbackDispatcher != null) { final ProcessInfo processInfo = callbackDispatcher.getProcessInfo(); processInfo.setContext(job.getProperty(PROPERTY_CONTEXT)); processInfo.addPropertyChangeListener(new CancelListener()); processInfo.setDialogVisible(job.isUser()); processInfo.setNote(job.getName()); processInfo.setTitle(job.getName()); } } private Job createJob(final String name, final boolean user, final Object context) { final Job newJob = new InternalJob(name); newJob.setUser(user); newJob.setProperty(PROPERTY_CONTEXT, context); return newJob; } private final class CancelListener implements PropertyChangeListener { public void propertyChange(final PropertyChangeEvent event) { if (ProcessInfo.PROPERTY_CANCELED.equals(event.getPropertyName())) { job.cancel(); } } } protected boolean forceMonitorBegin() { return true; } private final class InternalJob extends Job { public InternalJob(final String name) { super(name); } @Override protected IStatus run(final IProgressMonitor monitor) { beforeRun(monitor); if (forceMonitorBegin()) { monitor.beginTask(getName(), getTotalWork()); } boolean state = false; try { state = runJob(monitor); } catch (final OperationCanceledException e) { if (!monitor.isCanceled()) { monitor.setCanceled(true); } } catch (final Throwable t) { // Forward to exception handler Service.get(Activator.getDefault().getContext(), IExceptionHandlerManager.class).handleException(t); } monitor.done(); afterRun(monitor); ProgressProviderBridge.instance().unregisterMapping(this); return state ? Status.OK_STATUS : Status.CANCEL_STATUS; } } /** * * @return the job wrapped by the {@link UIProcess}. This job is run by the {@link IJobManager} on a worker {@link Thread} * @since 3.0 */ public Job getJob() { return job; } /** * called before {@link #runJob(IProgressMonitor)} is invoked (async) */ protected void beforeRun(final IProgressMonitor monitor) { } /** * called after {@link #runJob(IProgressMonitor)} is invoked (async) */ protected void afterRun(final IProgressMonitor monitor) { } protected int getTotalWork() { return IProgressMonitor.UNKNOWN; } /** * registers the job at the {@link ProgressProviderBridge} */ private void register() { // registers itself as a monitor callbackDispatcher.addUIMonitor(this); callbackDispatcher.addUIMonitor(listenerWrapper); ProgressProviderBridge.instance().registerMapping(job, this); } public UICallbackDispatcher getCallbackDispatcher() { return callbackDispatcher; } /** * called whenever a unit of work is completed */ public void updateProgress(final int progress) { } /** * called on the user interface thread before aynch work is done */ public void initialUpdateUI(final int totalWork) { } /** * called on the user interface thread after aynch work is done */ public void finalUpdateUI() { } /** * override this method for implementation of logic on a worker thread * * @param monitor * the jobs API monitor used to control the {@link UIProcess} * @return true if the method has been run without errors */ public boolean runJob(final IProgressMonitor monitor) { return true; } /** * starts the {@link UIProcess} using jobs API * * @return true if the UIProces could be scheduled false if the UIProcess is already scheduled. * @since 3.0 */ public boolean start() { final int state = job.getState(); if (state == Job.RUNNING || state == Job.WAITING || state == Job.SLEEPING) { return false; } register(); job.schedule(); return true; } @Override public Object getAdapter(final Class adapter) { Object adapted = super.getAdapter(adapter); if (adapted == null) { if (adapter.isInstance(this)) { adapted = this; } if (adapter.equals(UICallbackDispatcher.class)) { adapted = getCallbackDispatcher(); } } return adapted; } /** * @param note * the note to set */ public void setNote(final String note) { getProcessInfo().setNote(note); } /** * @param title * the title to set */ public void setTitle(final String title) { getProcessInfo().setTitle(title); } /** * Sets the enabled state of the cancel button of the uiprocess window * * @param enabled * @since 3.0 */ public void setCancelEnabled(final boolean enabled) { getProcessInfo().setCancelEnabled(enabled); } /** * Sets the visible state of the cancel button of the uiprocess window * * @param visible * @since 3.0 */ public void setCancelVisible(final boolean visible) { getProcessInfo().setCancelVisible(visible); } /** * @param icon * the icon to set */ public void setIcon(final String icon) { getProcessInfo().setIcon(icon); } /** * Sets the strategy of how progress is interpreted * * @param strategy * - the progress strategy */ public void setProgresStrategy(final ProcessInfo.ProgresStrategy strategy) { getProcessInfo().setProgresStartegy(strategy); } /** * @return the {@link ProcessInfo} object holding meta information of this {@link UIProcess} */ private ProcessInfo getProcessInfo() { return getCallbackDispatcher().getProcessInfo(); } /** * call this method to get a "ui thread serialized run" of {@link #updateUi()} */ protected void notifyUpdateUI() { // serialize on ui thread getCallbackDispatcher().getSyncher().syncExec(new Runnable() { public void run() { try { updateUI(); } catch (final Exception e) { Service.get(Activator.getDefault().getContext(), IExceptionHandlerManager.class).handleException(e); } } }); } /** * called on the user interface thread as the result of a call to {@link #notifyUpdateUI()} * * @deprecated use {@link updateUI} instead. */ @Deprecated public void updateUi() { } /** * called on the user interface thread as the result of a call to {@link #notifyUpdateUI()} * * @since 4.0 */ protected void updateUI() { updateUi(); } /** * Registers an execution listener to this UI process. If the given listener is already added, the call has no effect. * * @param listener * the listener to add, must be not <code>null</code> * @since 4.0 */ public void addUIProcessChangedListener(final IUIProcessChangeListener listener) { Assert.isNotNull(listener); listenerWrapper.add(listener); } /** * Removes the given listener from the registered listeners list. The call has no effect if the given listener is not registered. * * @since 4.0 */ public void removeUIProcessChangedListener(final IUIProcessChangeListener listener) { listenerWrapper.remove(listener); } }