/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, Inc.
* 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.core.launch;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IFlushableStreamMonitor;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.debug.internal.core.OutputStreamMonitor;
/**
* Wraps around an existing IStreamMonitor and provides a view to its listeners that is transformed
* in some way or another.
*
* @author Kris De Volder
*
* @since 2.8
*/
public class TransformedStreamMonitor implements IStreamMonitor, IFlushableStreamMonitor, IStreamListener {
/**
* An instance of this class implements some transformation on a stream. It gets called eachtime
* the original Stream is appended and decides when to pass on transformed data to a
* downstream StreamListener.
*
* @author Kris De Volder
*
* @since 2.8
*/
public static class StreamTransformer {
private TransformedStreamMonitor downstream;
/**
* The framework will call this to initialise the transformer and setup its downstream connection to
* received data. This method is of no importance to clients and should not be called or overridden by
* client code.
*/
final private void init(TransformedStreamMonitor downstream) {
Assert.isLegal(downstream!=null, "downstream param should not be null");
Assert.isLegal(this.downstream==null, "Init called more than once");
this.downstream = downstream;
}
/**
* Implementers should call this method to pass on transformed data to the downstream client.
*/
protected final void send(String transformedText) {
downstream.sendDownstream(transformedText);
}
/**
* Implementers should override this method to receive data from the upstream provider. A default
* implementation is provided that implements the 'identity' transform (i.e. it just passes any
* received data unchanged to the downstream receiver.
*/
protected void receive(String text) {
send(text);
}
}
// /**
// * We register ourselves as a listener to this monitor.
// */
// private IStreamMonitor wrappedMon;
/**
* We will present 'transformed' output to these listeners.
*/
private ListenerList listeners = new ListenerList();
/**
* Buffers up contents, may be null, if 'isBuffered' is false.
*/
private StringBuffer contents = createBuffer();
/**
* When true, received output is appended to the buffer and can be retrieved
* by getContents
*/
private boolean isBuffered = true;
/**
* Where we plugin the object that does the actual transformation of the Stream.
*/
private StreamTransformer transformer;
public TransformedStreamMonitor(OutputStreamMonitor mon, StreamTransformer transformer) {
this.transformer = transformer;
transformer.init(this);
// this.wrappedMon = mon;
synchronized (mon) {
//1: To avoid loosing output we must fetch the output that
//may already have been produced before we registered as a listener.
//2: To avoid duplicated output, we must lock the wrappedMonitor while
//we do this.
mon.addListener(this);
Assert.isTrue(listeners.isEmpty(), "It should not be possible to have listeneres at this point yet.");
//Since there are no listeners yet (listening to us), we don't need to notify listeners of the
//already produced text... we just need to make sure we don't loose that text.
transformer.receive(mon.getContents());
mon.flushContents();
mon.setBuffered(false); // otherwise it keeps buffering and eats up memory
}
}
////////////////////////////////////////////////////////////////////////////
//Implementation of IStreamMonitor
private StringBuffer createBuffer() {
return new StringBuffer(2048);
}
public synchronized void addListener(IStreamListener l) {
listeners.add(l);
}
public synchronized String getContents() {
return contents.toString();
}
public synchronized void removeListener(IStreamListener l) {
listeners.remove(l);
}
////////////////////////////////////////////////////////////////////////////
//Implementation of IFlushableStreamMonitor
public synchronized void flushContents() {
contents.setLength(0);
}
public synchronized void setBuffered(boolean buffer) {
this.isBuffered = buffer;
}
public synchronized boolean isBuffered() {
return isBuffered;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation of IStreamListener (this repesents a connection upstream, to receive the original data).
public synchronized void streamAppended(String text, IStreamMonitor monitor) {
// Assert.isTrue(monitor==wrappedMon,
// "I only registered myself as a listener with one monitor!");
transformer.receive(text);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// The code below represents the downstream connection where data is being sent by the transformer.
public void sendDownstream(String transformedText) {
//Note: no synch locks should be held by this method while calling the listeners.
//(too risky for deadlock as we do not know what those listeners might be doing/locking)
//Therefore make a local copy of the listeners.
Object[] copiedListeners = listeners.getListeners();
for (Object _l : copiedListeners) {
IStreamListener l = (IStreamListener) _l;
l.streamAppended(transformedText, this);
}
}
}