Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   package divconq.json3.base;
   
   import java.io.*;
   import java.math.BigDecimal;
   import java.math.BigInteger;
   
   import divconq.json3.*;
Intermediate base class used by all Jackson divconq.json3.JsonParser implementations. Contains most common things that are independent of actual underlying input source.
  
  public abstract class ParserBase extends ParserMinimalBase
  {
      /*
      /**********************************************************
      /* Generic I/O state
      /**********************************************************
       */
  
      /*
       * I/O context for this reader. It handles buffer allocation
       * for the reader.
       */
      final protected IOContext _ioContext;
  
      /*
       * Flag that indicates whether parser is closed or not. Gets
       * set when parser is either closed by explicit call
       * ({@link #close}) or when end-of-input is reached.
       */
      protected boolean _closed;
  
      /*
      /**********************************************************
      /* Current input data
      /**********************************************************
       */
  
      // Note: type of actual buffer depends on sub-class, can't include
  
      /*
       * Pointer to next available character in buffer
       */
      protected int _inputPtr = 0;
  
      /*
       * Index of character after last available one in the buffer.
       */
      protected int _inputEnd = 0;
  
      /*
      /**********************************************************
      /* Current input location information
      /**********************************************************
       */
  
      /*
       * Number of characters/bytes that were contained in previous blocks
       * (blocks that were already processed prior to the current buffer).
       */
      protected long _currInputProcessed = 0L;
  
      /*
       * Current row location of current point in input buffer, starting
       * from 1, if available.
       */
      protected int _currInputRow = 1;
  
      /*
       * Current index of the first character of the current row in input
       * buffer. Needed to calculate column position, if necessary; benefit
       * of not having column itself is that this only has to be updated
       * once per line.
       */
      protected int _currInputRowStart = 0;
  
      /*
      /**********************************************************
      /* Information about starting location of event
      /* Reader is pointing to; updated on-demand
      /**********************************************************
       */
  
      // // // Location info at point when current token was started
  
      /*
       * Total number of bytes/characters read before start of current token.
       * For big (gigabyte-sized) sizes are possible, needs to be long,
       * unlike pointers and sizes related to in-memory buffers.
       */
      protected long _tokenInputTotal = 0; 
  
      /*
      * Input row on which current token starts, 1-based
      */
     protected int _tokenInputRow = 1;
 
     /*
      * Column on input row that current token starts; 0-based (although
      * in the end it'll be converted to 1-based)
      */
     protected int _tokenInputCol = 0;
 
     /*
     /**********************************************************
     /* Parsing state
     /**********************************************************
      */
 
     /*
      * Information about parser context, context in which
      * the next token is to be parsed (root, array, object).
      */
     protected JsonReadContext _parsingContext;
     
     /*
      * Secondary token related to the next token after current one;
      * used if its type is known. This may be value token that
      * follows FIELD_NAME, for example.
      */
     protected JsonToken _nextToken;
 
     /*
     /**********************************************************
     /* Buffer(s) for local name(s) and text content
     /**********************************************************
      */
 
     /*
      * Buffer that contains contents of String values, including
      * field names if necessary (name split across boundary,
      * contains escape sequence, or access needed to char array)
      */
     protected final TextBuffer _textBuffer;
 
     /*
      * Temporary buffer that is needed if field name is accessed
      * using {@link #getTextCharacters} method (instead of String
      * returning alternatives)
      */
     protected char[] _nameCopyBuffer = null;
 
     /*
      * Flag set to indicate whether the field name is available
      * from the name copy buffer or not (in addition to its String
      * representation  being available via read context)
      */
     protected boolean _nameCopied = false;
 
     /*
      * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
      * we better reuse it for remainder of content.
      */
     protected ByteArrayBuilder _byteArrayBuilder = null;
 
     /*
      * We will hold on to decoded binary data, for duration of
      * current event, so that multiple calls to
      * {@link #getBinaryValue} will not need to decode data more
      * than once.
      */
     protected byte[] _binaryValue;
 
     /*
     /**********************************************************
     /* Constants and fields of former 'JsonNumericParserBase'
     /**********************************************************
      */
 
     final protected static int NR_UNKNOWN = 0;
 
     // First, integer types
 
     final protected static int NR_INT = 0x0001;
     final protected static int NR_LONG = 0x0002;
     final protected static int NR_BIGINT = 0x0004;
 
     // And then floating point types
 
     final protected static int NR_DOUBLE = 0x008;
     final protected static int NR_BIGDECIMAL = 0x0010;
 
     // Also, we need some numeric constants
 
     final static BigInteger BI_MIN_INT = BigInteger.valueOf(.);
     final static BigInteger BI_MAX_INT = BigInteger.valueOf(.);
 
     final static BigInteger BI_MIN_LONG = BigInteger.valueOf(.);
     final static BigInteger BI_MAX_LONG = BigInteger.valueOf(.);
     
     final static BigDecimal BD_MIN_LONG = new BigDecimal();
     final static BigDecimal BD_MAX_LONG = new BigDecimal();
 
     final static BigDecimal BD_MIN_INT = new BigDecimal();
     final static BigDecimal BD_MAX_INT = new BigDecimal();
 
     final static long MIN_INT_L = (long.;
     final static long MAX_INT_L = (long.;
 
     // These are not very accurate, but have to do... (for bounds checks)
 
     final static double MIN_LONG_D = (double.;
     final static double MAX_LONG_D = (double.;
 
     final static double MIN_INT_D = (double.;
     final static double MAX_INT_D = (double.;
 
     // Digits, numeric
     final protected static int INT_0 = '0';
     final protected static int INT_9 = '9';
 
     final protected static int INT_MINUS = '-';
     final protected static int INT_PLUS = '+';
 
     final protected static char CHAR_NULL = '\0';
     
     // Numeric value holders: multiple fields used for
     // for efficiency
 
    
Bitfield that indicates which numeric representations have been calculated for the current type
 
     protected int _numTypesValid = ;
 
     // First primitives
 
     protected int _numberInt;
 
     protected long _numberLong;
 
     protected double _numberDouble;
 
     // And then object types
 
     protected BigInteger _numberBigInt;
 
     protected BigDecimal _numberBigDecimal;
 
     // And then other information about value itself
 
    
Flag that indicates whether numeric value has a negative value. That is, whether its textual representation starts with minus character.
 
     protected boolean _numberNegative;

    
Length of integer part of the number, in characters
 
     protected int _intLength;

    
Length of the fractional part (not including decimal point or exponent), in characters. Not used for pure integer values.
 
     protected int _fractLength;

    
Length of the exponent part of the number, if any, not including 'e' marker or sign, just digits. Not used for pure integer values.
 
     protected int _expLength;
 
     /*
     /**********************************************************
     /* Life-cycle
     /**********************************************************
      */
 
     protected ParserBase(IOContext ctxt) {
         super();
          = ctxt;
          = ctxt.constructTextBuffer();
          = JsonReadContext.createRootContext();
     }
 
     /*
     /**********************************************************
     /* JsonParser impl
     /**********************************************************
      */
     
     /*
      * Method that can be called to get the name associated with
      * the current event.
      */
     @Override public String getCurrentName() throws IOException {
         // [JACKSON-395]: start markers require information from parent
         if ( == . ||  == .) {
             JsonReadContext parent = .getParent();
             return parent.getCurrentName();
         }
         return .getCurrentName();
     }
 
     @Override public void overrideCurrentName(String name) {
         // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
         JsonReadContext ctxt = ;
         if ( == . ||  == .) {
             ctxt = ctxt.getParent();
         }
         /* 24-Sep-2013, tatu: Unfortunate, but since we did not expose exceptions,
          *   need to wrap this here
          */
         try {
             ctxt.setCurrentName(name);
         } catch (IOException e) {
             throw new IllegalStateException(e);
         }
     }
     
     @Override public void close() throws IOException {
         if (!) {
              = true;
             try {
                 _closeInput();
             } finally {
                 // as per [JACKSON-324], do in finally block
                 // Also, internal buffer(s) can now be released as well
                 _releaseBuffers();
             }
         }
     }
 
     @Override public boolean isClosed() { return ; }
     @Override public JsonReadContext getParsingContext() { return ; }
 
     /*
      * Method that return the <b>starting</b> location of the current
      * token; that is, position of the first character from input
      * that starts the current token.
      */
     @Override
     public JsonLocation getTokenLocation() {
         return new JsonLocation(.getSourceReference(),
                 -1L, getTokenCharacterOffset(), // bytes, chars
                 getTokenLineNr(),
                 getTokenColumnNr());
     }
 
     /*
      * Method that returns location of the last processed character;
      * usually for error reporting purposes
      */
     @Override
     public JsonLocation getCurrentLocation() {
         int col =  -  + 1; // 1-based
         return new JsonLocation(.getSourceReference(),
                 -1L,  + // bytes, chars
                 col);
     }
 
     /*
     /**********************************************************
     /* Public API, access to token information, text and similar
     /**********************************************************
      */
 
     @Override
     public boolean hasTextCharacters() {
         if ( == .) { return true; } // usually true        
         if ( == .) { return ; }
         return false;
     }
 
     // No embedded objects with base impl...
     @Override public Object getEmbeddedObject() throws IOException { return null; }
     
     /*
     /**********************************************************
     /* Public low-level accessors
     /**********************************************************
      */
 
     public long getTokenCharacterOffset() { return ; }
     public int getTokenLineNr() { return ; }
     public int getTokenColumnNr() {
         // note: value of -1 means "not available"; otherwise convert from 0-based to 1-based
         int col = ;
         return (col < 0) ? col : (col + 1);
     }
 
     /*
     /**********************************************************
     /* Low-level reading, other
     /**********************************************************
      */
 
     protected final void loadMoreGuaranteed() throws IOException {
         if (!loadMore()) { _reportInvalidEOF(); }
     }
     
     /*
     /**********************************************************
     /* Abstract methods needed from sub-classes
     /**********************************************************
      */
 
     protected abstract boolean loadMore() throws IOException;
     protected abstract void _finishString() throws IOException;
     protected abstract void _closeInput() throws IOException;
     
     /*
     /**********************************************************
     /* Low-level reading, other
     /**********************************************************
      */
 
     /*
      * Method called to release internal buffers owned by the base
      * reader. This may be called along with {@link #_closeInput} (for
      * example, when explicitly closing this reader instance), or
      * separately (if need be).
      */
     protected void _releaseBuffers() throws IOException {
         .releaseBuffers();
         char[] buf = ;
         if (buf != null) {
              = null;
             .releaseNameCopyBuffer(buf);
         }
     }
     
     /*
      * Method called when an EOF is encountered between tokens.
      * If so, it may be a legitimate EOF, but only iff there
      * is no open non-root context.
      */
     @Override
     protected void _handleEOF() throws JsonParseException {
         if (!.inRoot()) {
             _reportInvalidEOF(": expected close marker for "+.getTypeDesc()+" (from "+.getStartLocation(.getSourceReference())+")");
         }
     }
 
     /*
      * @since 2.4
      */
     protected final int _eofAsNextChar() throws JsonParseException {
         _handleEOF();
         return -1;
     }
     
     /*
     /**********************************************************
     /* Internal/package methods: Error reporting
     /**********************************************************
      */
     
     protected void _reportMismatchedEndMarker(int actChchar expChthrows JsonParseException {
         _reportError("Unexpected close marker '"+((charactCh)+"': expected '"+expCh+"' (for "+.getTypeDesc()+" starting at "+startDesc+")");
     }
 
     /*
     /**********************************************************
     /* Internal/package methods: shared/reusable builders
     /**********************************************************
      */
     
     {
         if ( == null) {
              = new ByteArrayBuilder();
         } else {
             .reset();
         }
         return ;
     }
 
     /*
     /**********************************************************
     /* Methods from former JsonNumericParserBase
     /**********************************************************
      */
 
     // // // Life-cycle of number-parsing
     
     protected final JsonToken reset(boolean negativeint intLenint fractLenint expLen)
     {
         if (fractLen < 1 && expLen < 1) { // integer
             return resetInt(negativeintLen);
         }
         return resetFloat(negativeintLenfractLenexpLen);
     }
         
     protected final JsonToken resetInt(boolean negativeint intLen)
     {
          = negative;
          = intLen;
          = 0;
          = 0;
          = // to force parsing
         return .;
     }
     
     protected final JsonToken resetFloat(boolean negativeint intLenint fractLenint expLen)
     {
          = negative;
          = intLen;
          = fractLen;
          = expLen;
          = // to force parsing
         return .;
     }
     
     protected final JsonToken resetAsNaN(String valueStrdouble value)
     {
         .resetWithString(valueStr);
          = value;
          = ;
         return .;
     }
     
     /*
     /**********************************************************
     /* Numeric accessors of public API
     /**********************************************************
      */
     
     @Override
     public Number getNumberValue() throws IOException
     {
         if ( == ) {
             _parseNumericValue(); // will also check event type
         }
         // Separate types for int types
         if ( == .) {
             if (( & ) != 0) {
                 return ;
             }
             if (( & ) != 0) {
                 return ;
             }
             if (( & ) != 0) {
                 return ;
             }
             // Shouldn't get this far but if we do
             return ;
         }
     
         /* And then floating point types. But here optimal type
          * needs to be big decimal, to avoid losing any data?
          */
         if (( & ) != 0) {
             return ;
         }
         if (( & ) == 0) { // sanity check
             _throwInternal();
         }
         return ;
     }
     
     @Override
     public NumberType getNumberType() throws IOException
     {
         if ( == ) {
             _parseNumericValue(); // will also check event type
         }
         if ( == .) {
             if (( & ) != 0) {
                 return .;
             }
             if (( & ) != 0) {
                 return .;
             }
             return .;
         }
     
         /* And then floating point types. Here optimal type
          * needs to be big decimal, to avoid losing any data?
          * However... using BD is slow, so let's allow returning
          * double as type if no explicit call has been made to access
          * data as BD?
          */
         if (( & ) != 0) {
             return .;
         }
         return .;
     }
     
     @Override
     public int getIntValue() throws IOException
     {
         if (( & ) == 0) {
             if ( == ) { // not parsed at all
                 _parseNumericValue(); // will also check event type
             }
             if (( & ) == 0) { // wasn't an int natively?
                 convertNumberToInt(); // let's make it so, if possible
             }
         }
         return ;
     }
     
     @Override
     public long getLongValue() throws IOException
     {
         if (( & ) == 0) {
             if ( == ) {
                 _parseNumericValue();
             }
             if (( & ) == 0) {
                 convertNumberToLong();
             }
         }
         return ;
     }
     
     @Override
     public BigInteger getBigIntegerValue() throws IOException
     {
         if (( & ) == 0) {
             if ( == ) {
                 _parseNumericValue();
             }
             if (( & ) == 0) {
                 convertNumberToBigInteger();
             }
         }
         return ;
     }
     
     @Override
     public float getFloatValue() throws IOException
     {
         double value = getDoubleValue();
         /* 22-Jan-2009, tatu: Bounds/range checks would be tricky
          *   here, so let's not bother even trying...
          */
         /*
         if (value < -Float.MAX_VALUE || value > MAX_FLOAT_D) {
             _reportError("Numeric value ("+getText()+") out of range of Java float");
         }
         */
         return (floatvalue;
     }
     
     @Override
     public double getDoubleValue() throws IOException
     {
         if (( & ) == 0) {
             if ( == ) {
                 _parseNumericValue();
             }
             if (( & ) == 0) {
                 convertNumberToDouble();
             }
         }
         return ;
     }
     
     @Override
     public BigDecimal getDecimalValue() throws IOException
     {
         if (( & ) == 0) {
             if ( == ) {
                 _parseNumericValue();
             }
             if (( & ) == 0) {
                 convertNumberToBigDecimal();
             }
         }
         return ;
     }
 
     /*
     /**********************************************************
     /* Conversion from textual to numeric representation
     /**********************************************************
      */
     
     /*
      * Method that will parse actual numeric value out of a syntactically
      * valid number value. Type it will parse into depends on whether
      * it is a floating point number, as well as its magnitude: smallest
      * legal type (of ones available) is used for efficiency.
      *
      * @param expType Numeric type that we will immediately need, if any;
      *   mostly necessary to optimize handling of floating point numbers
      */
     protected void _parseNumericValue(int expTypethrows IOException
     {
         // Int or float?
         if ( == .) {
             char[] buf = .getTextBuffer();
             int offset = .getTextOffset();
             int len = ;
             if () {
                 ++offset;
             }
             if (len <= 9) { // definitely fits in int
                 int i = NumberInput.parseInt(bufoffsetlen);
                  =  ? -i : i;
                  = ;
                 return;
             }
             if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls
                 long l = NumberInput.parseLong(bufoffsetlen);
                 if () {
                     l = -l;
                 }
                 // [JACKSON-230] Could still fit in int, need to check
                 if (len == 10) {
                     if () {
                         if (l >= ) {
                              = (intl;
                              = ;
                             return;
                         }
                     } else {
                         if (l <= ) {
                              = (intl;
                              = ;
                             return;
                         }
                     }
                 }
                  = l;
                  = ;
                 return;
             }
             _parseSlowInt(expTypebufoffsetlen);
             return;
         }
         if ( == .) {
             _parseSlowFloat(expType);
             return;
         }
         _reportError("Current token ("++") not numeric, can not use numeric value accessors");
     }
     
     private void _parseSlowFloat(int expTypethrows IOException
     {
         /* Nope: floating point. Here we need to be careful to get
          * optimal parsing strategy: choice is between accurate but
          * slow (BigDecimal) and lossy but fast (Double). For now
          * let's only use BD when explicitly requested -- it can
          * still be constructed correctly at any point since we do
          * retain textual representation
          */
         try {
             if (expType == ) {
                  = .contentsAsDecimal();
                  = ;
             } else {
                 // Otherwise double has to do
                  = .contentsAsDouble();
                  = ;
             }
         } catch (NumberFormatException nex) {
             // Can this ever occur? Due to overflow, maybe?
             _wrapError("Malformed numeric value '"+.contentsAsString()+"'"nex);
         }
     }
     
     private void _parseSlowInt(int expTypechar[] bufint offsetint lenthrows IOException
     {
         String numStr = .contentsAsString();
         try {
             // [JACKSON-230] Some long cases still...
             if (NumberInput.inLongRange(bufoffsetlen)) {
                 // Probably faster to construct a String, call parse, than to use BigInteger
                  = Long.parseLong(numStr);
                  = ;
             } else {
                 // nope, need the heavy guns... (rare case)
                  = new BigInteger(numStr);
                  = ;
             }
         } catch (NumberFormatException nex) {
             // Can this ever occur? Due to overflow, maybe?
             _wrapError("Malformed numeric value '"+numStr+"'"nex);
         }
     }
     
     /*
     /**********************************************************
     /* Numeric conversions
     /**********************************************************
      */    
     
     protected void convertNumberToInt() throws IOException
     {
         // First, converting from long ought to be easy
         if (( & ) != 0) {
             // Let's verify it's lossless conversion by simple roundtrip
             int result = (int;
             if (((longresult) != ) {
                 _reportError("Numeric value ("+getText()+") out of range of int");
             }
              = result;
         } else if (( & ) != 0) {
             if (.compareTo() > 0 
                     || .compareTo() < 0) {
                 reportOverflowInt();
             }
              = .intValue();
         } else if (( & ) != 0) {
             // Need to check boundaries
             if ( <  ||  > ) {
                 reportOverflowInt();
             }
              = (int;
         } else if (( & ) != 0) {
             if (.compareTo() > 0 
                 || .compareTo() < 0) {
                 reportOverflowInt();
             }
              = .intValue();
         } else {
             _throwInternal();
         }
          |= ;
     }
     
     protected void convertNumberToLong() throws IOException
     {
         if (( & ) != 0) {
              = (long;
         } else if (( & ) != 0) {
             if (.compareTo() > 0 
                     || .compareTo() < 0) {
                 reportOverflowLong();
             }
              = .longValue();
         } else if (( & ) != 0) {
             // Need to check boundaries
             if ( <  ||  > ) {
                 reportOverflowLong();
             }
              = (long;
         } else if (( & ) != 0) {
             if (.compareTo() > 0 
                 || .compareTo() < 0) {
                 reportOverflowLong();
             }
              = .longValue();
         } else {
             _throwInternal();
         }
          |= ;
     }
     
     protected void convertNumberToBigInteger() throws IOException
     {
         if (( & ) != 0) {
             // here it'll just get truncated, no exceptions thrown
              = .toBigInteger();
         } else if (( & ) != 0) {
              = BigInteger.valueOf();
         } else if (( & ) != 0) {
              = BigInteger.valueOf();
         } else if (( & ) != 0) {
              = BigDecimal.valueOf().toBigInteger();
         } else {
             _throwInternal();
         }
          |= ;
     }
     
     protected void convertNumberToDouble() throws IOException
     {
         /* 05-Aug-2008, tatus: Important note: this MUST start with
          *   more accurate representations, since we don't know which
          *   value is the original one (others get generated when
          *   requested)
          */
     
         if (( & ) != 0) {
              = .doubleValue();
         } else if (( & ) != 0) {
              = .doubleValue();
         } else if (( & ) != 0) {
              = (double;
         } else if (( & ) != 0) {
              = (double;
         } else {
             _throwInternal();
         }
          |= ;
     }
     
     protected void convertNumberToBigDecimal() throws IOException
     {
         /* 05-Aug-2008, tatus: Important note: this MUST start with
          *   more accurate representations, since we don't know which
          *   value is the original one (others get generated when
          *   requested)
          */
     
         if (( & ) != 0) {
             /* Let's actually parse from String representation,
              * to avoid rounding errors that non-decimal floating operations
              * would incur
              */
              = NumberInput.parseBigDecimal(getText());
         } else if (( & ) != 0) {
              = new BigDecimal();
         } else if (( & ) != 0) {
              = BigDecimal.valueOf();
         } else if (( & ) != 0) {
              = BigDecimal.valueOf();
         } else {
             _throwInternal();
         }
          |= ;
     }
     
     /*
     /**********************************************************
     /* Number handling exceptions
     /**********************************************************
      */    
     
     protected void reportUnexpectedNumberChar(int chString commentthrows JsonParseException {
         String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value";
         if (comment != null) {
             msg += ": "+comment;
         }
         _reportError(msg);
     }
     
     protected void reportInvalidNumber(String msgthrows JsonParseException {
         _reportError("Invalid numeric value: "+msg);
     }
 
     protected void reportOverflowInt() throws IOException {
         _reportError("Numeric value ("+getText()+") out of range of int ("+.+" - "+.+")");
     }
     
     protected void reportOverflowLong() throws IOException {
         _reportError("Numeric value ("+getText()+") out of range of long ("+.+" - "+.+")");
     }    
 
     /*
     /**********************************************************
     /* Base64 handling support
     /**********************************************************
      */
 
     /*
      * Method that sub-classes must implement to support escaped sequences
      * in base64-encoded sections.
      * Sub-classes that do not need base64 support can leave this as is
      */
     protected char _decodeEscaped() throws IOException {
         throw new UnsupportedOperationException();
     }
     
     protected final int _decodeBase64Escape(Base64Variant b64variantint chint indexthrows IOException
     {
         // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
         if (ch != '\\') {
             throw reportInvalidBase64Char(b64variantchindex);
         }
         int unescaped = _decodeEscaped();
         // if white space, skip if first triplet; otherwise errors
         if (unescaped <= ) {
             if (index == 0) { // whitespace only allowed to be skipped between triplets
                 return -1;
             }
         }
         // otherwise try to find actual triplet value
         int bits = b64variant.decodeBase64Char(unescaped);
         if (bits < 0) {
             throw reportInvalidBase64Char(b64variantunescapedindex);
         }
         return bits;
     }
     
     protected final int _decodeBase64Escape(Base64Variant b64variantchar chint indexthrows IOException
     {
         // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
         if (ch != '\\') {
             throw reportInvalidBase64Char(b64variantchindex);
         }
         char unescaped = _decodeEscaped();
         // if white space, skip if first triplet; otherwise errors
         if (unescaped <= ) {
             if (index == 0) { // whitespace only allowed to be skipped between triplets
                 return -1;
             }
         }
         // otherwise try to find actual triplet value
         int bits = b64variant.decodeBase64Char(unescaped);
         if (bits < 0) {
             throw reportInvalidBase64Char(b64variantunescapedindex);
         }
        return bits;
    }
    
    protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variantint chint bindexthrows IllegalArgumentException {
        return reportInvalidBase64Char(b64variantchbindexnull);
    }
    // @param bindex Relative index within base64 character unit; between 0
    //   and 3 (as unit has exactly 4 characters)
    protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variantint chint bindexString msgthrows IllegalArgumentException {
        String base;
        if (ch <= ) {
            base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
        } else if (b64variant.usesPaddingChar(ch)) {
            base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
        } else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {
            // Not sure if we can really get here... ? (most illegal xml chars are caught at lower level)
            base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content";
        } else {
            base = "Illegal character '"+((char)ch)+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
        }
        if (msg != null) {
            base = base + ": " + msg;
        }
        return new IllegalArgumentException(base);
    }
    
    public final static void _throwInternal() {
        throw new RuntimeException("Internal error: this code path should never get executed");
    }