Source for java.nio.charset.CharsetDecoder

   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: 
  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: 
  85:   protected CharsetDecoder (Charset cs, float averageCharsPerByte,
  86:                             float maxCharsPerByte)
  87:   {
  88:     this (cs, averageCharsPerByte, maxCharsPerByte, DEFAULT_REPLACEMENT);
  89:   }
  90: 
  91:   public final float averageCharsPerByte ()
  92:   {
  93:     return averageCharsPerByte;
  94:   }
  95: 
  96:   public final Charset charset ()
  97:   {
  98:     return charset;
  99:   }
 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: }