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