Java tutorial
/* * 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.geode.internal.process; import static org.apache.commons.lang.Validate.isTrue; import static org.apache.commons.lang.Validate.notNull; import java.io.InputStream; import org.apache.commons.lang.SystemUtils; /** * Reads the output stream of a Process. * * @since GemFire 7.0 */ public abstract class ProcessStreamReader implements Runnable { private static final int DEFAULT_PROCESS_OUTPUT_WAIT_TIME_MILLIS = 5000; protected final Process process; protected final InputStream inputStream; protected final InputListener inputListener; private Thread thread; protected ProcessStreamReader(final Builder builder) { notNull(builder, "Invalid builder '" + builder + "' specified"); this.process = builder.process; this.inputStream = builder.inputStream; if (builder.inputListener == null) { this.inputListener = new InputListener() { @Override public void notifyInputLine(String line) { // do nothing } @Override public String toString() { return "NullInputListener"; } }; } else { this.inputListener = builder.inputListener; } } public ProcessStreamReader start() { synchronized (this) { if (thread == null) { thread = new Thread(this, createThreadName()); thread.setDaemon(true); thread.start(); } else if (thread.isAlive()) { throw new IllegalStateException(this + " has already started"); } else { throw new IllegalStateException(this + " was stopped and cannot be restarted"); } } return this; } public ProcessStreamReader stop() { synchronized (this) { if (thread != null && thread.isAlive()) { thread.interrupt(); } } return this; } public ProcessStreamReader stopAsync(final long delayMillis) { Runnable delayedStop = () -> { try { Thread.sleep(delayMillis); } catch (InterruptedException ignored) { } finally { stop(); } }; String threadName = getClass().getSimpleName() + " stopAfterDelay Thread @" + Integer.toHexString(hashCode()); Thread thread = new Thread(delayedStop, threadName); thread.setDaemon(true); thread.start(); return this; } public boolean isRunning() { synchronized (this) { if (thread != null) { return thread.isAlive(); } } return false; } public ProcessStreamReader join() throws InterruptedException { Thread thread; synchronized (this) { thread = this.thread; } if (thread != null) { thread.join(); } return this; } public ProcessStreamReader join(final long millis) throws InterruptedException { Thread thread; synchronized (this) { thread = this.thread; } if (thread != null) { thread.join(millis); } return this; } public ProcessStreamReader join(final long millis, final int nanos) throws InterruptedException { Thread thread; synchronized (this) { thread = this.thread; } if (thread != null) { thread.join(millis, nanos); } return this; } @Override public String toString() { StringBuilder sb = new StringBuilder(getClass().getSimpleName()); sb.append(" Thread").append(" #").append(System.identityHashCode(this)); sb.append(" alive=").append(isRunning()); sb.append(" listener=").append(inputListener); return sb.toString(); } private String createThreadName() { return getClass().getSimpleName() + '@' + Integer.toHexString(hashCode()); } /** * Defines the callback for lines of output found in the stream. */ public interface InputListener { void notifyInputLine(final String line); } /** Default ReadingMode is BLOCKING */ public enum ReadingMode { BLOCKING, NON_BLOCKING } private static String waitAndCaptureProcessStandardOutputStream(final Process process, final long waitTimeMilliseconds) { notNull(process, "Invalid process '" + process + "' specified"); return waitAndCaptureProcessStream(process, process.getInputStream(), waitTimeMilliseconds); } public static String waitAndCaptureProcessStandardErrorStream(final Process process) { return waitAndCaptureProcessStandardErrorStream(process, DEFAULT_PROCESS_OUTPUT_WAIT_TIME_MILLIS); } public static String waitAndCaptureProcessStandardErrorStream(final Process process, final long waitTimeMilliseconds) { return waitAndCaptureProcessStream(process, process.getErrorStream(), waitTimeMilliseconds); } private static String waitAndCaptureProcessStream(final Process process, final InputStream processInputStream, final long waitTimeMilliseconds) { StringBuffer buffer = new StringBuffer(); InputListener inputListener = line -> { buffer.append(line); buffer.append(SystemUtils.LINE_SEPARATOR); }; ProcessStreamReader reader = new ProcessStreamReader.Builder(process).inputStream(processInputStream) .inputListener(inputListener).build(); try { reader.start(); long endTime = System.currentTimeMillis() + waitTimeMilliseconds; while (System.currentTimeMillis() < endTime) { try { reader.join(waitTimeMilliseconds); } catch (InterruptedException ignore) { } } } finally { reader.stop(); } return buffer.toString(); } /** * Builds a ProcessStreamReader. * * @since GemFire 8.2 */ public static class Builder { final Process process; InputStream inputStream; InputListener inputListener; long continueReadingMillis = 0; ReadingMode readingMode = ReadingMode.BLOCKING; public Builder(final Process process) { this.process = process; } public Builder inputStream(final InputStream inputStream) { this.inputStream = inputStream; return this; } /** InputListener callback to invoke with read data */ public Builder inputListener(final InputListener inputListener) { this.inputListener = inputListener; return this; } /** millis to continue reading InputStream after Process terminates */ public Builder continueReadingMillis(final long continueReadingMillis) { this.continueReadingMillis = continueReadingMillis; return this; } /** ReadingMode to use for reading InputStream */ public Builder readingMode(final ReadingMode readingMode) { this.readingMode = readingMode; return this; } public ProcessStreamReader build() { notNull(process, "Invalid process '" + process + "' specified"); notNull(inputStream, "Invalid inputStream '" + inputStream + "' specified"); isTrue(continueReadingMillis >= 0, "Invalid continueReadingMillis '" + continueReadingMillis + "' specified"); switch (readingMode) { case NON_BLOCKING: return new NonBlockingProcessStreamReader(this); default: return new BlockingProcessStreamReader(this); } } } }