1 /* CharsetDecoder.java --
2 Copyright (C) 2002 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 package java.nio.charset;
39
40 import java.nio.ByteBuffer;
41 import java.nio.CharBuffer;
42
43 /**
44 * @author Jesse Rosenstock
45 * @since 1.4
46 */
47 public abstract class CharsetDecoder
48 {
49 private static final int STATE_RESET = 0;
50 private static final int STATE_CODING = 1;
51 private static final int STATE_END = 2;
52 private static final int STATE_FLUSHED = 3;
53
54 private static final String DEFAULT_REPLACEMENT = "\uFFFD";
55
56 private final Charset charset;
57 private final float averageCharsPerByte;
58 private final float maxCharsPerByte;
59 private String replacement;
60
61 private int state = STATE_RESET;
62
63 private CodingErrorAction malformedInputAction
64 = CodingErrorAction.REPORT;
65 private CodingErrorAction unmappableCharacterAction
66 = CodingErrorAction.REPORT;
67
68 private CharsetDecoder (Charset cs, float averageCharsPerByte,
69 float maxCharsPerByte, String replacement)
70 {
71 if (averageCharsPerByte <= 0.0f)
72 throw new IllegalArgumentException ("Non-positive averageCharsPerByte");
73 if (maxCharsPerByte <= 0.0f)
74 throw new IllegalArgumentException ("Non-positive maxCharsPerByte");
75
76 this.charset = cs;
77 this.averageCharsPerByte
78 = averageCharsPerByte;
79 this.maxCharsPerByte
80 = maxCharsPerByte;
81 this.replacement = replacement;
82 implReplaceWith (replacement);
83 }
84
85 protected CharsetDecoder (Charset cs, float averageCharsPerByte,
86 float maxCharsPerByte)
87 {
88 this (cs, averageCharsPerByte, maxCharsPerByte, DEFAULT_REPLACEMENT);
89 }
90
91 public final float averageCharsPerByte ()
92 {
93 return averageCharsPerByte;
94 }
95
96 public final Charset charset ()
97 {
98 return charset;
99 }
100
101 public final CharBuffer decode (ByteBuffer in)
102 throws CharacterCodingException
103 {
104 // XXX: Sun's Javadoc seems to contradict itself saying an
105 // IllegalStateException is thrown "if a decoding operation is already
106 // in progress" and also that "it resets this Decoder".
107 // Should we check to see that the state is reset, or should we
108 // call reset()?
109 if (state != STATE_RESET)
110 throw new IllegalStateException ();
111
112 // REVIEW: Using max instead of average may allocate a very large
113 // buffer. Maybe we should do something more efficient?
114 int remaining = in.remaining ();
115 int n = (int) (remaining * maxCharsPerByte ());
116 CharBuffer out = CharBuffer.allocate (n);
117
118 if (remaining == 0)
119 {
120 state = STATE_FLUSHED;
121 return out;
122 }
123
124 CoderResult cr = decode (in, out, true);
125 if (cr.isError ())
126 cr.throwException ();
127
128 cr = flush (out);
129 if (cr.isError ())
130 cr.throwException ();
131
132 reset();
133 out.flip ();
134
135 // Unfortunately, resizing the actual charbuffer array is required.
136 char[] resized = new char[out.remaining()];
137 out.get(resized);
138 return CharBuffer.wrap(resized);
139 }
140
141 public final CoderResult decode (ByteBuffer in, CharBuffer out,
142 boolean endOfInput)
143 {
144 int newState = endOfInput ? STATE_END : STATE_CODING;
145 // XXX: Need to check for "previous step was an invocation [not] of
146 // this method with a value of true for the endOfInput parameter but
147 // a return value indicating an incomplete decoding operation"
148 // XXX: We will not check the previous return value, just
149 // that the previous call passed true for endOfInput
150 if (state != STATE_RESET && state != STATE_CODING
151 && !(endOfInput && state == STATE_END))
152 throw new IllegalStateException ();
153 state = newState;
154
155 for (;;)
156 {
157 CoderResult cr;
158 try
159 {
160 cr = decodeLoop (in, out);
161 }
162 catch (RuntimeException e)
163 {
164 throw new CoderMalfunctionError (e);
165 }
166
167 if (cr.isOverflow ())
168 return cr;
169
170 if (cr.isUnderflow ())
171 {
172 if (endOfInput && in.hasRemaining ())
173 cr = CoderResult.malformedForLength (in.remaining ());
174 else
175 return cr;
176 }
177
178 CodingErrorAction action = cr.isMalformed ()
179 ? malformedInputAction
180 : unmappableCharacterAction;
181
182 if (action == CodingErrorAction.REPORT)
183 return cr;
184
185 if (action == CodingErrorAction.REPLACE)
186 {
187 if (out.remaining () < replacement.length ())
188 return CoderResult.OVERFLOW;
189 out.put (replacement);
190 }
191
192 in.position (in.position () + cr.length ());
193 }
194 }
195
196 protected abstract CoderResult decodeLoop (ByteBuffer in, CharBuffer out);
197
198 public Charset detectedCharset ()
199 {
200 throw new UnsupportedOperationException ();
201 }
202
203 public final CoderResult flush (CharBuffer out)
204 {
205 // It seems weird that you can flush after reset, but Sun's javadoc
206 // says an IllegalStateException is thrown "If the previous step of the
207 // current decoding operation was an invocation neither of the reset
208 // method nor ... of the three-argument decode method with a value of
209 // true for the endOfInput parameter."
210 // Further note that flush() only requires that there not be
211 // an IllegalStateException if the previous step was a call to
212 // decode with true as the last argument. It does not require
213 // that the call succeeded. decode() does require that it succeeded.
214 // XXX: test this to see if reality matches javadoc
215 if (state != STATE_RESET && state != STATE_END)
216 throw new IllegalStateException ();
217
218 state = STATE_FLUSHED;
219 return implFlush (out);
220 }
221
222 protected CoderResult implFlush (CharBuffer out)
223 {
224 return CoderResult.UNDERFLOW;
225 }
226
227 public final CharsetDecoder onMalformedInput (CodingErrorAction newAction)
228 {
229 if (newAction == null)
230 throw new IllegalArgumentException ("Null action");
231
232 malformedInputAction = newAction;
233 implOnMalformedInput (newAction);
234 return this;
235 }
236
237 protected void implOnMalformedInput (CodingErrorAction newAction)
238 {
239 // default implementation does nothing
240 }
241
242 protected void implOnUnmappableCharacter (CodingErrorAction newAction)
243 {
244 // default implementation does nothing
245 }
246
247 protected void implReplaceWith (String newReplacement)
248 {
249 // default implementation does nothing
250 }
251
252 protected void implReset ()
253 {
254 // default implementation does nothing
255 }
256
257 public boolean isAutoDetecting ()
258 {
259 return false;
260 }
261
262 public boolean isCharsetDetected ()
263 {
264 throw new UnsupportedOperationException ();
265 }
266
267 public CodingErrorAction malformedInputAction ()
268 {
269 return malformedInputAction;
270 }
271
272 public final float maxCharsPerByte ()
273 {
274 return maxCharsPerByte;
275 }
276
277 public final CharsetDecoder onUnmappableCharacter
278 (CodingErrorAction newAction)
279 {
280 if (newAction == null)
281 throw new IllegalArgumentException ("Null action");
282
283 unmappableCharacterAction = newAction;
284 implOnUnmappableCharacter (newAction);
285 return this;
286 }
287
288 public final String replacement ()
289 {
290 return replacement;
291 }
292
293 public final CharsetDecoder replaceWith (String newReplacement)
294 {
295 if (newReplacement == null)
296 throw new IllegalArgumentException ("Null replacement");
297 if (newReplacement.length () == 0)
298 throw new IllegalArgumentException ("Empty replacement");
299 // XXX: what about maxCharsPerByte?
300
301 this.replacement = newReplacement;
302 implReplaceWith (newReplacement);
303 return this;
304 }
305
306 public final CharsetDecoder reset ()
307 {
308 state = STATE_RESET;
309 implReset ();
310 return this;
311 }
312
313 public CodingErrorAction unmappableCharacterAction ()
314 {
315 return unmappableCharacterAction;
316 }
317 }