diff options
Diffstat (limited to 'libjava/java/nio/charset')
-rw-r--r-- | libjava/java/nio/charset/Charset.java | 257 | ||||
-rw-r--r-- | libjava/java/nio/charset/CharsetDecoder.java | 312 | ||||
-rw-r--r-- | libjava/java/nio/charset/CharsetEncoder.java | 360 | ||||
-rw-r--r-- | libjava/java/nio/charset/CoderResult.java | 193 |
4 files changed, 1074 insertions, 48 deletions
diff --git a/libjava/java/nio/charset/Charset.java b/libjava/java/nio/charset/Charset.java index 2a1338feae4..5d96daf7f76 100644 --- a/libjava/java/nio/charset/Charset.java +++ b/libjava/java/nio/charset/Charset.java @@ -37,54 +37,215 @@ exception statement from your version. */ package java.nio.charset; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.spi.CharsetProvider; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import gnu.java.nio.charset.Provider; -import java.nio.*; - -public class Charset -{ - public static Charset forName(String name) - { - return new Charset(); - } - -/* - public CharsetDecoder newDecoder() - { - return new CharsetDecoder(this,2,2) - { - protected CoderResult decodeLoop(ByteBuffer in, - CharBuffer out) - { - while (in.hasRemaining()) - { - char a = (char) in.get(); - out.put(a); - } - return null; - } - }; - } - - public CharsetEncoder newEncoder() - { - return new CharsetEncoder(this,2,2) - { - protected CoderResult encodeLoop(CharBuffer in, - ByteBuffer out) - { - //System.out.println("in encode loop:"+in.hasRemaining()); - - while (in.hasRemaining()) - { - char a = in.get(); - out.put((byte)a); - - //int len = out.position(); - //System.out.println("pos="+len + ","+a); - } - return null; - } - }; - } +/** + * @author Jesse Rosenstock + * @since 1.4 */ +public abstract class Charset implements Comparable +{ + private final String canonicalName; + private final String[] aliases; + + protected Charset (String canonicalName, String[] aliases) + { + checkName (canonicalName); + if (aliases != null) + { + int n = aliases.length; + for (int i = 0; i < n; ++i) + checkName (aliases[i]); + } + + this.canonicalName = canonicalName; + this.aliases = aliases; + } + + /** + * @throws IllegalCharsetNameException if the name is illegal + */ + private static void checkName (String name) + { + int n = name.length (); + + if (n == 0) + throw new IllegalCharsetNameException (name); + + char ch = name.charAt (0); + if (!(('A' <= ch && ch <= 'Z') + || ('a' <= ch && ch <= 'z') + || ('0' <= ch && ch <= '9'))) + throw new IllegalCharsetNameException (name); + + for (int i = 1; i < n; ++i) + { + ch = name.charAt (i); + if (!(('A' <= ch && ch <= 'Z') + || ('a' <= ch && ch <= 'z') + || ('0' <= ch && ch <= '9') + || ch == '-' || ch == '.' || ch == ':' || ch == '_')) + throw new IllegalCharsetNameException (name); + } + } + + public static boolean isSupported (String charsetName) + { + return charsetForName (charsetName) != null; + } + + public static Charset forName (String charsetName) + { + Charset cs = charsetForName (charsetName); + if (cs == null) + throw new UnsupportedCharsetException (charsetName); + return cs; + } + + /** + * Retrieves a charset for the given charset name. + * + * @return A charset object for the charset with the specified name, or + * <code>null</code> if no such charset exists. + * + * @throws IllegalCharsetNameException if the name is illegal + */ + private static Charset charsetForName (String charsetName) + { + checkName (charsetName); + return provider ().charsetForName (charsetName); + } + + public static SortedMap availableCharsets () + { + TreeMap charsets = new TreeMap (String.CASE_INSENSITIVE_ORDER); + + for (Iterator i = provider ().charsets (); i.hasNext (); ) + { + Charset cs = (Charset) i.next (); + charsets.put (cs.name (), cs); + } + + return Collections.unmodifiableSortedMap (charsets); + } + + // XXX: we need to support multiple providers, reading them from + // java.nio.charset.spi.CharsetProvider in the resource directory + // META-INF/services + private static final CharsetProvider provider () + { + return Provider.provider (); + } + + public final String name () + { + return canonicalName; + } + + public final Set aliases () + { + if (aliases == null) + return Collections.EMPTY_SET; + + // should we cache the aliasSet instead? + int n = aliases.length; + HashSet aliasSet = new HashSet (n); + for (int i = 0; i < n; ++i) + aliasSet.add (aliases[i]); + return Collections.unmodifiableSet (aliasSet); + } + + public String displayName () + { + return canonicalName; + } + + public String displayName (Locale locale) + { + return canonicalName; + } + + public final boolean isRegistered (String name) + { + return !name.startsWith ("x-") && !name.startsWith ("X-"); + } + + public abstract boolean contains (Charset cs); + + public abstract CharsetDecoder newDecoder (); + + public abstract CharsetEncoder newEncoder (); + + public boolean canEncode () + { + return true; + } + + public final ByteBuffer encode (CharBuffer cb) + { + try + { + // TODO: cache encoders between sucessive invocations + return newEncoder ().onMalformedInput (CodingErrorAction.REPLACE) + .onUnmappableCharacter (CodingErrorAction.REPLACE) + .encode (cb); + } + catch (CharacterCodingException e) + { + throw new AssertionError (e); + } + } + + public final ByteBuffer encode (String str) + { + return encode (CharBuffer.wrap (str)); + } + + public CharBuffer decode (ByteBuffer bb) + { + try + { + // TODO: cache encoders between sucessive invocations + return newDecoder ().onMalformedInput (CodingErrorAction.REPLACE) + .onUnmappableCharacter (CodingErrorAction.REPLACE) + .decode (bb); + } + catch (CharacterCodingException e) + { + throw new AssertionError (e); + } + } + + public final int compareTo (Object ob) + { + return canonicalName.compareToIgnoreCase (((Charset) ob).canonicalName); + } + + public final int hashCode () + { + return canonicalName.hashCode (); + } + + public final boolean equals (Object ob) + { + if (ob instanceof Charset) + return canonicalName.equalsIgnoreCase (((Charset) ob).canonicalName); + else + return false; + } + + public final String toString () + { + return canonicalName; + } } diff --git a/libjava/java/nio/charset/CharsetDecoder.java b/libjava/java/nio/charset/CharsetDecoder.java new file mode 100644 index 00000000000..185de149fd4 --- /dev/null +++ b/libjava/java/nio/charset/CharsetDecoder.java @@ -0,0 +1,312 @@ +/* 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., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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 (); + + out.flip (); + return out; + } + + 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; + } +} diff --git a/libjava/java/nio/charset/CharsetEncoder.java b/libjava/java/nio/charset/CharsetEncoder.java new file mode 100644 index 00000000000..dd434a79752 --- /dev/null +++ b/libjava/java/nio/charset/CharsetEncoder.java @@ -0,0 +1,360 @@ +/* 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., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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 (); + return out; + } + + 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 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; + } +} diff --git a/libjava/java/nio/charset/CoderResult.java b/libjava/java/nio/charset/CoderResult.java new file mode 100644 index 00000000000..69deadcc4f0 --- /dev/null +++ b/libjava/java/nio/charset/CoderResult.java @@ -0,0 +1,193 @@ +/* CoderResult.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., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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.lang.ref.WeakReference; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.util.HashMap; + +/** + * @author Jesse Rosenstock + * @since 1.4 + */ +public class CoderResult +{ + private static final int TYPE_MALFORMED = 0; + private static final int TYPE_OVERFLOW = 1; + private static final int TYPE_UNDERFLOW = 2; + private static final int TYPE_UNMAPPABLE = 3; + + public static final CoderResult OVERFLOW + = new CoderResult (TYPE_OVERFLOW, 0); + public static final CoderResult UNDERFLOW + = new CoderResult (TYPE_UNDERFLOW, 0); + + private static final String[] names + = { "MALFORMED", "OVERFLOW", "UNDERFLOW", "UNMAPPABLE" }; + + private static final Cache malformedCache + = new Cache () + { + protected CoderResult make (int length) + { + return new CoderResult (TYPE_MALFORMED, length); + } + }; + + private static final Cache unmappableCache + = new Cache () + { + protected CoderResult make (int length) + { + return new CoderResult (TYPE_UNMAPPABLE, length); + } + }; + + private final int type; + private final int length; + + private CoderResult (int type, int length) + { + this.type = type; + this.length = length; + } + + public boolean isError () + { + return length > 0; + } + + public boolean isMalformed () + { + return type == TYPE_MALFORMED; + } + + public boolean isOverflow () + { + return type == TYPE_OVERFLOW; + } + + public boolean isUnderflow () + { + return type == TYPE_UNDERFLOW; + } + + public boolean isUnmappable () + { + return type == TYPE_UNMAPPABLE; + } + + public int length () + { + if (length <= 0) + throw new UnsupportedOperationException (); + else + return length; + } + + public static CoderResult malformedForLength (int length) + { + return malformedCache.get (length); + } + + public void throwException () + throws CharacterCodingException + { + switch (type) + { + case TYPE_MALFORMED: + throw new MalformedInputException (length); + case TYPE_OVERFLOW: + throw new BufferOverflowException (); + case TYPE_UNDERFLOW: + throw new BufferUnderflowException (); + case TYPE_UNMAPPABLE: + throw new UnmappableCharacterException (length); + } + } + + public String toString () + { + String name = names[type]; + return (length > 0) ? name + '[' + length + ']' : name; + } + + public static CoderResult unmappableForLength (int length) + { + return unmappableCache.get (length); + } + + private abstract static class Cache + { + private final HashMap cache; + + private Cache () + { + // If we didn't synchronize on this, then cache would be initialized + // without holding a lock. Undefined behavior would occur if the + // first thread to call get(int) was not the same as the one that + // called the constructor. + synchronized (this) + { + cache = new HashMap (); + } + } + + private synchronized CoderResult get (int length) + { + if (length <= 0) + throw new IllegalArgumentException ("Non-positive length"); + + Integer len = new Integer (length); + CoderResult cr = null; + Object o; + if ((o = cache.get (len)) != null) + cr = (CoderResult) ((WeakReference) o).get (); + if (cr == null) + { + cr = make (length); + cache.put (len, cr); + } + + return cr; + } + + protected abstract CoderResult make (int length); + } +} |