blob: 8bcdf44cfb883206fde7f8da7207526e26358c48 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1996, 2008 Wind River Systems, Inc. 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:
* Michael Scharf (Wind River) - initial API and implementation
* Douglas Lea (Addison Wesley) - [cq:1552] BoundedBufferWithStateTracking adapted to BoundedByteBuffer
*******************************************************************************/
package org.eclipse.tm.internal.terminal.control.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.swt.widgets.Display;
/**
* The main purpose of this class is to start a runnable in the
* display thread when data is available and to pretend no data
* is available after a given amount of time the runnable is running.
*
*/
public class TerminalInputStream extends InputStream {
/**
* The maximum time in milliseconds the {@link #fNotifyChange} runs until
* {@link #ready()} returns false.
*/
private final int fUITimeout;
/**
* The output stream used by the terminal backend to write to the terminal
*/
protected final OutputStream fOutputStream;
/**
* This runnable is called every time some characters are available from...
*/
private final Runnable fNotifyChange;
/**
* A shared timer for all terminals. This times is used to limit the
* time used in the display thread....
*/
static Timer fgTimer=new Timer(false);
/**
* A blocking byte queue.
*/
private final BoundedByteBuffer fQueue;
/**
* The maximum amount of data read and written in one shot.
* The timer cannot interrupt reading this amount of data.
* {@link #available()} and {@link #read(byte[], int, int)}
* This is used as optimization, because reading single characters
* can be very inefficient, because each call is synchronized.
*/
// block size must be smaller than the Queue capacity!
final int BLOCK_SIZE=64;
/**
* The runnable that is scheduled in the display tread. Takes care of the
* timeout management. It calls the {@link #fNotifyChange}
*/
// synchronized with fQueue!
private Runnable fRunnable;
/**
* Used as flag to indicate that the current runnable
* has used enough time in the display thread.
* This variable is set by a timer thread after the
* Runnable starts to run in the Display thread after
* {@link #fUITimeout}.
*/
// synchronized with fQueue!
private boolean fEnoughDisplayTime;
/**
* A byte bounded buffer used to synchronize the input and the output stream.
* <p>
* Adapted from BoundedBufferWithStateTracking
* http://gee.cs.oswego.edu/dl/cpj/allcode.java
* http://gee.cs.oswego.edu/dl/cpj/
* <p>
* BoundedBufferWithStateTracking is part of the examples for the book
* Concurrent Programming in Java: Design Principles and Patterns by
* Doug Lea (ISBN 0-201-31009-0). Second edition published by
* Addison-Wesley, November 1999. The code is
* Copyright(c) Douglas Lea 1996, 1999 and released to the public domain
* and may be used for any purposes whatsoever.
* <p>
* For some reasons a solution based on
* PipedOutputStream/PipedIntputStream
* does work *very* slowly:
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4404700
* <p>
*
*/
class BoundedByteBuffer {
protected final byte[] fBuffer; // the elements
protected int fPutPos = 0; // circular indices
protected int fTakePos = 0;
protected int fUsedSlots = 0; // the count
public BoundedByteBuffer(int capacity) throws IllegalArgumentException {
// make sure we don't deadlock on too small capacity
if(capacity<BLOCK_SIZE)
capacity=2*BLOCK_SIZE;
if (capacity <= 0)
throw new IllegalArgumentException();
fBuffer = new byte[capacity];
}
/**
* @return the bytes available for {@link #read()}
*/
public synchronized int size() {
return fUsedSlots;
}
/**
* Writes a single byte to the buffer. Blocks if the buffer is full.
*
* @param b the byte to write
* @throws InterruptedException when the Thread is interrupted while
* waiting for the buffer to become available because it was
* full
*/
public synchronized void write(byte b) throws InterruptedException {
while (fUsedSlots == fBuffer.length)
// wait until not full
wait();
fBuffer[fPutPos] = b;
fPutPos = (fPutPos + 1) % fBuffer.length; // cyclically increment
if (fUsedSlots++ == 0) // signal if was empty
notifyAll();
}
/**
* Read a single byte. Blocks until a byte is available.
*
* @return a byte from the buffer
* @throws InterruptedException when the Thread is interrupted while
* waiting for the buffer to be filled with a readable byte
*/
public synchronized byte read() throws InterruptedException {
while (fUsedSlots == 0)
// wait until not empty
wait();
byte b = fBuffer[fTakePos];
fTakePos = (fTakePos + 1) % fBuffer.length;
if (fUsedSlots-- == fBuffer.length) // signal if was full
notifyAll();
return b;
}
}
/**
* An output stream that calls {@link TerminalInputStream#textAvailable}
* every time data is written to the stream. The data is written to
* {@link TerminalInputStream#fQueue}.
*
*/
class TerminalOutputStream extends OutputStream {
public void write(byte[] b, int off, int len) throws IOException {
try {
// optimization to avoid many synchronized
// sections: put the data in junks into the
// queue.
int noff=off;
int end=off+len;
while(noff<end) {
int n=noff+BLOCK_SIZE;
if(n>end)
n=end;
// now block the queue for the time we need to
// add some characters
synchronized(fQueue) {
for(int i=noff;i<n;i++) {
fQueue.write(b[i]);
}
bytesAreAvailable();
}
noff+=BLOCK_SIZE;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void write(int b) throws IOException {
try {
// a kind of optimization, because
// both calls use the fQueue lock...
synchronized(fQueue) {
fQueue.write((byte)b);
bytesAreAvailable();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* @param bufferSize the size of the buffer of the output stream
* @param uiTimeout the maximum time the notifyChange runnable runs. It will be
* rescheduled after uiTimeout if input data is still available.
* @param notifyChange a Runnable that is posted to the Display thread
* via {@link Display#asyncExec}. The runnable is posted several times!
*/
public TerminalInputStream(int bufferSize,int uiTimeout,Runnable notifyChange) {
//if(true) {notifyChange=new Runnable() {public void run() {byte buff[]=new byte[1024];while((available())>0)try {read(buff);} catch (IOException e) {break;}}};}
fOutputStream =new TerminalOutputStream();
fNotifyChange=notifyChange;
fQueue=new BoundedByteBuffer(bufferSize);
fUITimeout=uiTimeout;
}
/**
* Posts the runnable {@link #fNotifyChange} to the display Thread,
* unless the runnable is already scheduled.
* It will make {@link #ready} return false after
* {@link #fUITimeout} milli seconds.
*/
void bytesAreAvailable() {
// synchronize on the Queue to reduce the locks
synchronized(fQueue) {
if(fRunnable==null) {
fRunnable=new Runnable(){
public void run() {
// protect the access to fRunnable
synchronized(fQueue){
fRunnable=null;
}
// end the reading after some time
startTimer(fUITimeout);
// and start the real runnable
fNotifyChange.run();
}
};
// TODO: make sure we don't create a display if the display is disposed...
Display.getDefault().asyncExec(fRunnable);
}
}
}
/**
* Starts a timer that sets {@link #fEnoughDisplayTime} to
* true after milliSec.
* @param milliSec The time after which fEnoughDisplayTime is set to true.
*/
void startTimer(int milliSec) {
synchronized(fQueue) {
fEnoughDisplayTime=false;
}
fgTimer.schedule(new TimerTask(){
public void run() {
synchronized(fQueue) {
fEnoughDisplayTime=true;
// there is some data available
if(fQueue.size()>0) {
// schedule a new runnable to do the work
bytesAreAvailable();
}
}
}}, milliSec);
}
/**
* @return the output stream used by the backend to write to the terminal.
*/
public OutputStream getOutputStream() {
return fOutputStream;
}
/**
* Must be called in the Display Thread!
* @return true if a character is available for the terminal to show.
*/
public int available() {
int available;
synchronized(fQueue) {
if(fEnoughDisplayTime)
return 0;
available=fQueue.size();
}
// Limit the available amount of data.
// else our trick of limiting the time spend
// reading might not work.
if(available>BLOCK_SIZE)
available=BLOCK_SIZE;
return available;
}
/**
* @return the next available byte. Check with {@link #available}
* if characters are available.
*/
public int read() throws IOException {
try {
return fQueue.read();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
}
}
/**
* Closing a <tt>ByteArrayInputStream</tt> has no effect. The methods in
* this class can be called after the stream has been closed without
* generating an <tt>IOException</tt>.
* <p>
*/
public void close() throws IOException {
}
public int read(byte[] cbuf, int off, int len) throws IOException {
int n=0;
// read as much as we can using a single synchronized statement
synchronized (fQueue) {
try {
// The assumption is that the caller has used available to
// check if bytes are available! That's why we don't check
// for fEnoughDisplayTime!
// Make sure that not more than BLOCK_SIZE is read in one call
while(fQueue.size()>0 && n<len && n<BLOCK_SIZE) {
cbuf[off+n]=fQueue.read();
n++;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return n;
}
}