001/* CharsetDecoder.java -- 
002   Copyright (C) 2002 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package java.nio.charset;
039
040import java.nio.ByteBuffer;
041import java.nio.CharBuffer;
042
043/**
044 * @author Jesse Rosenstock
045 * @since 1.4
046 */
047public abstract class CharsetDecoder
048{
049  private static final int STATE_RESET   = 0;
050  private static final int STATE_CODING  = 1;
051  private static final int STATE_END     = 2;
052  private static final int STATE_FLUSHED = 3;
053
054  private static final String DEFAULT_REPLACEMENT = "\uFFFD";
055
056  private final Charset charset;
057  private final float averageCharsPerByte;
058  private final float maxCharsPerByte;
059  private String replacement;
060
061  private int state = STATE_RESET;
062
063  private CodingErrorAction malformedInputAction
064    = CodingErrorAction.REPORT;
065  private CodingErrorAction unmappableCharacterAction
066    = CodingErrorAction.REPORT;
067
068  private CharsetDecoder (Charset cs, float averageCharsPerByte,
069                          float maxCharsPerByte, String replacement)
070  {
071    if (averageCharsPerByte <= 0.0f)
072      throw new IllegalArgumentException ("Non-positive averageCharsPerByte");
073    if (maxCharsPerByte <= 0.0f)
074      throw new IllegalArgumentException ("Non-positive maxCharsPerByte");
075
076    this.charset = cs;
077    this.averageCharsPerByte
078      = averageCharsPerByte;
079    this.maxCharsPerByte
080      = maxCharsPerByte;
081    this.replacement = replacement;
082    implReplaceWith (replacement);
083  }
084
085  protected CharsetDecoder (Charset cs, float averageCharsPerByte,
086                            float maxCharsPerByte)
087  {
088    this (cs, averageCharsPerByte, maxCharsPerByte, DEFAULT_REPLACEMENT);
089  }
090
091  public final float averageCharsPerByte ()
092  {
093    return averageCharsPerByte;
094  }
095
096  public final Charset charset ()
097  {
098    return charset;
099  }
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}