| /* 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(); | |
| } | |
| } | |
| } |