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