mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			370 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* 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;
 | |
|   }
 | |
| }
 |