/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.util.stream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; public class StreamGobbler extends Thread implements Closeable { protected final InputStream stream; protected final PrintStream out; protected final Logger log; private final AtomicBoolean running = new AtomicBoolean(true); public StreamGobbler(InputStream stream, OutputStream out, Logger log) { this(stream, out != null ? new PrintStream(out) : null, log); } public StreamGobbler(InputStream stream, PrintStream out, Logger log) { this.stream = stream; this.out = out; this.log = log; } @Override public void close() { running.set(false); interrupt(); } /** * @deprecate Use close() instead. */ @Deprecated public void shutdown() { close(); } String logPrefix = ""; String printPrefix = ""; public StreamGobbler setPrefix(String prefix) { setLogPrefix(prefix); setPrintPrefix(prefix); return this; } public StreamGobbler setPrintPrefix(String prefix) { printPrefix = prefix; return this; } public StreamGobbler setLogPrefix(String prefix) { logPrefix = prefix; return this; } @Override public void run() { int c = -1; try { while (running.get() && (c=stream.read())>=0) { onChar(c); } onClose(); } catch (IOException e) { onClose(); //TODO parametrise log level, for this error, and for normal messages if (log!=null && log.isTraceEnabled()) log.trace(logPrefix+"exception reading from stream ("+e+")"); } } private final StringBuilder lineSoFar = new StringBuilder(16); public void onChar(int c) { if (c=='\n' || c=='\r') { if (lineSoFar.length()>0) //suppress blank lines, so that we can treat either newline char as a line separator //(eg to show curl updates frequently) onLine(lineSoFar.toString()); lineSoFar.setLength(0); } else { lineSoFar.append((char)c); } } public void onLine(String line) { //right trim, in case there is \r or other funnies while (line.length()>0 && Character.isWhitespace(line.charAt(line.length()-1))) line = line.substring(0, line.length()-1); //right trim, in case there is \r or other funnies while (line.length()>0 && (line.charAt(0)=='\n' || line.charAt(0)=='\r')) line = line.substring(1); if (!line.isEmpty()) { if (out!=null) out.println(printPrefix+line); if (log!=null && log.isDebugEnabled()) log.debug(logPrefix+line); } } public void onClose() { onLine(lineSoFar.toString()); if (out!=null) out.flush(); lineSoFar.setLength(0); finished = true; synchronized (this) { notifyAll(); } } private volatile boolean finished = false; /** convenience -- equivalent to calling join() */ public void blockUntilFinished() throws InterruptedException { synchronized (this) { while (!finished) wait(); } } /** convenience -- similar to !Thread.isAlive() */ public boolean isFinished() { return finished; } }