| /* CharsetEncoder.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 CharsetEncoder |
| { |
| 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 byte[] DEFAULT_REPLACEMENT = {(byte)'?'}; |
| |
| private final Charset charset; |
| private final float averageBytesPerChar; |
| private final float maxBytesPerChar; |
| private byte[] replacement; |
| |
| private int state = STATE_RESET; |
| |
| private CodingErrorAction malformedInputAction |
| = CodingErrorAction.REPORT; |
| private CodingErrorAction unmappableCharacterAction |
| = CodingErrorAction.REPORT; |
| |
| protected CharsetEncoder (Charset cs, float averageBytesPerChar, |
| float maxBytesPerChar) |
| { |
| this (cs, averageBytesPerChar, maxBytesPerChar, DEFAULT_REPLACEMENT); |
| } |
| |
| protected CharsetEncoder (Charset cs, float averageBytesPerChar, |
| float maxBytesPerChar, byte[] replacement) |
| { |
| if (averageBytesPerChar <= 0.0f) |
| throw new IllegalArgumentException ("Non-positive averageBytesPerChar"); |
| if (maxBytesPerChar <= 0.0f) |
| throw new IllegalArgumentException ("Non-positive maxBytesPerChar"); |
| |
| this.charset = cs; |
| this.averageBytesPerChar |
| = averageBytesPerChar; |
| this.maxBytesPerChar |
| = maxBytesPerChar; |
| this.replacement = replacement; |
| implReplaceWith (replacement); |
| } |
| |
| public final float averageBytesPerChar () |
| { |
| return averageBytesPerChar; |
| } |
| |
| public boolean canEncode (char c) |
| { |
| CharBuffer cb = CharBuffer.allocate (1).put (c); |
| cb.flip (); |
| return canEncode (cb); |
| } |
| |
| public boolean canEncode (CharSequence cs) |
| { |
| CharBuffer cb; |
| if (cs instanceof CharBuffer) |
| cb = ((CharBuffer) cs).duplicate (); |
| else |
| cb = CharBuffer.wrap (cs); |
| return canEncode (cb); |
| } |
| |
| private boolean canEncode (CharBuffer cb) |
| { |
| // It is an error if a coding operation is "in progress" |
| // I take that to mean the state is not reset or flushed. |
| // XXX: check "in progress" everywhere |
| if (state == STATE_FLUSHED) |
| reset (); |
| else if (state != STATE_RESET) |
| throw new IllegalStateException (); |
| |
| CodingErrorAction oldMalformedInputAction = malformedInputAction; |
| CodingErrorAction oldUnmappableCharacterAction |
| = unmappableCharacterAction; |
| |
| try |
| { |
| if (oldMalformedInputAction != CodingErrorAction.REPORT) |
| onMalformedInput (CodingErrorAction.REPORT); |
| if (oldUnmappableCharacterAction != CodingErrorAction.REPORT) |
| onUnmappableCharacter (CodingErrorAction.REPORT); |
| } |
| catch (Exception e) |
| { |
| return false; |
| } |
| finally |
| { |
| if (oldMalformedInputAction != CodingErrorAction.REPORT) |
| onMalformedInput (oldMalformedInputAction); |
| if (oldUnmappableCharacterAction != CodingErrorAction.REPORT) |
| onUnmappableCharacter (oldUnmappableCharacterAction); |
| } |
| |
| return true; |
| } |
| |
| public final Charset charset () |
| { |
| return charset; |
| } |
| |
| public final ByteBuffer encode (CharBuffer 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 Encoder". |
| // 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 * maxBytesPerChar ()); |
| ByteBuffer out = ByteBuffer.allocate (n); |
| |
| if (remaining == 0) |
| { |
| state = STATE_FLUSHED; |
| return out; |
| } |
| |
| CoderResult cr = encode (in, out, true); |
| if (cr.isError ()) |
| cr.throwException (); |
| |
| cr = flush (out); |
| if (cr.isError ()) |
| cr.throwException (); |
| |
| out.flip (); |
| |
| // Unfortunately, resizing the actual bytebuffer array is required. |
| byte[] resized = new byte[out.remaining()]; |
| out.get(resized); |
| return ByteBuffer.wrap(resized); |
| } |
| |
| public final CoderResult encode (CharBuffer in, ByteBuffer 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 = encodeLoop (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 encodeLoop (CharBuffer in, ByteBuffer out); |
| |
| public final CoderResult flush (ByteBuffer 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 encode 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 |
| // encode with true as the last argument. It does not require |
| // that the call succeeded. encode() 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 (ByteBuffer out) |
| { |
| return CoderResult.UNDERFLOW; |
| } |
| |
| protected void implOnMalformedInput (CodingErrorAction newAction) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implOnUnmappableCharacter (CodingErrorAction newAction) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implReplaceWith (byte[] newReplacement) |
| { |
| // default implementation does nothing |
| } |
| |
| protected void implReset () |
| { |
| // default implementation does nothing |
| } |
| |
| public boolean isLegalReplacement (byte[] replacement) |
| { |
| // TODO: cache the decoder |
| // error actions will be REPORT after construction |
| CharsetDecoder decoder = charset.newDecoder (); |
| ByteBuffer bb = ByteBuffer.wrap (replacement); |
| CharBuffer cb |
| = CharBuffer.allocate ((int) (replacement.length |
| * decoder.maxCharsPerByte ())); |
| return !decoder.decode (bb, cb, true).isError (); |
| } |
| |
| public CodingErrorAction malformedInputAction () |
| { |
| return malformedInputAction; |
| } |
| |
| public final float maxBytesPerChar () |
| { |
| return maxBytesPerChar; |
| } |
| |
| public final CharsetEncoder onMalformedInput (CodingErrorAction newAction) |
| { |
| if (newAction == null) |
| throw new IllegalArgumentException ("Null action"); |
| |
| malformedInputAction = newAction; |
| implOnMalformedInput (newAction); |
| return this; |
| } |
| |
| public CodingErrorAction unmappableCharacterAction () |
| { |
| return unmappableCharacterAction; |
| } |
| |
| public final CharsetEncoder onUnmappableCharacter |
| (CodingErrorAction newAction) |
| { |
| if (newAction == null) |
| throw new IllegalArgumentException ("Null action"); |
| |
| unmappableCharacterAction = newAction; |
| implOnUnmappableCharacter (newAction); |
| return this; |
| } |
| |
| public final byte[] replacement () |
| { |
| return replacement; |
| } |
| |
| public final CharsetEncoder replaceWith (byte[] newReplacement) |
| { |
| if (newReplacement == null) |
| throw new IllegalArgumentException ("Null replacement"); |
| if (newReplacement.length == 0) |
| throw new IllegalArgumentException ("Empty replacement"); |
| // XXX: what about maxBytesPerChar? |
| |
| if (!isLegalReplacement (newReplacement)) |
| throw new IllegalArgumentException ("Illegal replacement"); |
| |
| this.replacement = newReplacement; |
| implReplaceWith (newReplacement); |
| return this; |
| } |
| |
| public final CharsetEncoder reset () |
| { |
| state = STATE_RESET; |
| implReset (); |
| return this; |
| } |
| } |