blob: 15b57c0cdb2eee97f88a29425545db0f26a86841 [file] [log] [blame]
// ***************************************************************************************************************************
// * 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.juneau.utils;
import static org.apache.juneau.internal.IOUtils.*;
import static org.apache.juneau.internal.ThrowableUtils.*;
import java.io.*;
import java.util.*;
import org.apache.juneau.internal.*;
/**
* A utility class for piping input streams and readers to output streams and writers.
*
* <p>
* A typical usage is as follows...
* <p class='bcode w800'>
* InputStream in = getInputStream();
* Writer out = getWriter();
* IOPipe.create(in, out).closeOut().run();
* </p>
*
* <p>
* By default, the input stream is closed and the output stream is not.
* This can be changed by calling {@link #closeOut()} and {@link #close(boolean, boolean)}.
*/
public class IOPipe {
private Object input, output;
private boolean byLines;
private boolean closeIn = true, closeOut;
private int buffSize = 1024;
private LineProcessor lineProcessor;
private IOPipe(Object input, Object output) {
assertFieldNotNull(input, "input");
assertFieldNotNull(output, "output");
if (input instanceof InputStream || input instanceof Reader || input instanceof File || input instanceof byte[] || input instanceof CharSequence || input == null)
this.input = input;
else
illegalArg("Invalid input class type. Must be one of the following: InputStream, Reader, CharSequence, byte[], File");
if (output instanceof OutputStream || output instanceof Writer)
this.output = output;
else
illegalArg("Invalid output class type. Must be one of the following: OutputStream, Writer");
}
/**
* Creates a new pipe with the specified input and output.
*
* @param input The input. Must be one of the following types: Reader, InputStream, CharSequence.
* @param output The output. Must be one of the following types: Writer, OutputStream.
* @return This object (for method chaining).
*/
public static IOPipe create(Object input, Object output) {
return new IOPipe(input, output);
}
/**
* Close output after piping.
*
* @return This object (for method chaining).
*/
public IOPipe closeOut() {
this.closeOut = true;
return this;
}
/**
* Specifies whether to close the input and output after piping.
*
* @param in Close input stream. Default is <jk>true</jk>.
* @param out Close output stream. Default is <jk>false</jk>.
* @return This object (for method chaining).
*/
public IOPipe close(boolean in, boolean out) {
this.closeIn = in;
this.closeOut = out;
return this;
}
/**
* Specifies the temporary buffer size.
*
* @param buffSize The buffer size. Default is <c>1024</c>.
* @return This object (for method chaining).
*/
public IOPipe buffSize(int buffSize) {
assertFieldPositive(buffSize, "buffSize");
this.buffSize = buffSize;
return this;
}
/**
* Specifies whether the content should be piped line-by-line.
*
* <p>
* This can be useful if you're trying to pipe console-based input.
*
* @param byLines Pipe content line-by-line. Default is <jk>false</jk>.
* @return This object (for method chaining).
*/
public IOPipe byLines(boolean byLines) {
this.byLines = byLines;
return this;
}
/**
* Same as calling {@link #byLines()} with <jk>true</jk>.
*
* @return This object (for method chaining).
*/
public IOPipe byLines() {
this.byLines = true;
return this;
}
/**
* Specifies a line processor that can be used to process lines before they're piped to the output.
*
* @param lineProcessor The line processor.
* @return This object (for method chaining).
*/
public IOPipe lineProcessor(LineProcessor lineProcessor) {
this.lineProcessor = lineProcessor;
return this;
}
/**
* Interface to implement for the {@link #lineProcessor(LineProcessor)} method.
*/
public interface LineProcessor {
/**
* Process the specified line.
*
* @param line The line to process.
* @return The processed line.
*/
public String process(String line);
}
/**
* Performs the piping of the input to the output.
*
* @return The number of bytes (if streams) or characters (if readers/writers) piped.
* @throws IOException Thrown by underlying stream.
*/
public int run() throws IOException {
int c = 0;
try {
if (input == null)
return 0;
if ((input instanceof InputStream || input instanceof byte[]) && output instanceof OutputStream && lineProcessor == null) {
OutputStream out = (OutputStream)output;
if (input instanceof InputStream) {
InputStream in = (InputStream)input;
byte[] b = new byte[buffSize];
int i;
while ((i = in.read(b)) > 0) {
c += i;
out.write(b, 0, i);
}
} else {
byte[] b = (byte[])input;
out.write(b);
c = b.length;
}
out.flush();
} else {
@SuppressWarnings("resource")
Writer out = (output instanceof Writer ? (Writer)output : new OutputStreamWriter((OutputStream)output, UTF8));
closeIn |= input instanceof File;
if (byLines || lineProcessor != null) {
Reader in = null;
if (input instanceof Reader)
in = (Reader)input;
else if (input instanceof InputStream)
in = new InputStreamReader((InputStream)input, UTF8);
else if (input instanceof File)
in = new FileReader((File)input);
else if (input instanceof byte[])
in = new StringReader(new String((byte[])input, "UTF8"));
else if (input instanceof CharSequence)
in = new StringReader(input.toString());
try (Scanner s = new Scanner(in)) {
while (s.hasNextLine()) {
String l = s.nextLine();
if (lineProcessor != null)
l = lineProcessor.process(l);
if (l != null) {
out.write(l);
out.write("\n");
out.flush();
c += l.length() + 1;
}
}
}
} else {
if (input instanceof InputStream)
input = new InputStreamReader((InputStream)input, UTF8);
else if (input instanceof File)
input = new FileReader((File)input);
else if (input instanceof byte[])
input = new String((byte[])input, UTF8);
if (input instanceof Reader) {
Reader in = (Reader)input;
int i;
char[] b = new char[buffSize];
while ((i = in.read(b)) > 0) {
c += i;
out.write(b, 0, i);
}
} else {
String s = input.toString();
out.write(s);
c = s.length();
}
}
out.flush();
}
} finally {
closeQuietly(input, output);
}
return c;
}
private void closeQuietly(Object input, Object output) {
if (closeIn)
IOUtils.closeQuietly(input);
if (closeOut)
IOUtils.closeQuietly(output);
}
}