/* RepeatableReader.java Purpose: Description: History: Fri Mar 14 11:47:38 2008, Created by tomyeh Copyright (C) 2008 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.io; import java.io.CharArrayReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.net.URL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.lang.Library; /** * {@link RepeatableReader} adds functionality to another reader, * the ability to read repeatedly. * By repeatable-read we mean, after {@link #close}, the next invocation of * {@link #read} will re-open the reader. * * <p>{@link RepeatableInputStream} actually creates a temporary space * to buffer the content, so it can be re-opened again after closed. * Notice that the temporary space (a.k.a., the buffered reader) * is never closed until garbage-collected. * * <p>If the content size of the given reader is smaller than * the value specified in the system property called * "org.zkoss.io.memoryLimitSize", the content will be buffered in * the memory. If the size exceeds, the content will be buffered in * a temporary file. By default, it is 512KB. * Note: the maximal value is {@link Integer#MAX_VALUE} * * <p>If the content size of the given reader is larger than * the value specified in the system property called * "org.zkoss.io.bufferLimitSize", the content won't be buffered, * and it means the read is not repeatable. By default, it is 20MB. * Note: the maximal value is {@link Integer#MAX_VALUE} * * @author tomyeh * @since 3.0.4 */ public class RepeatableReader extends Reader implements Repeatable, Serializable { private static final Logger log = LoggerFactory.getLogger(RepeatableReader.class); private Reader _org; private Writer _out; private Reader _in; private File _f; /** The content size. It is meaningful only if !_nobuf. * Note: int is enough (since long makes no sense for buffering) */ private int _cntsz; private final int _bufmaxsz, _memmaxsz; private boolean _nobuf; private RepeatableReader(Reader is) { _org = is; _bufmaxsz = Library.getIntProperty( RepeatableInputStream.BUFFER_LIMIT_SIZE, 20 * 1024 * 1024); _memmaxsz = Library.getIntProperty( RepeatableInputStream.MEMORY_LIMIT_SIZE, 512 * 1024); } /** * Returns a reader that can be read repeatedly, or null if the given * reader is null. * Note: the returned reader encapsulates the given reader, rd * (a.k.a., the buffered reader) to adds the functionality to * re-opens the reader once {@link #close} is called. * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Use this method instead of instantiating {@link RepeatableReader} * with the constructor. * * @see #getInstance(File) */ public static Reader getInstance(Reader rd) { if ((rd instanceof CharArrayReader) || (rd instanceof StringReader)) return new ResetableReader(rd); else if (rd != null && !(rd instanceof Repeatable)) return new RepeatableReader(rd); return rd; } /** * Returns a reader to read a file that can be read repeatedly. * Note: it assumes the file is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @param charset the charset. If null, "UTF-8" is assumed. * @exception IllegalArgumentException if file is null. * @see #getInstance(Reader) * @see #getInstance(String, String) * @since 3.0.8 */ public static Reader getInstance(File file, String charset) throws FileNotFoundException { if (file == null) throw new IllegalArgumentException("null"); if (!file.exists()) throw new FileNotFoundException(file.toString()); return new RepeatableFileReader(file, charset); } /** * Returns a reader to read a file, encoded in UTF-8, * that can be read repeatedly. * Note: it assumes the file is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @exception IllegalArgumentException if file is null. * @see #getInstance(Reader) * @see #getInstance(String) */ public static Reader getInstance(File file) throws FileNotFoundException { return getInstance(file, "UTF-8"); } /** * Returns a reader to read a file that can be read repeatedly. * Note: it assumes the file is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @param filename the file name * @param charset the charset. If null, "UTF-8" is assumed. * @exception IllegalArgumentException if file is null. * @exception FileNotFoundException if file is not found. * @see #getInstance(Reader) * @see #getInstance(File, String) * @since 3.0.8 */ public static Reader getInstance(String filename, String charset) throws FileNotFoundException { return getInstance(new File(filename)); } /** * Returns a reader to read a file, encoded in UTF-8, * that can be read repeatedly. * Note: it assumes the file is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @param filename the file name * @exception IllegalArgumentException if file is null. * @exception FileNotFoundException if file is not found. * @see #getInstance(Reader) * @see #getInstance(File) */ public static Reader getInstance(String filename) throws FileNotFoundException { return getInstance(new File(filename), "UTF-8"); } /** * Returns a reader to read the resource of the specified URL. * The reader can be read repeatedly. * Note: it assumes the resource is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @param charset the charset. If null, "UTF-8" is assumed. * @exception IllegalArgumentException if file is null. * @see #getInstance(Reader) * @see #getInstance(String, String) * @since 3.0.8 */ public static Reader getInstance(URL url, String charset) { if (url == null) throw new IllegalArgumentException("null"); return new RepeatableURLReader(url, charset); } /** * Returns a reader to read the resource of the specified URL, * encoded in UTF-8. * The reader can be read repeatedly. * Note: it assumes the resource is text (rather than binary). * * <p>By repeatable-read we mean, after {@link #close}, the next * invocation of {@link #read} will re-open the reader. * * <p>Note: it is efficient since we don't have to buffer the * content of the file to make it repeatable-read. * * @exception IllegalArgumentException if file is null. * @see #getInstance(Reader) * @see #getInstance(String) */ public static Reader getInstance(URL url) { return getInstance(url, "UTF-8"); } private Writer getWriter() throws IOException { if (_out == null) return _nobuf ? null: (_out = new StringWriter()); //it is possible _membufsz <= 0, but OK to use memory first if (_cntsz >= _bufmaxsz) { //too large to buffer disableBuffering(); return null; } if (_f == null && _cntsz >= _memmaxsz) { //memory to file try { final File f = new File(System.getProperty("java.io.tmpdir"), "zk"); if (!f.isDirectory()) f.mkdir(); _f = File.createTempFile("zk.io", ".zk.io", f); final String cnt = ((StringWriter)_out).toString(); _out = new FileWriter(_f, "UTF-8"); _out.write(cnt); } catch (Throwable ex) { log.warn("Ignored: failed to buffer to a file, "+_f+"\nCause: "+ex.getMessage()); disableBuffering(); } } return _out; } private void disableBuffering() { _nobuf = true; if (_out != null) { try { _out.close(); } catch (Throwable ex) { //ignore } _out = null; } if (_f != null) { try { _f.delete(); } catch (Throwable ex) { //ignore } _f = null; } } public int read(char cbuf[], int off, int len) throws IOException { if (_org != null) { final int cnt = _org.read(cbuf, off, len); if (!_nobuf) if (cnt >= 0) { final Writer out = getWriter(); if (out != null) out.write(cbuf, off, cnt); _cntsz += cnt; } return cnt; } else { if (_in == null) _in = new FileReader(_f, "UTF-8"); //_f must be non-null return _in.read(cbuf, off, len); } } /** Closes the current access, and the next call of {@link #read} * re-opens the buffered reader. */ public void close() throws IOException { _cntsz = 0; if (_org != null) { _org.close(); if (_out != null) { try { _out.close(); } catch (Throwable ex) { log.warn("Ignored: failed to close the buffer.\nCause: "+ex.getMessage()); disableBuffering(); return; } if (_f == null) _in = new StringReader( ((StringWriter)_out).toString()); //we don't initialize _in if _f is not null //to reduce memory use (after all, read might not be called) _out = null; _org = null; } } else if (_in != null) { if (_f != null) { _in.close(); _in = null; } else { _in.reset(); } } } //Object// protected void finalize() throws Throwable { disableBuffering(); if (_org != null) _org.close(); if (_in != null) { _in.close(); _in = null; } super.finalize(); } private static class ResetableReader extends Reader implements Repeatable, Serializable { private final Reader _org; ResetableReader(Reader bais) { _org = bais; } public int read(char cbuf[], int off, int len) throws IOException { return _org.read(cbuf, off, len); } /** Closes the current access and the next call of {@link #read} * re-opens the buffered reader. */ public void close() throws IOException { _org.reset(); } //Object// protected void finalize() throws Throwable { _org.close(); super.finalize(); } } private static class RepeatableFileReader extends Reader implements Repeatable, Serializable { private final File _file; private Reader _in; private final String _charset; RepeatableFileReader(File file, String charset) { _file = file; _charset = charset != null ? charset: "UTF-8"; } public int read(char cbuf[], int off, int len) throws IOException { if (_in == null) _in = new FileReader(_file, _charset); return _in.read(cbuf, off, len); } /** Closes the current access and the next call of {@link #read} * re-opens the buffered reader. */ public void close() throws IOException { if (_in != null) { _in.close(); _in = null; } } //Object// protected void finalize() throws Throwable { close(); super.finalize(); } } private static class RepeatableURLReader extends Reader implements Repeatable, Serializable { private final URL _url; private Reader _in; private final String _charset; RepeatableURLReader(URL url, String charset) { _url = url; _charset = charset != null ? charset: "UTF-8"; } public int read(char cbuf[], int off, int len) throws IOException { if (_in == null) _in = new URLReader(_url, _charset); return _in.read(cbuf, off, len); } /** Closes the current access and the next call of {@link #read} * re-opens the buffered reader. */ public void close() throws IOException { if (_in != null) { _in.close(); _in = null; } } //Object// protected void finalize() throws Throwable { close(); super.finalize(); } } }