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 
CharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte, String replacement)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 
CharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte)85   protected CharsetDecoder (Charset cs, float averageCharsPerByte,
86                             float maxCharsPerByte)
87   {
88     this (cs, averageCharsPerByte, maxCharsPerByte, DEFAULT_REPLACEMENT);
89   }
90 
averageCharsPerByte()91   public final float averageCharsPerByte ()
92   {
93     return averageCharsPerByte;
94   }
95 
charset()96   public final Charset charset ()
97   {
98     return charset;
99   }
100 
decode(ByteBuffer in)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 
decode(ByteBuffer in, CharBuffer out, boolean endOfInput)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 
decodeLoop(ByteBuffer in, CharBuffer out)196   protected abstract CoderResult decodeLoop (ByteBuffer in, CharBuffer out);
197 
detectedCharset()198   public Charset detectedCharset ()
199   {
200     throw new UnsupportedOperationException ();
201   }
202 
flush(CharBuffer out)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 
implFlush(CharBuffer out)222   protected CoderResult implFlush (CharBuffer out)
223   {
224     return CoderResult.UNDERFLOW;
225   }
226 
onMalformedInput(CodingErrorAction newAction)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 
implOnMalformedInput(CodingErrorAction newAction)237   protected void implOnMalformedInput (CodingErrorAction newAction)
238   {
239     // default implementation does nothing
240   }
241 
implOnUnmappableCharacter(CodingErrorAction newAction)242   protected void implOnUnmappableCharacter (CodingErrorAction newAction)
243   {
244     // default implementation does nothing
245   }
246 
implReplaceWith(String newReplacement)247   protected void implReplaceWith (String newReplacement)
248   {
249     // default implementation does nothing
250   }
251 
implReset()252   protected void implReset ()
253   {
254     // default implementation does nothing
255   }
256 
isAutoDetecting()257   public boolean isAutoDetecting ()
258   {
259     return false;
260   }
261 
isCharsetDetected()262   public boolean isCharsetDetected ()
263   {
264     throw new UnsupportedOperationException ();
265   }
266 
malformedInputAction()267   public CodingErrorAction malformedInputAction ()
268   {
269     return malformedInputAction;
270   }
271 
maxCharsPerByte()272   public final float maxCharsPerByte ()
273   {
274     return maxCharsPerByte;
275   }
276 
onUnmappableCharacter(CodingErrorAction newAction)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 
replacement()288   public final String replacement ()
289   {
290     return replacement;
291   }
292 
replaceWith(String newReplacement)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 
reset()306   public final CharsetDecoder reset ()
307   {
308     state = STATE_RESET;
309     implReset ();
310     return this;
311   }
312 
unmappableCharacterAction()313   public CodingErrorAction unmappableCharacterAction ()
314   {
315     return unmappableCharacterAction;
316   }
317 }
318