| /* CharsetDecoder.java -- |
| Copyright (C) 2002 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package java.nio.charset; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| |
| /** |
| * @author Jesse Rosenstock |
| * @since 1.4 |
| */ |
| public abstract class CharsetDecoder |
| { |
| private static final int STATE_RESET = 0; |
| private static final int STATE_CODING = 1; |
| private static final int STATE_END = 2; |
| private static final int STATE_FLUSHED = 3; |
| |
| private static final String DEFAULT_REPLACEMENT = "\uFFFD"; |
| |
| private final Charset charset; |
| private final float averageCharsPerByte; |
| private final float maxCharsPerByte; |
| private String replacement; |
| |
| private int state = STATE_RESET; |
| |
| private CodingErrorAction malformedInputAction |
| = CodingErrorAction.REPORT; |
| private CodingErrorAction unmappableCharacterAction |
| = CodingErrorAction.REPORT; |
| |
| private CharsetDecoder (Charset cs, float averageCharsPerByte, |
| float maxCharsPerByte, String replacement) |
| { |
| if (averageCharsPerByte <= 0.0f) |
| throw new IllegalArgumentException ("Non-positive averageCharsPerByte"); |
| if (maxCharsPerByte <= 0.0f) |
| throw new IllegalArgumentException ("Non-positive maxCharsPerByte"); |
| |
| this.charset = cs; |
| this.averageCharsPerByte |
| = averageCharsPerByte; |
| this.maxCharsPerByte |
| = maxCharsPerByte; |
| this.replacement = replacement; |
| implReplaceWith (replacement); |
| } |
| |
| protected CharsetDecoder (Charset cs, float averageCharsPerByte, |
| float maxCharsPerByte) |
| { |
| this (cs, averageCharsPerByte, maxCharsPerByte, DEFAULT_REPLACEMENT); |
| } |
| |
| public final float averageCharsPerByte () |
| { |
| return averageCharsPerByte; |
| } |
| |
| public final Charset charset () |
| { |
| return charset; |
| } |
| |
| public final CharBuffer decode (ByteBuffer in) |
| throws CharacterCodingException |
| { |
| // XXX: Sun's Javadoc seems to contradict itself saying an |
| // IllegalStateException is thrown "if a decoding operation is already |
| // in progress" and also that "it resets this Decoder". |
| // Should we check to see that the state is reset, or should we |
| // call reset()? |
| if (state != STATE_RESET) |
| throw new IllegalStateException (); |
| |
| // REVIEW: Using max instead of average may allocate a very large |
| // buffer. Maybe we should do something more efficient? |
| int remaining = in.remaining (); |
| int n = (int) (remaining * maxCharsPerByte ()); |
| CharBuffer out = CharBuffer.allocate (n); |
| |
| if (remaining == 0) |
| { |
| state = STATE_FLUSHED; |
| return out; |
| } |
| |
| CoderResult cr = decode (in, out, true); |
| if (cr.isError ()) |
| cr.throwException (); |
| |
| cr = flush (out); |
| if (cr.isError ()) |
| cr.throwException (); |
| |
| reset(); |
| out.flip (); |
| |
| // Unfortunately, resizing the actual charbuffer array is required. |
| char[] resized = new char[out.remaining()]; |
| out.get(resized); |
| return CharBuffer.wrap(resized); |
| } |
| |
| public final CoderResult decode (ByteBuffer in, CharBuffer out, |
| boolean endOfInput) |
| { |
| int newState = endOfInput ? STATE_END : STATE_CODING; |
| // XXX: Need to check for "previous step was an invocation [not] of |
| // this method with a value of true for the endOfInput parameter but |
| // a return value indicating an incomplete decoding operation" |
| // XXX: We will not check the previous return value, just |
| // that the previous call passed true for endOfInput |
| if (state != STATE_RESET && state != STATE_CODING |
| && !(endOfInput && state == STATE_END)) |
| throw new IllegalStateException (); |
| state = newState; |
| |
| for (;;) |
| { |
| CoderResult cr; |
| try |
| { |
| cr = decodeLoop (in, out); |
| } |
| catch (RuntimeException e) |
| { |
| throw new CoderMalfunctionError (e); |
| } |
| |
| if (cr.isOverflow ()) |
| return cr; |
| |
| if (cr.isUnderflow ()) |
| { |
| if (endOfInput && in.hasRemaining ()) |
| cr = CoderResult.malformedForLength (in.remaining ()); |
| else |
| return cr; |
| } |
| |
| CodingErrorAction action = cr.isMalformed () |
| ? malformedInputAction |
| : unmappableCharacterAction; |
| |
| if (action == CodingErrorAction.REPORT) |
| return cr; |
| |
| if (action == CodingErrorAction.REPLACE) |
| { |
| if (out.remaining () < replacement.length ()) |
| return CoderResult.OVERFLOW; |
| out.put (replacement); |
| } |
| |
| in.position (in.position () + cr.length ()); |
| } |
| } |
| |
| protected abstract CoderResult decodeLoop (ByteBuffer in, CharBuffer out); |
| |
| public Charset detectedCharset () |
| { |
| throw new UnsupportedOperationException (); |
| } |
| |
| public final CoderResult flush (CharBuffer out) |
| { |
| // It seems weird that you can flush after reset, but Sun's javadoc |
| // says an IllegalStateException is thrown "If the previous step of the |
| // current decoding operation was an invocation neither of the reset |
| // method nor ... of the three-argument decode method with a value of |
| // true for the endOfInput parameter." |
| // Further note that flush() only requires that there not be |
| // an IllegalStateException if the previous step was a call to |
| // decode with true as the last argument. It does not require |
| // that the call succeeded. decode() does require that it succeeded. |
| // XXX: test this to see if reality matches javadoc |
| if (state != STATE_RESET && state != STATE_END) |
| throw new IllegalStateException (); |
| |
| state = STATE_FLUSHED; |
| return implFlush (out); |
| } |
| |
| protected CoderResult implFlush (CharBuffer out) |
| { |
| return CoderResult.UNDERFLOW; |
| } |
| |
| public final CharsetDecoder onMalformedInput (CodingErrorAction newAction) |
| { |
| if (newAction == null) |
| throw new IllegalArgumentException ("Null action"); |
| |
| malformedInputAction = newAction; |
| implOnMalformedInput (newAction); |
| return this; |
| } |
| |
| protected void implOnMalformedInput (CodingErrorAction newAction) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implOnUnmappableCharacter (CodingErrorAction newAction) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implReplaceWith (String newReplacement) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implReset () |
| { |
| // default implementation does nothing |
| } |
| |
| public boolean isAutoDetecting () |
| { |
| return false; |
| } |
| |
| public boolean isCharsetDetected () |
| { |
| throw new UnsupportedOperationException (); |
| } |
| |
| public CodingErrorAction malformedInputAction () |
| { |
| return malformedInputAction; |
| } |
| |
| public final float maxCharsPerByte () |
| { |
| return maxCharsPerByte; |
| } |
| |
| public final CharsetDecoder onUnmappableCharacter |
| (CodingErrorAction newAction) |
| { |
| if (newAction == null) |
| throw new IllegalArgumentException ("Null action"); |
| |
| unmappableCharacterAction = newAction; |
| implOnUnmappableCharacter (newAction); |
| return this; |
| } |
| |
| public final String replacement () |
| { |
| return replacement; |
| } |
| |
| public final CharsetDecoder replaceWith (String newReplacement) |
| { |
| if (newReplacement == null) |
| throw new IllegalArgumentException ("Null replacement"); |
| if (newReplacement.length () == 0) |
| throw new IllegalArgumentException ("Empty replacement"); |
| // XXX: what about maxCharsPerByte? |
| |
| this.replacement = newReplacement; |
| implReplaceWith (newReplacement); |
| return this; |
| } |
| |
| public final CharsetDecoder reset () |
| { |
| state = STATE_RESET; |
| implReset (); |
| return this; |
| } |
| |
| public CodingErrorAction unmappableCharacterAction () |
| { |
| return unmappableCharacterAction; |
| } |
| } |