/*******************************************************************************
* 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.swt.uiprocess;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.osgi.service.log.LogService;
import org.eclipse.core.runtime.Assert;
import org.eclipse.equinox.log.Logger;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.riena.core.Log4r;
import org.eclipse.riena.core.exception.IExceptionHandlerManager;
import org.eclipse.riena.core.service.Service;
import org.eclipse.riena.internal.ui.swt.utils.RcpUtilities;
import org.eclipse.riena.ui.core.uiprocess.IUISynchronizer;
/**
* Serializes a runnable to the SWT-Thread
*
*/
public class SwtUISynchronizer implements IUISynchronizer {
private static List<FutureSyncLatch> syncJobs = Collections.synchronizedList(new ArrayList<FutureSyncLatch>());
private static List<Runnable> asyncJobs = Collections.synchronizedList(new ArrayList<Runnable>());
private static Thread displayObserver;
private static AtomicBoolean workbenchShutdown = new AtomicBoolean(false);
private final Display display;
public SwtUISynchronizer() {
// we try to remember the display. This is a hack for Riena on RAP.
this.display = Display.getCurrent();
}
/**
* @see IUISynchronizer#syncExec(Runnable)
*/
public void syncExec(final Runnable runnable) {
execute(new SyncExecutor(), runnable);
}
/**
* @see IUISynchronizer#asyncExec(Runnable)
*/
public void asyncExec(final Runnable runnable) {
execute(new ASyncExecutor(), runnable);
}
/*
* Executes the given runnable using the executor. First checks if there is
* a display available.
*/
private void execute(final Executor executor, final Runnable runnable) {
if (isWorkbenchShutdown()) {
return;
}
if (!hasDisplay()) {
waitForDisplay(15000);
}
final Display currentDisplay = getDisplay();
if (executeOnDisplay(executor, runnable, currentDisplay)) {
return;
}
if (currentDisplay == null || getDisplay().isDisposed()) {
if (isSyncExecutor(executor)) {
waitForDisplayInitialisation(runnable);
} else {
queueRunnable(executor, runnable);
}
}
}
private void startObserver() {
synchronized (SwtUISynchronizer.class) {
if (displayObserver == null) {
displayObserver = new DisplayObserver();
displayObserver.start();
}
}
}
private void queueRunnable(final Executor executor, final Runnable runnable) {
synchronized (SwtUISynchronizer.class) {
asyncJobs.add(runnable);
}
startObserver();
}
private class DisplayObserver extends Thread {
@Override
public void run() {
// TODO [ev] I think there are two problems with this code:
// (a) getDisplay().isDisposed() will NPE when getDisplay() == null
// (b) if the display is disposed, we won't get another one. What happens with
// the things that need to run?
while (PlatformUI.isWorkbenchRunning() && getDisplay() == null
|| (getDisplay() != null && getDisplay().isDisposed())) {
try {
Thread.sleep(50);
} catch (final InterruptedException e) {
getLogger().log(LogService.LOG_ERROR, e.getMessage());
}
}
if (getDisplay() == null) {
return;
}
synchronized (SwtUISynchronizer.class) {
// notify job waiters (syncExec)
final Iterator<FutureSyncLatch> syncIter = syncJobs.iterator();
while (syncIter.hasNext()) {
final SwtUISynchronizer.FutureSyncLatch futureSyncLatch = syncIter.next();
futureSyncLatch.countDown();
syncIter.remove();
}
// execute jobs (asyncExec)
final Iterator<Runnable> asyncIter = asyncJobs.iterator();
while (asyncIter.hasNext()) {
final Runnable next = asyncIter.next();
if (!isWorkbenchShutdown()) {
new ASyncExecutor().execute(getDisplay(), next);
}
asyncIter.remove();
}
displayObserver = null;
}
}
}
private void waitForDisplayInitialisation(final Runnable job) {
final FutureSyncLatch latch = new FutureSyncLatch(1, job);
synchronized (SwtUISynchronizer.class) {
syncJobs.add(latch);
}
startObserver();
try {
latch.await();
} catch (final InterruptedException e) {
getLogger().log(LogService.LOG_ERROR, e.getMessage());
}
}
private boolean isSyncExecutor(final Executor executor) {
return executor instanceof SyncExecutor;
}
private class FutureSyncLatch extends CountDownLatch {
private final Runnable job;
public FutureSyncLatch(final int count, final Runnable job) {
super(count);
this.job = job;
}
@Override
public void await() throws InterruptedException {
super.await();
if (!isWorkbenchShutdown()) {
new SyncExecutor().execute(getDisplay(), job);
}
}
}
private boolean executeOnDisplay(final Executor executor, final Runnable runnable, final Display display) {
if (null != display && !display.isDisposed()) {
executor.execute(display, runnable);
return true;
}
return false;
}
public Display getDisplay() {
if (display != null) {
return display;
}
return RcpUtilities.getDisplay();
}
private Logger getLogger() {
return Log4r.getLogger(org.eclipse.riena.internal.ui.swt.Activator.getDefault(), SwtUISynchronizer.class);
}
protected boolean hasDisplay() {
return display != null || RcpUtilities.hasDisplay();
}
/**
* Wait for display in 500ms increments, up to timeoutMs
*
* @param timeoutMs
* time out in ms (positive)
*/
private void waitForDisplay(final int timeoutMs) {
Assert.isTrue(timeoutMs >= 0);
int time = 0;
do {
try {
Thread.sleep(500);
time += 500;
} catch (final InterruptedException e) {
return;
}
} while (time < timeoutMs && !hasDisplay());
}
private interface Executor {
void execute(Display display, Runnable runnable);
}
private static class SyncExecutor implements Executor {
public void execute(final Display display, final Runnable runnable) {
display.syncExec(runnable);
}
}
private static class ASyncExecutor implements Executor {
public void execute(final Display display, final Runnable runnable) {
display.asyncExec(runnable);
}
}
/**
* @return if true if the workbench has been shutdown
*/
public static boolean isWorkbenchShutdown() {
return workbenchShutdown.get();
}
/**
* @param workbenchShutdown
* the workbenchShutdown to set
*/
public static void setWorkbenchShutdown(final boolean workbenchShutdown) {
SwtUISynchronizer.workbenchShutdown.set(workbenchShutdown);
}
/**
* @since 3.0
*/
public void readAndDispatch(final Callable<Boolean> condition) {
Assert.isNotNull(condition);
final Display currentDisplay = Display.getCurrent();
// dispatch events
try {
while (!(condition.call() || isWorkbenchShutdown() || currentDisplay.isDisposed())) {
if (!currentDisplay.readAndDispatch()) {
currentDisplay.sleep();
}
}
} catch (final Exception e) {
Service.get(IExceptionHandlerManager.class).handleException(e);
}
}
}