1 /* ChannelReader.java -- 2 Copyright (C) 2005 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.nio; 40 41 import java.io.IOException; 42 import java.io.Reader; 43 import java.nio.ByteBuffer; 44 import java.nio.CharBuffer; 45 import java.nio.channels.ReadableByteChannel; 46 import java.nio.charset.CharsetDecoder; 47 import java.nio.charset.CoderResult; 48 import java.nio.charset.CodingErrorAction; 49 50 /** 51 * A Reader implementation that works using a ReadableByteChannel and a 52 * CharsetDecoder. 53 * 54 * <p> 55 * This is a bridge between NIO <-> IO character decoding. 56 * </p> 57 * 58 * @author Robert Schuster 59 */ 60 public class ChannelReader extends Reader 61 { 62 63 private static final int DEFAULT_BUFFER_CAP = 8192; 64 65 private ReadableByteChannel channel; 66 67 private CharsetDecoder decoder; 68 69 private ByteBuffer byteBuffer; 70 71 private CharBuffer charBuffer; 72 ChannelReader(ReadableByteChannel channel, CharsetDecoder decoder, int minBufferCap)73 public ChannelReader(ReadableByteChannel channel, CharsetDecoder decoder, 74 int minBufferCap) 75 { 76 this.channel = channel; 77 this.decoder = decoder; 78 79 // JDK reports errors, so we do the same. 80 decoder.onMalformedInput(CodingErrorAction.REPORT); 81 decoder.onUnmappableCharacter(CodingErrorAction.REPORT); 82 decoder.reset(); 83 84 int size = (minBufferCap == -1) ? DEFAULT_BUFFER_CAP : minBufferCap; 85 86 // Allocates the buffers and prepares them for reading, because that is the 87 // first operation being done on them. 88 byteBuffer = ByteBuffer.allocate(size); 89 byteBuffer.flip(); 90 charBuffer = CharBuffer.allocate((int) (size * decoder.averageCharsPerByte())); 91 } 92 read(char[] buf, int offset, int count)93 public int read(char[] buf, int offset, int count) throws IOException 94 { 95 synchronized (lock) 96 { 97 // I declared channel being null meaning that the reader is closed. 98 if (!channel.isOpen()) 99 throw new IOException("Reader was already closed."); 100 101 // I declared decoder being null meaning that there is no more data to read 102 // and convert. 103 if (decoder == null) 104 return -1; 105 106 // Stores the amount of character being read. It -1 so that if no conversion 107 // occured the caller will see this as an 'end of file'. 108 int sum = -1; 109 110 // Copies any characters which may be left from the last invocation into the 111 // destination array. 112 if (charBuffer.remaining() > 0) 113 { 114 sum = Math.min(count, charBuffer.remaining()); 115 charBuffer.get(buf, offset, sum); 116 117 // Updates the control variables according to the latest copy operation. 118 offset += sum; 119 count -= sum; 120 } 121 122 // Copies the character which have not been put in the destination array to 123 // the beginning. If data is actually copied count will be 0. If no data is 124 // copied count is >0 and we can now convert some more characters. 125 charBuffer.compact(); 126 127 int converted = 0; 128 boolean last = false; 129 130 while (count != 0) 131 { 132 // Tries to convert some bytes (Which will intentionally fail in the 133 // first place because we have not read any bytes yet.) 134 CoderResult result = decoder.decode(byteBuffer, charBuffer, last); 135 if (result.isMalformed() || result.isUnmappable()) 136 { 137 // JDK throws exception when bytes are malformed for sure. 138 // FIXME: Unsure what happens when a character is simply 139 // unmappable. 140 result.throwException(); 141 } 142 143 // Marks that we should end this loop regardless whether the caller 144 // wants more chars or not, when this was the last conversion. 145 if (last) 146 { 147 decoder = null; 148 } 149 else if (result.isUnderflow()) 150 { 151 // We need more bytes to do the conversion. 152 153 // Copies the not yet converted bytes to the beginning making it 154 // being able to receive more bytes. 155 byteBuffer.compact(); 156 157 // Reads in another bunch of bytes for being converted. 158 if (channel.read(byteBuffer) == -1) 159 { 160 // If there is no more data available in the channel we mark 161 // that state for the final character conversion run which is 162 // done in the next loop iteration. 163 last = true; 164 } 165 166 // Prepares the byteBuffer for the next character conversion run. 167 byteBuffer.flip(); 168 } 169 170 // Prepares the charBuffer for being drained. 171 charBuffer.flip(); 172 173 converted = Math.min(count, charBuffer.remaining()); 174 charBuffer.get(buf, offset, converted); 175 176 // Copies characters which have not yet being copied into the char-Array 177 // to the beginning making it possible to read them later (If data is 178 // really copied here, then the caller has received enough characters so 179 // far.). 180 charBuffer.compact(); 181 182 // Updates the control variables according to the latest copy operation. 183 offset += converted; 184 count -= converted; 185 186 // Updates the amount of transferred characters. 187 sum += converted; 188 189 if (decoder == null) 190 { 191 break; 192 } 193 194 // Now that more characters have been transfered we let the loop decide 195 // what to do next. 196 } 197 198 // Makes the charBuffer ready for reading on the next invocation. 199 charBuffer.flip(); 200 201 return sum; 202 } 203 } 204 close()205 public void close() throws IOException 206 { 207 synchronized (lock) 208 { 209 channel.close(); 210 211 // Makes sure all intermediate data is released by the decoder. 212 if (decoder != null) 213 decoder.reset(); 214 } 215 } 216 217 } 218