Source for java.nio.charset.CharsetEncoder

   1: /* CharsetEncoder.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 CharsetEncoder
  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 byte[] DEFAULT_REPLACEMENT = {(byte)'?'};
  55: 
  56:   private final Charset charset;
  57:   private final float averageBytesPerChar;
  58:   private final float maxBytesPerChar;
  59:   private byte[] 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:   protected CharsetEncoder (Charset cs, float averageBytesPerChar,
  69:                             float maxBytesPerChar)
  70:   {
  71:     this (cs, averageBytesPerChar, maxBytesPerChar, DEFAULT_REPLACEMENT);
  72:   }
  73: 
  74:   protected CharsetEncoder (Charset cs, float averageBytesPerChar,
  75:                             float maxBytesPerChar, byte[] replacement)
  76:   {
  77:     if (averageBytesPerChar <= 0.0f)
  78:       throw new IllegalArgumentException ("Non-positive averageBytesPerChar");
  79:     if (maxBytesPerChar <= 0.0f)
  80:       throw new IllegalArgumentException ("Non-positive maxBytesPerChar");
  81: 
  82:     this.charset = cs;
  83:     this.averageBytesPerChar
  84:       = averageBytesPerChar;
  85:     this.maxBytesPerChar
  86:       = maxBytesPerChar;
  87:     this.replacement = replacement;
  88:     implReplaceWith (replacement);
  89:   }
  90: 
  91:   public final float averageBytesPerChar ()
  92:   {
  93:     return averageBytesPerChar;
  94:   }
  95: 
  96:   public boolean canEncode (char c)
  97:   {
  98:     CharBuffer cb = CharBuffer.allocate (1).put (c);
  99:     cb.flip ();
 100:     return canEncode (cb);
 101:   }
 102: 
 103:   public boolean canEncode (CharSequence cs)
 104:   {
 105:     CharBuffer cb;
 106:     if (cs instanceof CharBuffer)
 107:       cb = ((CharBuffer) cs).duplicate ();
 108:     else
 109:       cb = CharBuffer.wrap (cs);
 110:     return canEncode (cb);
 111:   }
 112: 
 113:   private boolean canEncode (CharBuffer cb)
 114:   {
 115:     // It is an error if a coding operation is "in progress"
 116:     // I take that to mean the state is not reset or flushed.
 117:     // XXX: check "in progress" everywhere
 118:     if (state == STATE_FLUSHED)
 119:       reset ();
 120:     else if (state != STATE_RESET)
 121:       throw new IllegalStateException ();
 122: 
 123:     CodingErrorAction oldMalformedInputAction = malformedInputAction;
 124:     CodingErrorAction oldUnmappableCharacterAction
 125:       = unmappableCharacterAction;
 126: 
 127:     try
 128:       {
 129:         if (oldMalformedInputAction != CodingErrorAction.REPORT)
 130:           onMalformedInput (CodingErrorAction.REPORT);
 131:         if (oldUnmappableCharacterAction != CodingErrorAction.REPORT)
 132:           onUnmappableCharacter (CodingErrorAction.REPORT);
 133:       }
 134:     catch (Exception e)
 135:       {
 136:         return false;
 137:       }
 138:     finally
 139:       {
 140:         if (oldMalformedInputAction != CodingErrorAction.REPORT)
 141:           onMalformedInput (oldMalformedInputAction);
 142:         if (oldUnmappableCharacterAction != CodingErrorAction.REPORT)
 143:           onUnmappableCharacter (oldUnmappableCharacterAction);
 144:       }
 145: 
 146:     return true;
 147:   }
 148: 
 149:   public final Charset charset ()
 150:   {
 151:     return charset;
 152:   }
 153: 
 154:   public final ByteBuffer encode (CharBuffer in)
 155:     throws CharacterCodingException
 156:   {
 157:     // XXX: Sun's Javadoc seems to contradict itself saying an
 158:     // IllegalStateException is thrown "if a decoding operation is already
 159:     // in progress" and also that "it resets this Encoder".
 160:     // Should we check to see that the state is reset, or should we
 161:     // call reset()?
 162:     if (state != STATE_RESET)
 163:       throw new IllegalStateException ();
 164: 
 165:     // REVIEW: Using max instead of average may allocate a very large
 166:     // buffer.  Maybe we should do something more efficient?
 167:     int remaining = in.remaining ();
 168:     int n = (int) (remaining * maxBytesPerChar ());
 169:     ByteBuffer out = ByteBuffer.allocate (n);
 170: 
 171:     if (remaining == 0)
 172:       {
 173:         state = STATE_FLUSHED;
 174:         return out;
 175:       }
 176: 
 177:     CoderResult cr = encode (in, out, true);
 178:     if (cr.isError ())
 179:       cr.throwException ();
 180: 
 181:     cr = flush (out);
 182:     if (cr.isError ())
 183:       cr.throwException ();
 184: 
 185:     out.flip ();
 186: 
 187:     // Unfortunately, resizing the actual bytebuffer array is required.
 188:     byte[] resized = new byte[out.remaining()];
 189:     out.get(resized);
 190:     return ByteBuffer.wrap(resized);
 191:   }
 192: 
 193:   public final CoderResult encode (CharBuffer in, ByteBuffer out,
 194:                                    boolean endOfInput)
 195:   {
 196:     int newState = endOfInput ? STATE_END : STATE_CODING;
 197:     // XXX: Need to check for "previous step was an invocation [not] of
 198:     // this method with a value of true for the endOfInput parameter but
 199:     // a return value indicating an incomplete decoding operation"
 200:     // XXX: We will not check the previous return value, just
 201:     // that the previous call passed true for endOfInput
 202:     if (state != STATE_RESET && state != STATE_CODING
 203:         && !(endOfInput && state == STATE_END))
 204:       throw new IllegalStateException ();
 205:     state = newState;
 206: 
 207:     for (;;)
 208:       {
 209:         CoderResult cr;
 210:         try
 211:           {
 212:             cr = encodeLoop (in, out);
 213:           }
 214:         catch (RuntimeException e)
 215:           {
 216:             throw new CoderMalfunctionError (e);
 217:           }
 218: 
 219:         if (cr.isOverflow ())
 220:           return cr;
 221: 
 222:         if (cr.isUnderflow ())
 223:           {
 224:             if (endOfInput && in.hasRemaining ())
 225:               cr = CoderResult.malformedForLength (in.remaining ());
 226:             else
 227:               return cr;
 228:           }
 229: 
 230:         CodingErrorAction action = cr.isMalformed ()
 231:                                      ? malformedInputAction
 232:                                      : unmappableCharacterAction;
 233: 
 234:         if (action == CodingErrorAction.REPORT)
 235:           return cr;
 236: 
 237:         if (action == CodingErrorAction.REPLACE)
 238:           {
 239:             if (out.remaining () < replacement.length)
 240:               return CoderResult.OVERFLOW;
 241:             out.put (replacement);
 242:           }
 243: 
 244:         in.position (in.position () + cr.length ());
 245:       }
 246:   }
 247: 
 248:   protected abstract CoderResult encodeLoop (CharBuffer in, ByteBuffer out);
 249: 
 250:   public final CoderResult flush (ByteBuffer out)
 251:   {
 252:     // It seems weird that you can flush after reset, but Sun's javadoc
 253:     // says an IllegalStateException is thrown "If the previous step of the
 254:     // current decoding operation was an invocation neither of the reset
 255:     // method nor ... of the three-argument encode method with a value of
 256:     // true for the endOfInput parameter."
 257:     // Further note that flush() only requires that there not be
 258:     // an IllegalStateException if the previous step was a call to
 259:     // encode with true as the last argument.  It does not require
 260:     // that the call succeeded.  encode() does require that it succeeded.
 261:     // XXX: test this to see if reality matches javadoc
 262:     if (state != STATE_RESET && state != STATE_END)
 263:       throw new IllegalStateException ();
 264: 
 265:     state = STATE_FLUSHED;
 266:     return implFlush (out);
 267:   }
 268: 
 269:   protected CoderResult implFlush (ByteBuffer out)
 270:   {
 271:     return CoderResult.UNDERFLOW;
 272:   }
 273: 
 274:   protected void implOnMalformedInput (CodingErrorAction newAction)
 275:   {
 276:     // default implementation does nothing
 277:   }
 278: 
 279:   protected void implOnUnmappableCharacter (CodingErrorAction newAction)
 280:   {
 281:     // default implementation does nothing
 282:   }
 283: 
 284:   protected void implReplaceWith (byte[] newReplacement)
 285:   {
 286:     // default implementation does nothing
 287:   }
 288: 
 289:   protected void implReset ()
 290:   {
 291:     // default implementation does nothing
 292:   }
 293: 
 294:   public boolean isLegalReplacement (byte[] replacement)
 295:   {
 296:     // TODO: cache the decoder
 297:     // error actions will be REPORT after construction
 298:     CharsetDecoder decoder = charset.newDecoder ();
 299:     ByteBuffer bb = ByteBuffer.wrap (replacement);
 300:     CharBuffer cb
 301:       = CharBuffer.allocate ((int) (replacement.length
 302:                                     * decoder.maxCharsPerByte ()));
 303:     return !decoder.decode (bb, cb, true).isError ();
 304:   }
 305: 
 306:   public CodingErrorAction malformedInputAction ()
 307:   {
 308:     return malformedInputAction;
 309:   }
 310: 
 311:   public final float maxBytesPerChar ()
 312:   {
 313:     return maxBytesPerChar;
 314:   }
 315: 
 316:   public final CharsetEncoder onMalformedInput (CodingErrorAction newAction)
 317:   {
 318:     if (newAction == null)
 319:       throw new IllegalArgumentException ("Null action");
 320: 
 321:     malformedInputAction = newAction;
 322:     implOnMalformedInput (newAction);
 323:     return this;
 324:   }
 325: 
 326:   public CodingErrorAction unmappableCharacterAction ()
 327:   {
 328:     return unmappableCharacterAction;
 329:   }
 330: 
 331:   public final CharsetEncoder onUnmappableCharacter
 332:     (CodingErrorAction newAction)
 333:   {
 334:     if (newAction == null)
 335:       throw new IllegalArgumentException ("Null action");
 336: 
 337:     unmappableCharacterAction = newAction;
 338:     implOnUnmappableCharacter (newAction);
 339:     return this;
 340:   }
 341: 
 342:   public final byte[] replacement ()
 343:   {
 344:     return replacement;
 345:   }
 346: 
 347:   public final CharsetEncoder replaceWith (byte[] newReplacement)
 348:   {
 349:     if (newReplacement == null)
 350:       throw new IllegalArgumentException ("Null replacement");
 351:     if (newReplacement.length == 0)
 352:       throw new IllegalArgumentException ("Empty replacement");
 353:     // XXX: what about maxBytesPerChar?
 354: 
 355:       if (!isLegalReplacement (newReplacement))
 356:         throw new IllegalArgumentException ("Illegal replacement");
 357: 
 358:     this.replacement = newReplacement;
 359:     implReplaceWith (newReplacement);
 360:     return this;
 361:   }
 362: 
 363:   public final CharsetEncoder reset ()
 364:   {
 365:     state = STATE_RESET;
 366:     implReset ();
 367:     return this;
 368:   }
 369: }