/******************************************************************************* * 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.longrunning; import java.io.IOException; import java.io.OutputStream; import org.codehaus.groovy.ast.stmt.DoWhileStatement; import org.grails.ide.eclipse.core.launch.GrailsRuntimeProcess; /** * Cleans Grails 2.0 output by removing duplicated messages. * * @author Kris De Volder */ public class Grails20OutputStreamCleaner extends OutputStream { //TODO: This code copied from org.grails.ide.eclipse.core.launch.Grails20OutputCleaner // somehow it should be possible to avoid duplicating all this code and just. // abstract out the cleaning logic into a reusable entity instead. /** * 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; /** * The place where the cleaned output will be sent to. */ private OutputStream downStream; /** * Create a Grails20OutputStreamCleaner. This is an output stream that cleans the * output it receives and sends on a filtered/cleaned version of the output to the * wrapped 'downStream' Stream. * * @param downStream */ public Grails20OutputStreamCleaner(OutputStream downStream) { this.downStream = downStream; } /** * 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. * @throws IOException */ protected void receive(String text) throws IOException { //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() throws IOException { flushLine(); 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). flushLine(); } } /** * 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. * @throws IOException */ private void processLinePiece(String text) throws IOException { // 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 flushLine(); } } } /** * Flushes any unforwarded output. * @throws IOException */ private void flushLine() throws IOException { //Note: can not call this method flush, because that's a public method in the // output stream api. Be careful to call the right flush method! if (pendingNewline) { send(GrailsRuntimeProcess.NEWLINE); pendingNewline = false; } if (currentLineIsPending) { send(currentLine.toString()); currentLineIsPending = false; } } private void send(String string) throws IOException { downStream.write(string.getBytes()); } private boolean hasNewline(String text) { return text.contains(GrailsRuntimeProcess.NEWLINE); } /////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////// I am an output stream so I implement all the 'write' methods @Override public void write(int b) throws IOException { String input = ""+((char)b); receive(input); } @Override public void write(byte[] b, int off, int len) throws IOException { receive(new String(b, off, len)); } @Override public void write(byte[] b) throws IOException { receive(new String(b)); } @Override public void flush() throws IOException { //Important: this does not and should not flush the currentline Otherwise //it messes up our transfomration by forcing output before we have decided //whether it should be transformed. downStream.flush(); } @Override public void close() throws IOException { flushLine(); downStream.close(); } }