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