/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.util; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; /** * This class wraps a Reader with the InputStream API. It is used internally * by the JNode shell, and is not recommended for general use. * * @author crawley@jnode.org */ public class ReaderInputStream extends InputStream { private final Reader reader; private CharBuffer chars = CharBuffer.allocate(1024); private ByteBuffer bytes = ByteBuffer.allocate(2048); private CharsetEncoder encoder; private CoderResult cr; public ReaderInputStream(Reader reader) { this(reader, Charset.defaultCharset().name()); } public ReaderInputStream(Reader reader, String encoding) { this.reader = reader; this.encoder = Charset.forName(encoding).newEncoder(); this.bytes.position(bytes.limit()); this.chars.position(chars.limit()); } @Override public synchronized int read() throws IOException { if (bytes.remaining() == 0) { if (fillBuffer(true) == -1) { return -1; } } return bytes.get(); } @Override public synchronized int read(byte[] b, int off, int len) throws IOException { if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) { throw new IndexOutOfBoundsException(); } // This implementation is simple-minded. I'm sure we could recode it to avoid // the 'bytes.get' copying step if we thought about it. int count = 0; do { if (bytes.remaining() == 0) { int nosRead = fillBuffer(count == 0); if (nosRead <= 0) { return count > 0 ? count : -1; } } int toCopy = Math.min(bytes.remaining(), len); bytes.get(b, off, toCopy); count += toCopy; len -= toCopy; off += toCopy; } while (count < len); return count; } @Override public int read(byte[] b) throws IOException { return this.read(b, 0, b.length); } /** * This method puts bytes into the (empty) 'bytes' buffer. It returns * <code>false</code> if no bytes were copied either because the reader * would have blocked or because it returned <code>-1</code>. * * @param wait if <code>true</code> allow the reader to block. * @return the number of bytes added; <code>-1</code> if none were added * and the reader is at the EOF. * @throws IOException */ private int fillBuffer(boolean wait) throws IOException { // If there was a coder error left over from the last call, process // it now. resetAndThrowOnError(); bytes.clear(); // The loop is necessary because of the way that an encoder has to deal // with UTF-16 surrogate pairs. int count; do { if (chars.remaining() == 0 || cr == CoderResult.UNDERFLOW) { if (chars.remaining() == 0) { if (!reader.ready() && !wait) { bytes.flip(); return 0; } chars.clear(); } else { char[] tmp = new char[chars.remaining()]; chars.get(tmp); chars.clear(); chars.put(tmp); } if (reader.read(chars) == -1) { chars.flip(); cr = encoder.encode(chars, bytes, true); count = bytes.position(); if (count == 0) { // Only report errors now if we didn't manage to encode anything resetAndThrowOnError(); } bytes.flip(); return count > 0 ? count : -1; } chars.flip(); } cr = encoder.encode(chars, bytes, false); count = bytes.position(); if (count == 0) { // Only report errors now if we didn't manage to encode anything resetAndThrowOnError(); } } while (wait && count == 0); bytes.flip(); return count; } private void resetAndThrowOnError() throws CharacterCodingException { // Reset the encoder so that it will work next time we try to use it. encoder.reset(); if (cr != null && cr.isError()) { // Skip over the problem characters for (int i = 0; i < cr.length(); i++) { chars.get(); } // Clear the coder result CoderResult tmp = cr; cr = null; // ... and report the error using the appropriate exception. tmp.throwException(); } } public Reader getReader() { return reader; } }