| /* ChannelReader.java -- |
| Copyright (C) 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.java.nio; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.channels.ReadableByteChannel; |
| import java.nio.charset.CharsetDecoder; |
| import java.nio.charset.CoderResult; |
| import java.nio.charset.CodingErrorAction; |
| |
| /** |
| * A Reader implementation that works using a ReadableByteChannel and a |
| * CharsetDecoder. |
| * |
| * <p> |
| * This is a bridge between NIO <-> IO character decoding. |
| * </p> |
| * |
| * @author Robert Schuster |
| */ |
| public class ChannelReader extends Reader |
| { |
| |
| private static final int DEFAULT_BUFFER_CAP = 8192; |
| |
| private ReadableByteChannel channel; |
| |
| private CharsetDecoder decoder; |
| |
| private ByteBuffer byteBuffer; |
| |
| private CharBuffer charBuffer; |
| |
| public ChannelReader(ReadableByteChannel channel, CharsetDecoder decoder, |
| int minBufferCap) |
| { |
| this.channel = channel; |
| this.decoder = decoder; |
| |
| // JDK reports errors, so we do the same. |
| decoder.onMalformedInput(CodingErrorAction.REPORT); |
| decoder.onUnmappableCharacter(CodingErrorAction.REPORT); |
| decoder.reset(); |
| |
| int size = (minBufferCap == -1) ? DEFAULT_BUFFER_CAP : minBufferCap; |
| |
| // Allocates the buffers and prepares them for reading, because that is the |
| // first operation being done on them. |
| byteBuffer = ByteBuffer.allocate(size); |
| byteBuffer.flip(); |
| charBuffer = CharBuffer.allocate((int) (size * decoder.averageCharsPerByte())); |
| } |
| |
| public int read(char[] buf, int offset, int count) throws IOException |
| { |
| synchronized (lock) |
| { |
| // I declared channel being null meaning that the reader is closed. |
| if (!channel.isOpen()) |
| throw new IOException("Reader was already closed."); |
| |
| // I declared decoder being null meaning that there is no more data to read |
| // and convert. |
| if (decoder == null) |
| return -1; |
| |
| // Stores the amount of character being read. It -1 so that if no conversion |
| // occured the caller will see this as an 'end of file'. |
| int sum = -1; |
| |
| // Copies any characters which may be left from the last invocation into the |
| // destination array. |
| if (charBuffer.remaining() > 0) |
| { |
| sum = Math.min(count, charBuffer.remaining()); |
| charBuffer.get(buf, offset, sum); |
| |
| // Updates the control variables according to the latest copy operation. |
| offset += sum; |
| count -= sum; |
| } |
| |
| // Copies the character which have not been put in the destination array to |
| // the beginning. If data is actually copied count will be 0. If no data is |
| // copied count is >0 and we can now convert some more characters. |
| charBuffer.compact(); |
| |
| int converted = 0; |
| boolean last = false; |
| |
| while (count != 0) |
| { |
| // Tries to convert some bytes (Which will intentionally fail in the |
| // first place because we have not read any bytes yet.) |
| CoderResult result = decoder.decode(byteBuffer, charBuffer, last); |
| if (result.isMalformed() || result.isUnmappable()) |
| { |
| // JDK throws exception when bytes are malformed for sure. |
| // FIXME: Unsure what happens when a character is simply |
| // unmappable. |
| result.throwException(); |
| } |
| |
| // Marks that we should end this loop regardless whether the caller |
| // wants more chars or not, when this was the last conversion. |
| if (last) |
| { |
| decoder = null; |
| } |
| else if (result.isUnderflow()) |
| { |
| // We need more bytes to do the conversion. |
| |
| // Copies the not yet converted bytes to the beginning making it |
| // being able to receive more bytes. |
| byteBuffer.compact(); |
| |
| // Reads in another bunch of bytes for being converted. |
| if (channel.read(byteBuffer) == -1) |
| { |
| // If there is no more data available in the channel we mark |
| // that state for the final character conversion run which is |
| // done in the next loop iteration. |
| last = true; |
| } |
| |
| // Prepares the byteBuffer for the next character conversion run. |
| byteBuffer.flip(); |
| } |
| |
| // Prepares the charBuffer for being drained. |
| charBuffer.flip(); |
| |
| converted = Math.min(count, charBuffer.remaining()); |
| charBuffer.get(buf, offset, converted); |
| |
| // Copies characters which have not yet being copied into the char-Array |
| // to the beginning making it possible to read them later (If data is |
| // really copied here, then the caller has received enough characters so |
| // far.). |
| charBuffer.compact(); |
| |
| // Updates the control variables according to the latest copy operation. |
| offset += converted; |
| count -= converted; |
| |
| // Updates the amount of transferred characters. |
| sum += converted; |
| |
| if (decoder == null) |
| { |
| break; |
| } |
| |
| // Now that more characters have been transfered we let the loop decide |
| // what to do next. |
| } |
| |
| // Makes the charBuffer ready for reading on the next invocation. |
| charBuffer.flip(); |
| |
| return sum; |
| } |
| } |
| |
| public void close() throws IOException |
| { |
| synchronized (lock) |
| { |
| channel.close(); |
| |
| // Makes sure all intermediate data is released by the decoder. |
| if (decoder != null) |
| decoder.reset(); |
| } |
| } |
| |
| } |