blob: fa8e18d05407410fb0e2eab25bf1d808c2287b00 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1997, 2018 by ProSyst Software GmbH
* http://www.prosyst.com
* 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:
* ProSyst Software GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.util.event;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.eclipse.equinox.internal.util.UtilActivator;
/**
* Abstract class for asynchronous event dispatching
*
* @author Pavlin Dobrev
* @version 1.0
*/
public abstract class EventThread implements Runnable {
/**
* The last callbacked listener. If the events thread is not responding,
* subclasses can take the appropriate actions - remove the listener for
* example
*/
public Object bad;
/**
* The event queue. This object must be used for synchronization with the
* events thread's state
*/
protected Queue queue;
/**
* The state of the thread.
* <li> bit 0: 0 = started / 1 = stopped;
* <li> bit 1: 0 not waiting / 1 = waiting
*/
protected int state;
/**
* The time spent in the current callback, or 0 if the thread is not in a
* callback
*/
protected long time = 0;
/**
* Instancies counter. Subclasses must not modify it.
*/
protected int counter = 1;
/**
* The event to be dispatched
*/
protected Object element;
protected String baseName;
protected String name;
protected Thread thread;
protected ThreadGroup group;
private static PrivilegedActionImpl privilegedAction = null;
/**
* Constructs the first instance of the EventThread
*
* @param group
* The ThreadGroup of the thread, or null for the current thread
* group
* @param name
* The base name of the thread. The <code> counter </code> value
* will be added at the end of the string to construct the full
* name.
* @param size
* The initial number of elements of the events queue
*/
public EventThread(ThreadGroup group, String name, int size) {
makeThread(this.group = group, this.name = name + '0');
baseName = name;
queue = new Queue(size);
int priority = getThreadPriority();
if (priority != Thread.NORM_PRIORITY)
thread.setPriority(priority);
}
/**
* Constructs the first instance of the EventThread
*
* @param group
* The ThreadGroup of the thread, or null for the current thread
* group
* @param name
* The base name of the thread. The <code> counter </code> value
* will be added at the end of the string to construct the full
* name.
* @param queue
* The events queue
*/
public EventThread(ThreadGroup group, String name, Queue queue) {
makeThread(this.group = group, this.name = name + '0');
baseName = name;
this.queue = queue;
int priority = getThreadPriority();
if (priority != Thread.NORM_PRIORITY)
thread.setPriority(priority);
}
/**
* Constructs a new EventThread, after the <code> old </code> event thread
* has stopped responding
*
* @param old
* The previous instance
*/
protected EventThread(EventThread old) {
makeThread(group = old.thread.getThreadGroup(), name = old.baseName + old.counter++);
baseName = old.baseName;
counter = old.counter;
queue = old.queue;
int priority = getThreadPriority();
if (priority != Thread.NORM_PRIORITY)
thread.setPriority(priority);
}
public void start() {
thread.start();
}
/**
* Adds an event in the event queue. The method must be synchronized
* outside, on the <code> queue </code> field.
*
* @param event
* The event to add
* @param check
* If true, the method will check if the EventThread is still
* responding
*/
public void addEvent(Object event, boolean check) {
try {
queue.put(event);
} catch (Throwable t) {
print(t);
return;
}
if ((state & 2) != 0)
queue.notify();
else if (check && checkTime())
try {
state |= 1;
newEventDispatcher(); // must call start
} catch (Throwable t) {
print(t);
state &= 254;
}
}
/**
* Processes the event queue. Sets the event to be dispathed in the
* <code> element </code> field and calls <cope> processEvent </code>
*/
@Override
public void run() {
synchronized (queue) {
queue.notifyAll();
}
while (true) {
try {
synchronized (queue) {
if ((state & 1) != 0)
return; // closed
while ((element = queue.get()) == null)
try {
state |= 2; // waiting
queue.wait();
if ((state & 1) != 0)
return; // closed
state &= 253; // not waiting
} catch (InterruptedException ie) {
}
}
processEvent();
} catch (Throwable t) {
print(t);
try { // fix memory leak
throw new Exception();
} catch (Exception _) {
}
}
}
}
private void makeThread(ThreadGroup threadGroup, String threadName) {
try {
if (privilegedAction == null) {
privilegedAction = new PrivilegedActionImpl();
}
privilegedAction.set(threadGroup, this, threadName);
thread = AccessController.doPrivileged(privilegedAction);
// thread = new Thread(group, this, name);
// thread.setDaemon(false);
// if (!disableContextClassLoader)
// thread.setContextClassLoader(null);
} catch (RuntimeException re) {
throw re;
} catch (Exception exc) {
throw new RuntimeException(exc.toString());
}
}
public Thread getThread() {
return thread;
}
public String getName() {
return name;
}
/**
* Returns the desired thread priority. Called in the constructors of the
* class in order to set the returned value to the thread.
*
* @return priority of the thread
*/
public abstract int getThreadPriority();
/**
* Performs the actual event delivery. The event is stored in the
* <code> element </code> field. The method is supposed to perform the
* following for every listener:
* <li> synchronized on <code> queue </code> check the state of the thread -
* if it si closed - return
* <li> set the fields <code> bad and time <code>
* <li> callback
* <li> set bad to null and time to 0
*/
public abstract void processEvent();
/**
* Checks if the thread is still active. The fields <code> time </code> and
* <code> bad </code> must be used. The method is called from the addEvent
* method - thus should be synchronizes on the <code> queue </code> field
* outside and additional synchronization is not needed.
*/
public abstract boolean checkTime();
/**
* The method must create a new EventThread instance, using
* <code> super.EventThread(this) </code> and start it.
*/
public abstract void newEventDispatcher();
/**
* Logs the error.
*
* @param t
*/
public abstract void print(Throwable t);
}
class PrivilegedActionImpl implements PrivilegedAction<Thread> {
private ThreadGroup group;
private Runnable runnable;
private String name;
private boolean locked = false;
private int waiting = 0;
void set(ThreadGroup group, Runnable runnable, String name) {
lock();
this.group = group;
this.runnable = runnable;
this.name = name;
}
@Override
public Thread run() {
ThreadGroup group1 = this.group;
Runnable runnable1 = this.runnable;
String name1 = this.name;
unlock();
Thread th = new Thread(group1, runnable1, name1);
if (!UtilActivator.getBoolean("equinox.disableContextClassLoader")) //$NON-NLS-1$
th.setContextClassLoader(null);
th.setDaemon(false);
return th;
}
private synchronized void lock() {
while (locked)
try {
waiting++;
wait();
waiting--;
} catch (Exception exc) {
}
locked = true;
}
private synchronized void unlock() {
locked = false;
group = null;
runnable = null;
name = null;
if (waiting > 0)
notifyAll();
}
}