package org.grails.ide.eclipse.core.launch;
import org.grails.ide.eclipse.core.launch.TransformedStreamMonitor.StreamTransformer;
public class Grails20OutputCleaner extends StreamTransformer {
/**
* Remembers the last line of text received. When it is a prefix of the currentLine the prefix will
* be supressed in the output.
*/
String lastLine = "";
/**
* Builds up the currentLine as we receive output from the original Stream.
*/
StringBuilder currentLine = new StringBuilder();
/**
* When a newline is sent, we hold on to it until we have made a decision what to do with the
* next line (maybe it will not be on a newline if it just 'extends' the previous line).
* <p>
* This flag is true when we are holding on to a newline that has not been sent downstream
* yet.
*/
boolean pendingNewline = false;
/**
* This will be true as long as we have not yet been able to decide whether the current line of
* input matches the last line and needs to be transformed. Once a decision is made,
* the output will be transformed as required and flushed; and this flag set to false.
*/
boolean currentLineIsPending = true;
/**
* Text can be received from the process in bits and pieces. In large chunks or small chunks.
* The chunks don't necessarily break off at newline boundaries.
*/
@Override
protected void receive(String text) {
//TODO: A lot of String copying in here. Could be optimized?
while (text.length()!=0) {
if (!hasNewline(text)) {
processLinePiece(text);
text = "";
} else { //There's a newline in the text somewhere.
int newlinePos = text.indexOf(GrailsRuntimeProcess.NEWLINE);
String firstPiece = text.substring(0, newlinePos);
String rest = text.substring(newlinePos+GrailsRuntimeProcess.NEWLINE.length());
processLinePiece(firstPiece);
processNewline();
text = rest;
}
}
}
private void processNewline() {
flush();
lastLine = currentLine.toString();
currentLine.setLength(0);
pendingNewline = true;
currentLineIsPending = true;
if (lastLine.contains("http://") || lastLine.contains("https://")) {
//STS-2155: don't prevent console window from detecting the URL (which it will only do once a newline is received).
flush();
}
}
/**
* Called on to process a piece of text that has no newlines. Could be a whole line of
* text or just a portion of a line.
*/
private void processLinePiece(String text) {
// The tricky bit in here is that we must flush output as early as possible.
//otherwise the output won't appear in the console window.
// In particular, we can't wait for a newline to decide when the current line is complete.
// It is common for Grails to print a question without a newline at the end, and
// this text should appear immediately on the screen rather than remain buffered up in this
// transformer (leaving the user wondering why the process appears stuck).
currentLine.append(text);
if (!currentLineIsPending) {
send(text);
} else { //currentLineIsPending
String currentLineStr = currentLine.toString();
if (currentLineStr.startsWith(lastLine)) {
//We've got a match... eat the repeated bit and send the rest.
currentLineIsPending = false;
send(currentLineStr.substring(lastLine.length()));
pendingNewline = false;
} else if (lastLine.startsWith(currentLineStr)) {
//Could be a match... or not...
//Nothing to do, just keep it pending
} else {
//It's not a match... flush it
flush();
}
}
}
/**
* Flushes any unforwarded output.
*/
private void flush() {
if (pendingNewline) {
send(GrailsRuntimeProcess.NEWLINE);
pendingNewline = false;
}
if (currentLineIsPending) {
send(currentLine.toString());
currentLineIsPending = false;
}
}
private boolean hasNewline(String text) {
return text.contains(GrailsRuntimeProcess.NEWLINE);
}
}