blob: ede9e4b0a2d4e864a22ffcd1d75587791763c424 [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.codehaus.groovy.runtime;
import groovy.io.GroovyPrintWriter;
import groovy.lang.Closure;
import groovy.lang.StringWriterIOException;
import groovy.lang.Writable;
import groovy.transform.stc.ClosureParams;
import groovy.transform.stc.FirstParam;
import groovy.transform.stc.FromString;
import groovy.transform.stc.PickFirstResolver;
import groovy.transform.stc.SimpleType;
import org.apache.groovy.io.StringBuilderWriter;
import org.codehaus.groovy.runtime.callsite.BooleanClosureWrapper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import static org.codehaus.groovy.ast.tools.ClosureUtils.hasSingleStringArg;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.callClosureForLine;
/**
* This class defines new groovy methods for Files, URLs, URIs which appear
* on normal JDK classes inside the Groovy environment.
* Static methods are used with the first parameter being the destination class,
* i.e. <code>public static long size(File self)</code>
* provides a <code>size()</code> method for <code>File</code>.
* <p>
* NOTE: While this class contains many 'public' static methods, it is
* primarily regarded as an internal class (its internal package name
* suggests this also). We value backwards compatibility of these
* methods when used within Groovy but value less backwards compatibility
* at the Java method call level. I.e. future versions of Groovy may
* remove or move a method call in this file but would normally
* aim to keep the method available from within Groovy.
*/
public class IOGroovyMethods extends DefaultGroovyMethodsSupport {
private static final Logger LOG = Logger.getLogger(IOGroovyMethods.class.getName());
/**
* Overloads the leftShift operator for Writer to allow an object to be written
* using Groovy's default representation for the object.
*
* @param self a Writer
* @param value an Object whose default representation will be written to the Writer
* @return the writer on which this operation was invoked
* @throws IOException if an I/O error occurs.
* @since 1.0
*/
public static Writer leftShift(Writer self, Object value) throws IOException {
InvokerHelper.write(self, value);
return self;
}
/**
* Overloads the leftShift operator for Appendable to allow an object to be appended
* using Groovy's default representation for the object.
*
* @param self an Appendable
* @param value an Object whose default representation will be appended to the Appendable
* @return the Appendable on which this operation was invoked
* @throws IOException if an I/O error occurs.
* @since 2.1.0
*/
public static Appendable leftShift(Appendable self, Object value) throws IOException {
InvokerHelper.append(self, value);
return self;
}
/**
* Invokes a Closure that uses a Formatter taking care of resource handling.
* A Formatter is created and passed to the Closure as its argument.
* After the Closure executes, the Formatter is flushed and closed releasing any
* associated resources.
*
* @param self an Appendable
* @param closure a 1-arg Closure which will be called with a Formatter as its argument
* @return the Appendable on which this operation was invoked
* @since 2.1.0
*/
public static Appendable withFormatter(Appendable self, @ClosureParams(value=SimpleType.class, options="java.util.Formatter") Closure closure) {
Formatter formatter = new Formatter(self);
callWithFormatter(closure, formatter);
return self;
}
/**
* Invokes a Closure that uses a Formatter taking care of resource handling.
* A Formatter is created using the given Locale and passed to the Closure as its argument.
* After the Closure executes, the Formatter is flushed and closed releasing any
* associated resources.
*
* @param self an Appendable
* @param locale a Locale used when creating the Formatter
* @param closure a 1-arg Closure which will be called with a Formatter as its argument
* @return the Appendable on which this operation was invoked
* @since 2.1.0
*/
public static Appendable withFormatter(Appendable self, Locale locale, @ClosureParams(value=SimpleType.class, options="java.util.Formatter") Closure closure) {
Formatter formatter = new Formatter(self, locale);
callWithFormatter(closure, formatter);
return self;
}
private static void callWithFormatter(Closure closure, Formatter formatter) {
try {
closure.call(formatter);
} finally {
formatter.flush();
formatter.close();
}
}
/**
* A helper method so that dynamic dispatch of the writer.write(object) method
* will always use the more efficient Writable.writeTo(writer) mechanism if the
* object implements the Writable interface.
*
* @param self a Writer
* @param writable an object implementing the Writable interface
* @throws IOException if an I/O error occurs.
* @since 1.0
*/
public static void write(Writer self, Writable writable) throws IOException {
writable.writeTo(self);
}
/**
* Overloads the leftShift operator to provide an append mechanism to add values to a stream.
*
* @param self an OutputStream
* @param value a value to append
* @return a Writer
* @throws java.io.IOException if an I/O error occurs.
* @since 1.0
*/
public static Writer leftShift(OutputStream self, Object value) throws IOException {
OutputStreamWriter writer = new FlushingStreamWriter(self);
leftShift(writer, value);
return writer;
}
/**
* Overloads the leftShift operator to add objects to an ObjectOutputStream.
*
* @param self an ObjectOutputStream
* @param value an object to write to the stream
* @throws IOException if an I/O error occurs.
* @since 1.5.0
*/
public static void leftShift(ObjectOutputStream self, Object value) throws IOException {
self.writeObject(value);
}
/**
* Pipe an InputStream into an OutputStream for efficient stream copying.
*
* @param self stream on which to write
* @param in stream to read from
* @return the outputstream itself
* @throws IOException if an I/O error occurs.
* @since 1.0
*/
public static OutputStream leftShift(OutputStream self, InputStream in) throws IOException {
byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
for (int count; -1 != (count = in.read(buf)); ) {
self.write(buf, 0, count);
}
self.flush();
return self;
}
/**
* Overloads the leftShift operator to provide an append mechanism to add bytes to a stream.
*
* @param self an OutputStream
* @param value a value to append
* @return an OutputStream
* @throws IOException if an I/O error occurs.
* @since 1.0
*/
public static OutputStream leftShift(OutputStream self, byte[] value) throws IOException {
self.write(value);
self.flush();
return self;
}
/**
* Create an object output stream for this output stream.
*
* @param outputStream an output stream
* @return an object output stream
* @throws IOException if an IOException occurs.
* @since 1.5.0
*/
public static ObjectOutputStream newObjectOutputStream(OutputStream outputStream) throws IOException {
return new ObjectOutputStream(outputStream);
}
/**
* Create a new ObjectOutputStream for this output stream and then pass it to the
* closure. This method ensures the stream is closed after the closure
* returns.
*
* @param outputStream am output stream
* @param closure a closure
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see #withStream(java.io.OutputStream, groovy.lang.Closure)
* @since 1.5.0
*/
public static <T> T withObjectOutputStream(OutputStream outputStream, @ClosureParams(value=SimpleType.class, options="java.io.ObjectOutputStream") Closure<T> closure) throws IOException {
return withStream(newObjectOutputStream(outputStream), closure);
}
/**
* Create an object input stream for this input stream.
*
* @param inputStream an input stream
* @return an object input stream
* @throws IOException if an IOException occurs.
* @since 1.5.0
*/
public static ObjectInputStream newObjectInputStream(InputStream inputStream) throws IOException {
return new ObjectInputStream(inputStream);
}
/**
* Create an object input stream for this input stream using the given class loader.
*
* @param inputStream an input stream
* @param classLoader the class loader to use when loading the class
* @return an object input stream
* @throws IOException if an IOException occurs.
* @since 1.5.0
*/
public static ObjectInputStream newObjectInputStream(InputStream inputStream, final ClassLoader classLoader) throws IOException {
return new ObjectInputStream(inputStream) {
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
return Class.forName(desc.getName(), true, classLoader);
}
};
}
/**
* Iterates through the given object stream object by object. The
* ObjectInputStream is closed afterwards.
*
* @param ois an ObjectInputStream, closed after the operation
* @param closure a closure
* @throws IOException if an IOException occurs.
* @throws ClassNotFoundException if the class is not found.
* @since 1.0
*/
public static void eachObject(ObjectInputStream ois, Closure closure) throws IOException, ClassNotFoundException {
try {
while (true) {
try {
Object obj = ois.readObject();
// we allow null objects in the object stream
closure.call(obj);
} catch (EOFException e) {
break;
}
}
InputStream temp = ois;
ois = null;
temp.close();
} finally {
closeWithWarning(ois);
}
}
/**
* Create a new ObjectInputStream for this file and pass it to the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param inputStream an input stream
* @param closure a closure
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see #withStream(java.io.InputStream, groovy.lang.Closure)
* @since 1.5.0
*/
public static <T> T withObjectInputStream(InputStream inputStream, @ClosureParams(value=SimpleType.class, options="java.io.ObjectInputStream") Closure<T> closure) throws IOException {
return withStream(newObjectInputStream(inputStream), closure);
}
/**
* Create a new ObjectInputStream for this file and pass it to the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param inputStream an input stream
* @param classLoader the class loader to use when loading the class
* @param closure a closure
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see #withStream(java.io.InputStream, groovy.lang.Closure)
* @since 1.5.0
*/
public static <T> T withObjectInputStream(InputStream inputStream, ClassLoader classLoader, @ClosureParams(value=SimpleType.class, options="java.io.ObjectInputStream") Closure<T> closure) throws IOException {
return withStream(newObjectInputStream(inputStream, classLoader), closure);
}
/**
* Iterates through this stream reading with the provided charset, passing each line to the
* given 1 or 2 arg closure. The stream is closed before this method returns.
*
* @param stream a stream
* @param charset opens the stream with a specified charset
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.InputStream, java.lang.String, int, groovy.lang.Closure)
* @since 1.5.5
*/
public static <T> T eachLine(InputStream stream, String charset, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(stream, charset, 1, closure);
}
/**
* Iterates through this stream reading with the provided charset, passing each line to
* the given 1 or 2 arg closure. The stream is closed after this method returns.
*
* @param stream a stream
* @param charset opens the stream with a specified charset
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.Reader, int, groovy.lang.Closure)
* @since 1.5.7
*/
public static <T> T eachLine(InputStream stream, String charset, int firstLine, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(new InputStreamReader(stream, charset), firstLine, closure);
}
/**
* Iterates through this stream, passing each line to the given 1 or 2 arg closure.
* The stream is closed before this method returns.
*
* @param stream a stream
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.InputStream, int, groovy.lang.Closure)
* @since 1.5.6
*/
public static <T> T eachLine(InputStream stream, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(stream, 1, closure);
}
/**
* Iterates through this stream, passing each line to the given 1 or 2 arg closure.
* The stream is closed before this method returns.
*
* @param stream a stream
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.Reader, int, groovy.lang.Closure)
* @since 1.5.7
*/
public static <T> T eachLine(InputStream stream, int firstLine, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(new InputStreamReader(stream), firstLine, closure);
}
/**
* Iterates through the given reader line by line. Each line is passed to the
* given 1 or 2 arg closure. If the closure has two arguments, the line count is passed
* as the second argument. The Reader is closed before this method returns.
*
* @param self a Reader, closed after the method returns
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #eachLine(java.io.Reader, int, groovy.lang.Closure)
* @since 1.5.6
*/
public static <T> T eachLine(Reader self, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(self, 1, closure);
}
/**
* Iterates through the given reader line by line. Each line is passed to the
* given 1 or 2 arg closure. If the closure has two arguments, the line count is passed
* as the second argument. The Reader is closed before this method returns.
*
* @param self a Reader, closed after the method returns
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure which will be passed each line (or for 2 arg closures the line and line count)
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.7
*/
public static <T> T eachLine(Reader self, int firstLine, @ClosureParams(value=FromString.class,options={"String","String,Integer"}) Closure<T> closure) throws IOException {
BufferedReader br;
int count = firstLine;
T result = null;
if (self instanceof BufferedReader)
br = (BufferedReader) self;
else
br = new BufferedReader(self);
try {
while (true) {
String line = br.readLine();
if (line == null) {
break;
} else {
result = callClosureForLine(closure, line, count);
count++;
}
}
Reader temp = self;
self = null;
temp.close();
return result;
} finally {
closeWithWarning(self);
closeWithWarning(br);
}
}
/**
* Iterates through the given reader line by line, splitting each line using
* the given regex separator. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression. The Reader is closed afterwards.
* <p>
* Here is an example:
* <pre>
* def s = 'The 3 quick\nbrown 4 fox'
* def result = ''
* new StringReader(s).splitEachLine(/\d/){ parts {@code ->}
* result += "${parts[0]}_${parts[1]}|"
* }
* assert result == 'The _ quick|brown _ fox|'
* </pre>
*
* @param self a Reader, closed after the method returns
* @param regex the delimiting regular expression
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException
* if the regular expression's syntax is invalid
* @see java.lang.String#split(java.lang.String)
* @since 1.5.5
*/
public static <T> T splitEachLine(Reader self, String regex, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
return splitEachLine(self, Pattern.compile(regex), closure);
}
/**
* Iterates through the given reader line by line, splitting each line using
* the given regex separator Pattern. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression. The Reader is closed afterwards.
* <p>
* Here is an example:
* <pre>
* def s = 'The 3 quick\nbrown 4 fox'
* def result = ''
* new StringReader(s).splitEachLine(~/\d/){ parts {@code ->}
* result += "${parts[0]}_${parts[1]}|"
* }
* assert result == 'The _ quick|brown _ fox|'
* </pre>
*
* @param self a Reader, closed after the method returns
* @param pattern the regular expression Pattern for the delimiter
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException
* if the regular expression's syntax is invalid
* @see java.lang.String#split(java.lang.String)
* @since 1.6.8
*/
public static <T> T splitEachLine(Reader self, Pattern pattern, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
BufferedReader br;
T result = null;
if (self instanceof BufferedReader)
br = (BufferedReader) self;
else
br = new BufferedReader(self);
try {
while (true) {
String line = br.readLine();
if (line == null) {
break;
} else {
List vals = Arrays.asList(pattern.split(line));
result = closure.call(hasSingleStringArg(closure) ? vals.get(0) : vals);
}
}
Reader temp = self;
self = null;
temp.close();
return result;
} finally {
closeWithWarning(self);
closeWithWarning(br);
}
}
/**
* Iterates through the given InputStream line by line using the specified
* encoding, splitting each line using the given separator. The list of tokens
* for each line is then passed to the given closure. Finally, the stream
* is closed.
*
* @param stream an InputStream
* @param regex the delimiting regular expression
* @param charset opens the stream with a specified charset
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException
* if the regular expression's syntax is invalid
* @see #splitEachLine(java.io.Reader, java.lang.String, groovy.lang.Closure)
* @since 1.5.5
*/
public static <T> T splitEachLine(InputStream stream, String regex, String charset, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
return splitEachLine(new BufferedReader(new InputStreamReader(stream, charset)), regex, closure);
}
/**
* Iterates through the given InputStream line by line using the specified
* encoding, splitting each line using the given separator Pattern. The list of tokens
* for each line is then passed to the given closure. Finally, the stream
* is closed.
*
* @param stream an InputStream
* @param pattern the regular expression Pattern for the delimiter
* @param charset opens the stream with a specified charset
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #splitEachLine(java.io.Reader, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.6.8
*/
public static <T> T splitEachLine(InputStream stream, Pattern pattern, String charset, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
return splitEachLine(new BufferedReader(new InputStreamReader(stream, charset)), pattern, closure);
}
/**
* Iterates through the given InputStream line by line, splitting each line using
* the given separator. The list of tokens for each line is then passed to
* the given closure. The stream is closed before the method returns.
*
* @param stream an InputStream
* @param regex the delimiting regular expression
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException
* if the regular expression's syntax is invalid
* @see #splitEachLine(java.io.Reader, java.lang.String, groovy.lang.Closure)
* @since 1.5.6
*/
public static <T> T splitEachLine(InputStream stream, String regex, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
return splitEachLine(new BufferedReader(new InputStreamReader(stream)), regex, closure);
}
/**
* Iterates through the given InputStream line by line, splitting each line using
* the given separator Pattern. The list of tokens for each line is then passed to
* the given closure. The stream is closed before the method returns.
*
* @param stream an InputStream
* @param pattern the regular expression Pattern for the delimiter
* @param closure a closure
* @return the last value returned by the closure
* @throws IOException if an IOException occurs.
* @see #splitEachLine(java.io.Reader, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.6.8
*/
public static <T> T splitEachLine(InputStream stream, Pattern pattern, @ClosureParams(value=FromString.class,options={"List<String>","String[]"},conflictResolutionStrategy=PickFirstResolver.class) Closure<T> closure) throws IOException {
return splitEachLine(new BufferedReader(new InputStreamReader(stream)), pattern, closure);
}
/**
* Read a single, whole line from the given Reader. This method is designed for use with
* Readers that support the {@code mark()} operation like BufferReader. It has a fallback
* behavior for Readers that don't support mark() but the behavior doesn't correctly
* detect multi-character line termination (e.g. carriage return followed by linefeed).
* We recommend for Readers that don't support mark() you consider using one of the
* following methods instead: eachLine, readLines, or iterator.
*
* @param self a Reader
* @return a line
* @throws IOException if an IOException occurs.
* @see #readLines(java.io.Reader)
* @see #iterator(java.io.Reader)
* @see #eachLine(java.io.Reader, groovy.lang.Closure)
* @since 1.0
*/
public static String readLine(Reader self) throws IOException {
if (self instanceof BufferedReader) {
BufferedReader br = (BufferedReader) self;
return br.readLine();
}
if (self.markSupported()) {
return readLineFromReaderWithMark(self);
}
return readLineFromReaderWithoutMark(self);
}
private static int charBufferSize = 4096; // half the default stream buffer size
private static int expectedLineLength = 160; // double the default line length
private static int EOF = -1; // End Of File
/*
* This method tries to read subsequent buffers from the reader using a mark
*/
private static String readLineFromReaderWithMark(final Reader input)
throws IOException {
char[] cbuf = new char[charBufferSize];
try {
input.mark(charBufferSize);
} catch (IOException e) {
// this should never happen
LOG.warning("Caught exception setting mark on supporting reader: " + e);
// fallback
return readLineFromReaderWithoutMark(input);
}
// could be changed into do..while, but then
// we might create an additional StringBuilder
// instance at the end of the stream
int count = input.read(cbuf);
if (count == EOF) // we are at the end of the input data
return null;
StringBuilder line = new StringBuilder(expectedLineLength);
// now work on the buffer(s)
int ls = lineSeparatorIndex(cbuf, count);
while (ls == -1) {
line.append(cbuf, 0, count);
count = input.read(cbuf);
if (count == EOF) {
// we are at the end of the input data
return line.toString();
}
ls = lineSeparatorIndex(cbuf, count);
}
line.append(cbuf, 0, ls);
// correct ls if we have \r\n
int skipLS = 1;
if (ls + 1 < count) {
// we are not at the end of the buffer
if (cbuf[ls] == '\r' && cbuf[ls + 1] == '\n') {
skipLS++;
}
} else {
if (cbuf[ls] == '\r' && input.read() == '\n') {
skipLS++;
}
}
//reset() and skip over last linesep
input.reset();
input.skip(line.length() + skipLS);
return line.toString();
}
/*
* This method reads without a buffer.
* It returns too many empty lines if \r\n combinations
* are used. Nothing can be done because we can't push
* back the character we have just read.
*/
private static String readLineFromReaderWithoutMark(Reader input)
throws IOException {
int c = input.read();
if (c == -1)
return null;
StringBuilder line = new StringBuilder(expectedLineLength);
while (c != EOF && c != '\n' && c != '\r') {
char ch = (char) c;
line.append(ch);
c = input.read();
}
return line.toString();
}
/*
* searches for \n or \r
* Returns -1 if not found.
*/
private static int lineSeparatorIndex(char[] array, int length) {
for (int k = 0; k < length; k++) {
if (isLineSeparator(array[k])) {
return k;
}
}
return -1;
}
/*
* true if either \n or \r
*/
private static boolean isLineSeparator(char c) {
return c == '\n' || c == '\r';
}
/**
* Reads the stream into a list, with one element for each line.
*
* @param stream a stream
* @return a List of lines
* @throws IOException if an IOException occurs.
* @see #readLines(java.io.Reader)
* @since 1.0
*/
public static List<String> readLines(InputStream stream) throws IOException {
return readLines(newReader(stream));
}
/**
* Reads the stream into a list, with one element for each line.
*
* @param stream a stream
* @param charset opens the stream with a specified charset
* @return a List of lines
* @throws IOException if an IOException occurs.
* @see #readLines(java.io.Reader)
* @since 1.6.8
*/
public static List<String> readLines(InputStream stream, String charset) throws IOException {
return readLines(newReader(stream, charset));
}
/**
* Reads the reader into a list of Strings, with one entry for each line.
* The reader is closed before this method returns.
*
* @param reader a Reader
* @return a List of lines
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static List<String> readLines(Reader reader) throws IOException {
IteratorClosureAdapter<String> closure = new IteratorClosureAdapter<String>(reader);
eachLine(reader, closure);
return closure.asList();
}
/**
* Read the content of this InputStream and return it as a String.
* The stream is closed before this method returns.
*
* @param is an input stream
* @return the text from that URL
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static String getText(InputStream is) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
return getText(reader);
}
/**
* Read the content of this InputStream using specified charset and return
* it as a String. The stream is closed before this method returns.
*
* @param is an input stream
* @param charset opens the stream with a specified charset
* @return the text from that URL
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static String getText(InputStream is, String charset) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
return getText(reader);
}
/**
* Read the content of the Reader and return it as a String. The reader
* is closed before this method returns.
*
* @param reader a Reader whose content we want to read
* @return a String containing the content of the buffered reader
* @throws IOException if an IOException occurs.
* @see #getText(java.io.BufferedReader)
* @since 1.0
*/
public static String getText(Reader reader) throws IOException {
BufferedReader bufferedReader = new BufferedReader(reader);
return getText(bufferedReader);
}
/**
* Read the content of the BufferedReader and return it as a String.
* The BufferedReader is closed afterwards.
*
* @param reader a BufferedReader whose content we want to read
* @return a String containing the content of the buffered reader
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static String getText(BufferedReader reader) throws IOException {
StringBuilder answer = new StringBuilder();
// reading the content of the file within a char buffer
// allow to keep the correct line endings
char[] charBuffer = new char[8192];
int nbCharRead /* = 0*/;
try {
while ((nbCharRead = reader.read(charBuffer)) != -1) {
// appends buffer
answer.append(charBuffer, 0, nbCharRead);
}
Reader temp = reader;
reader = null;
temp.close();
} finally {
closeWithWarning(reader);
}
return answer.toString();
}
/**
* Read the content of this InputStream and return it as a byte[].
* The stream is closed before this method returns.
*
* @param is an input stream
* @return the byte[] from that InputStream
* @throws IOException if an IOException occurs.
* @since 1.7.1
*/
public static byte[] getBytes(InputStream is) throws IOException {
ByteArrayOutputStream answer = new ByteArrayOutputStream();
// reading the content of the file within a byte buffer
byte[] byteBuffer = new byte[8192];
int nbByteRead /* = 0*/;
try {
while ((nbByteRead = is.read(byteBuffer)) != -1) {
// appends buffer
answer.write(byteBuffer, 0, nbByteRead);
}
} finally {
closeWithWarning(is);
}
return answer.toByteArray();
}
/**
* Write the byte[] to the output stream.
* The stream is closed before this method returns.
*
* @param os an output stream
* @param bytes the byte[] to write to the output stream
* @throws IOException if an IOException occurs.
* @since 1.7.1
*/
public static void setBytes(OutputStream os, byte[] bytes) throws IOException {
try {
os.write(bytes);
} finally {
closeWithWarning(os);
}
}
/**
* Write the text and append a newline (using the platform's line-ending).
*
* @param writer a BufferedWriter
* @param line the line to write
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static void writeLine(BufferedWriter writer, String line) throws IOException {
writer.write(line);
writer.newLine();
}
/**
* Creates an iterator which will traverse through the reader a line at a time.
*
* @param self a Reader object
* @return an Iterator for the Reader
* @see java.io.BufferedReader#readLine()
* @since 1.5.0
*/
public static Iterator<String> iterator(Reader self) {
final BufferedReader bufferedReader;
if (self instanceof BufferedReader)
bufferedReader = (BufferedReader) self;
else
bufferedReader = new BufferedReader(self);
return new Iterator<String>() {
String nextVal /* = null */;
boolean nextMustRead = true;
boolean hasNext = true;
public boolean hasNext() {
if (nextMustRead && hasNext) {
try {
nextVal = readNext();
nextMustRead = false;
} catch (IOException e) {
hasNext = false;
}
}
return hasNext;
}
public String next() {
String retval = null;
if (nextMustRead) {
try {
retval = readNext();
} catch (IOException e) {
hasNext = false;
}
} else
retval = nextVal;
nextMustRead = true;
return retval;
}
private String readNext() throws IOException {
String nv = bufferedReader.readLine();
if (nv == null)
hasNext = false;
return nv;
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove() from a Reader Iterator");
}
};
}
/**
* Standard iterator for a input stream which iterates through the stream
* content in a byte-based fashion.
*
* @param self an InputStream object
* @return an Iterator for the InputStream
* @since 1.5.0
*/
public static Iterator<Byte> iterator(InputStream self) {
return iterator(new DataInputStream(self));
}
/**
* Standard iterator for a data input stream which iterates through the
* stream content a Byte at a time.
*
* @param self a DataInputStream object
* @return an Iterator for the DataInputStream
* @since 1.5.0
*/
public static Iterator<Byte> iterator(final DataInputStream self) {
return new Iterator<Byte>() {
Byte nextVal;
boolean nextMustRead = true;
boolean hasNext = true;
public boolean hasNext() {
if (nextMustRead && hasNext) {
try {
nextVal = self.readByte();
nextMustRead = false;
} catch (IOException e) {
hasNext = false;
}
}
return hasNext;
}
public Byte next() {
Byte retval = null;
if (nextMustRead) {
try {
retval = self.readByte();
} catch (IOException e) {
hasNext = false;
}
} else
retval = nextVal;
nextMustRead = true;
return retval;
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove() from a DataInputStream Iterator");
}
};
}
/**
* Creates a reader for this input stream.
*
* @param self an input stream
* @return a reader
* @since 1.0
*/
public static BufferedReader newReader(InputStream self) {
return new BufferedReader(new InputStreamReader(self));
}
/**
* Creates a reader for this input stream, using the specified
* charset as the encoding.
*
* @param self an input stream
* @param charset the charset for this input stream
* @return a reader
* @throws UnsupportedEncodingException if the encoding specified is not supported
* @since 1.6.0
*/
public static BufferedReader newReader(InputStream self, String charset) throws UnsupportedEncodingException {
return new BufferedReader(new InputStreamReader(self, charset));
}
/**
* Create a new PrintWriter for this Writer.
*
* @param writer a Writer
* @return a PrintWriter
* @since 1.6.0
*/
public static PrintWriter newPrintWriter(Writer writer) {
return new GroovyPrintWriter(writer);
}
/**
* Create a new PrintWriter for this OutputStream.
*
* @param stream an OutputStream
* @return a PrintWriter
* @since 2.2.0
*/
public static PrintWriter newPrintWriter(OutputStream stream) {
return new GroovyPrintWriter(stream);
}
/**
* Create a new PrintWriter for this Writer. The writer is passed to the
* closure, and will be closed before this method returns.
*
* @param writer a writer
* @param closure the closure to invoke with the PrintWriter
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.6.0
*/
public static <T> T withPrintWriter(Writer writer, @ClosureParams(value=SimpleType.class, options="java.io.PrintWriter") Closure<T> closure) throws IOException {
return withWriter(newPrintWriter(writer), closure);
}
/**
* Create a new PrintWriter for this OutputStream. The writer is passed to the
* closure, and will be closed before this method returns.
*
* @param stream an OutputStream
* @param closure the closure to invoke with the PrintWriter
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 2.2.0
*/
public static <T> T withPrintWriter(OutputStream stream, @ClosureParams(value=SimpleType.class, options="java.io.PrintWriter") Closure<T> closure) throws IOException {
return withWriter(newPrintWriter(stream), closure);
}
/**
* Allows this writer to be used within the closure, ensuring that it
* is flushed and closed before this method returns.
*
* @param writer the writer which is used and then closed
* @param closure the closure that the writer is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.2
*/
public static <T> T withWriter(Writer writer, @ClosureParams(FirstParam.class) Closure<T> closure) throws IOException {
try {
T result = closure.call(writer);
try {
writer.flush();
} catch (IOException e) {
// try to continue even in case of error
}
Writer temp = writer;
writer = null;
temp.close();
return result;
} finally {
closeWithWarning(writer);
}
}
/**
* Allows this reader to be used within the closure, ensuring that it
* is closed before this method returns.
*
* @param reader the reader which is used and then closed
* @param closure the closure that the writer is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.2
*/
public static <T> T withReader(Reader reader, @ClosureParams(FirstParam.class) Closure<T> closure) throws IOException {
try {
T result = closure.call(reader);
Reader temp = reader;
reader = null;
temp.close();
return result;
} finally {
closeWithWarning(reader);
}
}
/**
* Allows this input stream to be used within the closure, ensuring that it
* is flushed and closed before this method returns.
*
* @param stream the stream which is used and then closed
* @param closure the closure that the stream is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.2
*/
public static <T, U extends InputStream> T withStream(U stream, @ClosureParams(value=FirstParam.class) Closure<T> closure) throws IOException {
try {
T result = closure.call(stream);
InputStream temp = stream;
stream = null;
temp.close();
return result;
} finally {
closeWithWarning(stream);
}
}
/**
* Helper method to create a new Reader for a stream and then
* passes it into the closure. The reader (and this stream) is closed after
* the closure returns.
*
* @param in a stream
* @param closure the closure to invoke with the InputStream
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see java.io.InputStreamReader
* @since 1.5.2
*/
public static <T> T withReader(InputStream in, @ClosureParams(value=SimpleType.class, options="java.io.Reader") Closure<T> closure) throws IOException {
return withReader(new InputStreamReader(in), closure);
}
/**
* Helper method to create a new Reader for a stream and then
* passes it into the closure. The reader (and this stream) is closed after
* the closure returns.
*
* @param in a stream
* @param charset the charset used to decode the stream
* @param closure the closure to invoke with the reader
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see java.io.InputStreamReader
* @since 1.5.6
*/
public static <T> T withReader(InputStream in, String charset, @ClosureParams(value=SimpleType.class, options="java.io.Reader") Closure<T> closure) throws IOException {
return withReader(new InputStreamReader(in, charset), closure);
}
/**
* Creates a writer from this stream, passing it to the given closure.
* This method ensures the stream is closed after the closure returns.
*
* @param stream the stream which is used and then closed
* @param closure the closure that the writer is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see #withWriter(java.io.Writer, groovy.lang.Closure)
* @since 1.5.2
*/
public static <T> T withWriter(OutputStream stream, @ClosureParams(value=SimpleType.class, options="java.io.Writer") Closure<T> closure) throws IOException {
return withWriter(new OutputStreamWriter(stream), closure);
}
/**
* Creates a writer for this stream.
*
* @param stream the stream which is used and then closed
* @return the newly created Writer
* @since 2.2.0
*/
public static Writer newWriter(OutputStream stream) {
return new OutputStreamWriter(stream);
}
/**
* Creates a writer from this stream, passing it to the given closure.
* This method ensures the stream is closed after the closure returns.
*
* @param stream the stream which is used and then closed
* @param charset the charset used
* @param closure the closure that the writer is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @see #withWriter(java.io.Writer, groovy.lang.Closure)
* @since 1.5.2
*/
public static <T> T withWriter(OutputStream stream, String charset, @ClosureParams(value=SimpleType.class, options="java.io.Writer") Closure<T> closure) throws IOException {
return withWriter(new OutputStreamWriter(stream, charset), closure);
}
/**
* Creates a writer for this stream using the given charset.
*
* @param stream the stream which is used and then closed
* @param charset the charset used
* @return the newly created Writer
* @throws UnsupportedEncodingException if an encoding exception occurs.
* @since 2.2.0
*/
public static Writer newWriter(OutputStream stream, String charset) throws UnsupportedEncodingException {
return new OutputStreamWriter(stream, charset);
}
/**
* Passes this OutputStream to the closure, ensuring that the stream
* is closed after the closure returns, regardless of errors.
*
* @param os the stream which is used and then closed
* @param closure the closure that the stream is passed into
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 1.5.2
*/
public static <T, U extends OutputStream> T withStream(U os, @ClosureParams(value=FirstParam.class) Closure<T> closure) throws IOException {
try {
T result = closure.call(os);
os.flush();
OutputStream temp = os;
os = null;
temp.close();
return result;
} finally {
closeWithWarning(os);
}
}
/**
* Traverse through each byte of the specified stream. The
* stream is closed after the closure returns.
*
* @param is stream to iterate over, closed after the method call
* @param closure closure to apply to each byte
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static void eachByte(InputStream is, @ClosureParams(value=SimpleType.class, options="byte") Closure closure) throws IOException {
try {
while (true) {
int b = is.read();
if (b == -1) {
break;
} else {
closure.call((byte) b);
}
}
InputStream temp = is;
is = null;
temp.close();
} finally {
closeWithWarning(is);
}
}
/**
* Traverse through each the specified stream reading bytes into a buffer
* and calling the 2 parameter closure with this buffer and the number of bytes.
*
* @param is stream to iterate over, closed after the method call.
* @param bufferLen the length of the buffer to use.
* @param closure a 2 parameter closure which is passed the byte[] and a number of bytes successfully read.
* @throws IOException if an IOException occurs.
* @since 1.8
*/
public static void eachByte(InputStream is, int bufferLen, @ClosureParams(value=FromString.class, options="byte[],Integer") Closure closure) throws IOException {
byte[] buffer = new byte[bufferLen];
int bytesRead;
try {
while ((bytesRead = is.read(buffer, 0, bufferLen)) > 0) {
closure.call(buffer, bytesRead);
}
InputStream temp = is;
is = null;
temp.close();
} finally {
closeWithWarning(is);
}
}
/**
* Transforms each character from this reader by passing it to the given
* closure. The Closure should return each transformed character, which
* will be passed to the Writer. The reader and writer will both be
* closed before this method returns.
*
* @param self a Reader object
* @param writer a Writer to receive the transformed characters
* @param closure a closure that performs the required transformation
* @throws IOException if an IOException occurs.
* @since 1.5.0
*/
public static void transformChar(Reader self, Writer writer, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure closure) throws IOException {
int c;
try {
char[] chars = new char[1];
while ((c = self.read()) != -1) {
chars[0] = (char) c;
Object o = closure.call(new String(chars));
if (o != null) {
writer.write(o.toString());
}
}
writer.flush();
Writer temp2 = writer;
writer = null;
temp2.close();
Reader temp1 = self;
self = null;
temp1.close();
} finally {
closeWithWarning(self);
closeWithWarning(writer);
}
}
/**
* Transforms the lines from a reader with a Closure and
* write them to a writer. Both Reader and Writer are
* closed after the operation.
*
* @param reader Lines of text to be transformed. Reader is closed afterwards.
* @param writer Where transformed lines are written. Writer is closed afterwards.
* @param closure Single parameter closure that is called to transform each line of
* text from the reader, before writing it to the writer.
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static void transformLine(Reader reader, Writer writer, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure closure) throws IOException {
BufferedReader br = new BufferedReader(reader);
BufferedWriter bw = new BufferedWriter(writer);
String line;
try {
while ((line = br.readLine()) != null) {
Object o = closure.call(line);
if (o != null) {
bw.write(o.toString());
bw.newLine();
}
}
bw.flush();
Writer temp2 = writer;
writer = null;
temp2.close();
Reader temp1 = reader;
reader = null;
temp1.close();
} finally {
closeWithWarning(br);
closeWithWarning(reader);
closeWithWarning(bw);
closeWithWarning(writer);
}
}
/**
* Filter the lines from a reader and write them on the writer,
* according to a closure which returns true if the line should be included.
* Both Reader and Writer are closed after the operation.
*
* @param reader a reader, closed after the call
* @param writer a writer, closed after the call
* @param closure the closure which returns booleans
* @throws IOException if an IOException occurs.
* @since 1.0
*/
public static void filterLine(Reader reader, Writer writer, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure closure) throws IOException {
BufferedReader br = new BufferedReader(reader);
BufferedWriter bw = new BufferedWriter(writer);
String line;
try {
BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure);
while ((line = br.readLine()) != null) {
if (bcw.call(line)) {
bw.write(line);
bw.newLine();
}
}
bw.flush();
Writer temp2 = writer;
writer = null;
temp2.close();
Reader temp1 = reader;
reader = null;
temp1.close();
} finally {
closeWithWarning(br);
closeWithWarning(reader);
closeWithWarning(bw);
closeWithWarning(writer);
}
}
/**
* Filter the lines from this Reader, and return a Writable which can be
* used to stream the filtered lines to a destination. The closure should
* return <code>true</code> if the line should be passed to the writer.
*
* @param reader this reader
* @param closure a closure used for filtering
* @return a Writable which will use the closure to filter each line
* from the reader when the Writable#writeTo(Writer) is called.
* @since 1.0
*/
public static Writable filterLine(Reader reader, @ClosureParams(value=SimpleType.class, options="java.lang.String") final Closure closure) {
final BufferedReader br = new BufferedReader(reader);
return new Writable() {
public Writer writeTo(Writer out) throws IOException {
BufferedWriter bw = new BufferedWriter(out);
String line;
BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure);
while ((line = br.readLine()) != null) {
if (bcw.call(line)) {
bw.write(line);
bw.newLine();
}
}
bw.flush();
return out;
}
public String toString() {
Writer buffer = new StringBuilderWriter();
try {
writeTo(buffer);
} catch (IOException e) {
throw new StringWriterIOException(e);
}
return buffer.toString();
}
};
}
/**
* Filter lines from an input stream using a closure predicate. The closure
* will be passed each line as a String, and it should return
* <code>true</code> if the line should be passed to the writer.
*
* @param self an input stream
* @param predicate a closure which returns boolean and takes a line
* @return a writable which writes out the filtered lines
* @see #filterLine(java.io.Reader, groovy.lang.Closure)
* @since 1.0
*/
public static Writable filterLine(InputStream self, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure predicate) {
return filterLine(newReader(self), predicate);
}
/**
* Filter lines from an input stream using a closure predicate. The closure
* will be passed each line as a String, and it should return
* <code>true</code> if the line should be passed to the writer.
*
* @param self an input stream
* @param charset opens the stream with a specified charset
* @param predicate a closure which returns boolean and takes a line
* @return a writable which writes out the filtered lines
* @throws UnsupportedEncodingException if the encoding specified is not supported
* @see #filterLine(java.io.Reader, groovy.lang.Closure)
* @since 1.6.8
*/
public static Writable filterLine(InputStream self, String charset, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure predicate)
throws UnsupportedEncodingException {
return filterLine(newReader(self, charset), predicate);
}
/**
* Uses a closure to filter lines from this InputStream and pass them to
* the given writer. The closure will be passed each line as a String, and
* it should return <code>true</code> if the line should be passed to the
* writer.
*
* @param self the InputStream
* @param writer a writer to write output to
* @param predicate a closure which returns true if a line should be accepted
* @throws IOException if an IOException occurs.
* @see #filterLine(java.io.Reader, java.io.Writer, groovy.lang.Closure)
* @since 1.0
*/
public static void filterLine(InputStream self, Writer writer, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure predicate)
throws IOException {
filterLine(newReader(self), writer, predicate);
}
/**
* Uses a closure to filter lines from this InputStream and pass them to
* the given writer. The closure will be passed each line as a String, and
* it should return <code>true</code> if the line should be passed to the
* writer.
*
* @param self the InputStream
* @param writer a writer to write output to
* @param charset opens the stream with a specified charset
* @param predicate a closure which returns true if a line should be accepted
* @throws IOException if an IOException occurs.
* @see #filterLine(java.io.Reader, java.io.Writer, groovy.lang.Closure)
* @since 1.6.8
*/
public static void filterLine(InputStream self, Writer writer, String charset, @ClosureParams(value=SimpleType.class, options="java.lang.String") Closure predicate)
throws IOException {
filterLine(newReader(self, charset), writer, predicate);
}
/**
* Allows this closeable to be used within the closure, ensuring that it
* is closed once the closure has been executed and before this method returns.
* <p>
* As with the try-with-resources statement, if multiple exceptions are thrown
* the exception from the closure will be returned and the exception from closing
* will be added as a suppressed exception.
*
* @param self the Closeable
* @param action the closure taking the Closeable as parameter
* @return the value returned by the closure
* @throws IOException if an IOException occurs.
* @since 2.4.0
*/
public static <T, U extends Closeable> T withCloseable(U self, @ClosureParams(value=FirstParam.class) Closure<T> action) throws IOException {
Throwable thrown = null;
try {
return action.call(self);
} catch (Throwable e) {
thrown = e;
throw e;
} finally {
if (thrown != null) {
Throwable suppressed = tryClose(self, true);
if (suppressed != null) {
thrown.addSuppressed(suppressed);
}
} else {
self.close();
}
}
}
/**
* Allows this AutoCloseable to be used within the closure, ensuring that it
* is closed once the closure has been executed and before this method returns.
* <p>
* As with the try-with-resources statement, if multiple exceptions are thrown
* the exception from the closure will be returned and the exception from closing
* will be added as a suppressed exception.
*
* @param self the AutoCloseable
* @param action the closure taking the AutoCloseable as parameter
* @return the value returned by the closure
* @throws Exception if an Exception occurs.
* @since 2.5.0
*/
public static <T, U extends AutoCloseable> T withCloseable(U self, @ClosureParams(value=FirstParam.class) Closure<T> action) throws Exception {
Throwable thrown = null;
try {
return action.call(self);
} catch (Throwable e) {
thrown = e;
throw e;
} finally {
if (thrown != null) {
Throwable suppressed = tryClose(self, true);
if (suppressed != null) {
thrown.addSuppressed(suppressed);
}
} else {
self.close();
}
}
}
private static final int DEFAULT_BUFFER_SIZE = 8192; // 8k
}