*/
public class FastStringWriter extends Writer {
@@ -94,8 +97,20 @@ public class FastStringWriter extends Writer {
}
- public void close() throws IOException {
-
+ static int MAX_SIZE = 1024 * 128;
+
+ /**
+ * 由 StringWriter.close() 改造而来,原先该方法中无任何代码 ,改造如下:
+ * 1:去掉 throws IOException
+ * 2:添加 buf 空间释放处理逻辑
+ * 3:添加 buf.setLength(0),以便于配合 ThreadLocal 回收利用
+ */
+ public void close() {
+ if (buf.length() > MAX_SIZE) {
+ buf = new StringBuilder(); // 释放空间占用过大的 buf
+ } else {
+ buf.setLength(0);
+ }
}
}
diff --git a/src/main/java/com/jfinal/template/io/FloatingDecimal.java b/src/main/java/com/jfinal/template/io/FloatingDecimal.java
new file mode 100644
index 0000000..bc17ea5
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/FloatingDecimal.java
@@ -0,0 +1,1306 @@
+/*
+ * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ */
+
+package com.jfinal.template.io;
+
+public class FloatingDecimal{
+ boolean isExceptional;
+ boolean isNegative;
+ int decExponent;
+ char digits[];
+ int nDigits;
+ int bigIntExp;
+ int bigIntNBits;
+ boolean mustSetRoundDir = false;
+ boolean fromHex = false;
+ int roundDir = 0; // set by doubleValue
+
+ /*
+ * Constants of the implementation
+ * Most are IEEE-754 related.
+ * (There are more really boring constants at the end.)
+ */
+ static final long signMask = 0x8000000000000000L;
+ static final long expMask = 0x7ff0000000000000L;
+ static final long fractMask= ~(signMask|expMask);
+ static final int expShift = 52;
+ static final int expBias = 1023;
+ static final long fractHOB = ( 1L< 0L ) { // i.e. while ((v&highbit) == 0L )
+ v <<= 1;
+ }
+
+ int n = 0;
+ while (( v & lowbytes ) != 0L ){
+ v <<= 8;
+ n += 8;
+ }
+ while ( v != 0L ){
+ v <<= 1;
+ n += 1;
+ }
+ return n;
+ }
+
+ /*
+ * Keep big powers of 5 handy for future reference.
+ */
+ private static FDBigInt b5p[];
+
+ private static synchronized FDBigInt
+ big5pow( int p ){
+ assert p >= 0 : p; // negative power of 5
+ if ( b5p == null ){
+ b5p = new FDBigInt[ p+1 ];
+ }else if (b5p.length <= p ){
+ FDBigInt t[] = new FDBigInt[ p+1 ];
+ System.arraycopy( b5p, 0, t, 0, b5p.length );
+ b5p = t;
+ }
+ if ( b5p[p] != null )
+ return b5p[p];
+ else if ( p < small5pow.length )
+ return b5p[p] = new FDBigInt( small5pow[p] );
+ else if ( p < long5pow.length )
+ return b5p[p] = new FDBigInt( long5pow[p] );
+ else {
+ // construct the value.
+ // recursively.
+ int q, r;
+ // in order to compute 5^p,
+ // compute its square root, 5^(p/2) and square.
+ // or, let q = p / 2, r = p -q, then
+ // 5^p = 5^(q+r) = 5^q * 5^r
+ q = p >> 1;
+ r = p - q;
+ FDBigInt bigq = b5p[q];
+ if ( bigq == null )
+ bigq = big5pow ( q );
+ if ( r < small5pow.length ){
+ return (b5p[p] = bigq.mult( small5pow[r] ) );
+ }else{
+ FDBigInt bigr = b5p[ r ];
+ if ( bigr == null )
+ bigr = big5pow( r );
+ return (b5p[p] = bigq.mult( bigr ) );
+ }
+ }
+ }
+
+ //
+ // a common operation
+ //
+ private static FDBigInt
+ multPow52( FDBigInt v, int p5, int p2 ){
+ if ( p5 != 0 ){
+ if ( p5 < small5pow.length ){
+ v = v.mult( small5pow[p5] );
+ } else {
+ v = v.mult( big5pow( p5 ) );
+ }
+ }
+ if ( p2 != 0 ){
+ v.lshiftMe( p2 );
+ }
+ return v;
+ }
+
+ //
+ // another common operation
+ //
+ private static FDBigInt
+ constructPow52( int p5, int p2 ){
+ FDBigInt v = new FDBigInt( big5pow( p5 ) );
+ if ( p2 != 0 ){
+ v.lshiftMe( p2 );
+ }
+ return v;
+ }
+
+ /*
+ * This is the easy subcase --
+ * all the significant bits, after scaling, are held in lvalue.
+ * negSign and decExponent tell us what processing and scaling
+ * has already been done. Exceptional cases have already been
+ * stripped out.
+ * In particular:
+ * lvalue is a finite number (not Inf, nor NaN)
+ * lvalue > 0L (not zero, nor negative).
+ *
+ * The only reason that we develop the digits here, rather than
+ * calling on Long.toString() is that we can do it a little faster,
+ * and besides want to treat trailing 0s specially. If Long.toString
+ * changes, we should re-evaluate this strategy!
+ */
+ private void
+ developLongDigits( int decExponent, long lvalue, long insignificant ){
+ char digits[];
+ int ndigits;
+ int digitno;
+ int c;
+ //
+ // Discard non-significant low-order bits, while rounding,
+ // up to insignificant value.
+ int i;
+ for ( i = 0; insignificant >= 10L; i++ )
+ insignificant /= 10L;
+ if ( i != 0 ){
+ long pow10 = long5pow[i] << i; // 10^i == 5^i * 2^i;
+ long residue = lvalue % pow10;
+ lvalue /= pow10;
+ decExponent += i;
+ if ( residue >= (pow10>>1) ){
+ // round up based on the low-order bits we're discarding
+ lvalue++;
+ }
+ }
+ if ( lvalue <= Integer.MAX_VALUE ){
+ assert lvalue > 0L : lvalue; // lvalue <= 0
+ // even easier subcase!
+ // can do int arithmetic rather than long!
+ int ivalue = (int)lvalue;
+ ndigits = 10;
+ digits = (char[])(perThreadBuffer.get());
+ digitno = ndigits-1;
+ c = ivalue%10;
+ ivalue /= 10;
+ while ( c == 0 ){
+ decExponent++;
+ c = ivalue%10;
+ ivalue /= 10;
+ }
+ while ( ivalue != 0){
+ digits[digitno--] = (char)(c+'0');
+ decExponent++;
+ c = ivalue%10;
+ ivalue /= 10;
+ }
+ digits[digitno] = (char)(c+'0');
+ } else {
+ // same algorithm as above (same bugs, too )
+ // but using long arithmetic.
+ ndigits = 20;
+ digits = (char[])(perThreadBuffer.get());
+ digitno = ndigits-1;
+ c = (int)(lvalue%10L);
+ lvalue /= 10L;
+ while ( c == 0 ){
+ decExponent++;
+ c = (int)(lvalue%10L);
+ lvalue /= 10L;
+ }
+ while ( lvalue != 0L ){
+ digits[digitno--] = (char)(c+'0');
+ decExponent++;
+ c = (int)(lvalue%10L);
+ lvalue /= 10;
+ }
+ digits[digitno] = (char)(c+'0');
+ }
+ char result [];
+ ndigits -= digitno;
+ result = new char[ ndigits ];
+ System.arraycopy( digits, digitno, result, 0, ndigits );
+ this.digits = result;
+ this.decExponent = decExponent+1;
+ this.nDigits = ndigits;
+ }
+
+ //
+ // add one to the least significant digit.
+ // in the unlikely event there is a carry out,
+ // deal with it.
+ // assert that this will only happen where there
+ // is only one digit, e.g. (float)1e-44 seems to do it.
+ //
+ private void
+ roundup(){
+ int i;
+ int q = digits[ i = (nDigits-1)];
+ if ( q == '9' ){
+ while ( q == '9' && i > 0 ){
+ digits[i] = '0';
+ q = digits[--i];
+ }
+ if ( q == '9' ){
+ // carryout! High-order 1, rest 0s, larger exp.
+ decExponent += 1;
+ digits[0] = '1';
+ return;
+ }
+ // else fall through.
+ }
+ digits[i] = (char)(q+1);
+ }
+
+ /*
+ * FIRST IMPORTANT CONSTRUCTOR: DOUBLE
+ */
+ public FloatingDecimal( double d )
+ {
+ long dBits = Double.doubleToLongBits( d );
+ long fractBits;
+ int binExp;
+ int nSignificantBits;
+
+ // discover and delete sign
+ if ( (dBits&signMask) != 0 ){
+ isNegative = true;
+ dBits ^= signMask;
+ } else {
+ isNegative = false;
+ }
+ // Begin to unpack
+ // Discover obvious special cases of NaN and Infinity.
+ binExp = (int)( (dBits&expMask) >> expShift );
+ fractBits = dBits&fractMask;
+ if ( binExp == (int)(expMask>>expShift) ) {
+ isExceptional = true;
+ if ( fractBits == 0L ){
+ digits = infinity;
+ } else {
+ digits = notANumber;
+ isNegative = false; // NaN has no sign!
+ }
+ nDigits = digits.length;
+ return;
+ }
+ isExceptional = false;
+ // Finish unpacking
+ // Normalize denormalized numbers.
+ // Insert assumed high-order bit for normalized numbers.
+ // Subtract exponent bias.
+ if ( binExp == 0 ){
+ if ( fractBits == 0L ){
+ // not a denorm, just a 0!
+ decExponent = 0;
+ digits = zero;
+ nDigits = 1;
+ return;
+ }
+ while ( (fractBits&fractHOB) == 0L ){
+ fractBits <<= 1;
+ binExp -= 1;
+ }
+ nSignificantBits = expShift + binExp +1; // recall binExp is - shift count.
+ binExp += 1;
+ } else {
+ fractBits |= fractHOB;
+ nSignificantBits = expShift+1;
+ }
+ binExp -= expBias;
+ // call the routine that actually does all the hard work.
+ dtoa( binExp, fractBits, nSignificantBits );
+ }
+
+ /*
+ * SECOND IMPORTANT CONSTRUCTOR: SINGLE
+ */
+ public FloatingDecimal( float f )
+ {
+ int fBits = Float.floatToIntBits( f );
+ int fractBits;
+ int binExp;
+ int nSignificantBits;
+
+ // discover and delete sign
+ if ( (fBits&singleSignMask) != 0 ){
+ isNegative = true;
+ fBits ^= singleSignMask;
+ } else {
+ isNegative = false;
+ }
+ // Begin to unpack
+ // Discover obvious special cases of NaN and Infinity.
+ binExp = (int)( (fBits&singleExpMask) >> singleExpShift );
+ fractBits = fBits&singleFractMask;
+ if ( binExp == (int)(singleExpMask>>singleExpShift) ) {
+ isExceptional = true;
+ if ( fractBits == 0L ){
+ digits = infinity;
+ } else {
+ digits = notANumber;
+ isNegative = false; // NaN has no sign!
+ }
+ nDigits = digits.length;
+ return;
+ }
+ isExceptional = false;
+ // Finish unpacking
+ // Normalize denormalized numbers.
+ // Insert assumed high-order bit for normalized numbers.
+ // Subtract exponent bias.
+ if ( binExp == 0 ){
+ if ( fractBits == 0 ){
+ // not a denorm, just a 0!
+ decExponent = 0;
+ digits = zero;
+ nDigits = 1;
+ return;
+ }
+ while ( (fractBits&singleFractHOB) == 0 ){
+ fractBits <<= 1;
+ binExp -= 1;
+ }
+ nSignificantBits = singleExpShift + binExp +1; // recall binExp is - shift count.
+ binExp += 1;
+ } else {
+ fractBits |= singleFractHOB;
+ nSignificantBits = singleExpShift+1;
+ }
+ binExp -= singleExpBias;
+ // call the routine that actually does all the hard work.
+ dtoa( binExp, ((long)fractBits)<<(expShift-singleExpShift), nSignificantBits );
+ }
+
+ private void
+ dtoa( int binExp, long fractBits, int nSignificantBits )
+ {
+ int nFractBits; // number of significant bits of fractBits;
+ int nTinyBits; // number of these to the right of the point.
+ int decExp;
+
+ // Examine number. Determine if it is an easy case,
+ // which we can do pretty trivially using float/long conversion,
+ // or whether we must do real work.
+ nFractBits = countBits( fractBits );
+ nTinyBits = Math.max( 0, nFractBits - binExp - 1 );
+ if ( binExp <= maxSmallBinExp && binExp >= minSmallBinExp ){
+ // Look more closely at the number to decide if,
+ // with scaling by 10^nTinyBits, the result will fit in
+ // a long.
+ if ( (nTinyBits < long5pow.length) && ((nFractBits + n5bits[nTinyBits]) < 64 ) ){
+ /*
+ * We can do this:
+ * take the fraction bits, which are normalized.
+ * (a) nTinyBits == 0: Shift left or right appropriately
+ * to align the binary point at the extreme right, i.e.
+ * where a long int point is expected to be. The integer
+ * result is easily converted to a string.
+ * (b) nTinyBits > 0: Shift right by expShift-nFractBits,
+ * which effectively converts to long and scales by
+ * 2^nTinyBits. Then multiply by 5^nTinyBits to
+ * complete the scaling. We know this won't overflow
+ * because we just counted the number of bits necessary
+ * in the result. The integer you get from this can
+ * then be converted to a string pretty easily.
+ */
+ long halfULP;
+ if ( nTinyBits == 0 ) {
+ if ( binExp > nSignificantBits ){
+ halfULP = 1L << ( binExp-nSignificantBits-1);
+ } else {
+ halfULP = 0L;
+ }
+ if ( binExp >= expShift ){
+ fractBits <<= (binExp-expShift);
+ } else {
+ fractBits >>>= (expShift-binExp) ;
+ }
+ developLongDigits( 0, fractBits, halfULP );
+ return;
+ }
+ /*
+ * The following causes excess digits to be printed
+ * out in the single-float case. Our manipulation of
+ * halfULP here is apparently not correct. If we
+ * better understand how this works, perhaps we can
+ * use this special case again. But for the time being,
+ * we do not.
+ * else {
+ * fractBits >>>= expShift+1-nFractBits;
+ * fractBits *= long5pow[ nTinyBits ];
+ * halfULP = long5pow[ nTinyBits ] >> (1+nSignificantBits-nFractBits);
+ * developLongDigits( -nTinyBits, fractBits, halfULP );
+ * return;
+ * }
+ */
+ }
+ }
+ /*
+ * This is the hard case. We are going to compute large positive
+ * integers B and S and integer decExp, s.t.
+ * d = ( B / S ) * 10^decExp
+ * 1 <= B / S < 10
+ * Obvious choices are:
+ * decExp = floor( log10(d) )
+ * B = d * 2^nTinyBits * 10^max( 0, -decExp )
+ * S = 10^max( 0, decExp) * 2^nTinyBits
+ * (noting that nTinyBits has already been forced to non-negative)
+ * I am also going to compute a large positive integer
+ * M = (1/2^nSignificantBits) * 2^nTinyBits * 10^max( 0, -decExp )
+ * i.e. M is (1/2) of the ULP of d, scaled like B.
+ * When we iterate through dividing B/S and picking off the
+ * quotient bits, we will know when to stop when the remainder
+ * is <= M.
+ *
+ * We keep track of powers of 2 and powers of 5.
+ */
+
+ /*
+ * Estimate decimal exponent. (If it is small-ish,
+ * we could double-check.)
+ *
+ * First, scale the mantissa bits such that 1 <= d2 < 2.
+ * We are then going to estimate
+ * log10(d2) ~=~ (d2-1.5)/1.5 + log(1.5)
+ * and so we can estimate
+ * log10(d) ~=~ log10(d2) + binExp * log10(2)
+ * take the floor and call it decExp.
+ * FIXME -- use more precise constants here. It costs no more.
+ */
+ double d2 = Double.longBitsToDouble(
+ expOne | ( fractBits &~ fractHOB ) );
+ decExp = (int)Math.floor(
+ (d2-1.5D)*0.289529654D + 0.176091259 + (double)binExp * 0.301029995663981 );
+ int B2, B5; // powers of 2 and powers of 5, respectively, in B
+ int S2, S5; // powers of 2 and powers of 5, respectively, in S
+ int M2, M5; // powers of 2 and powers of 5, respectively, in M
+ int Bbits; // binary digits needed to represent B, approx.
+ int tenSbits; // binary digits needed to represent 10*S, approx.
+ FDBigInt Sval, Bval, Mval;
+
+ B5 = Math.max( 0, -decExp );
+ B2 = B5 + nTinyBits + binExp;
+
+ S5 = Math.max( 0, decExp );
+ S2 = S5 + nTinyBits;
+
+ M5 = B5;
+ M2 = B2 - nSignificantBits;
+
+ /*
+ * the long integer fractBits contains the (nFractBits) interesting
+ * bits from the mantissa of d ( hidden 1 added if necessary) followed
+ * by (expShift+1-nFractBits) zeros. In the interest of compactness,
+ * I will shift out those zeros before turning fractBits into a
+ * FDBigInt. The resulting whole number will be
+ * d * 2^(nFractBits-1-binExp).
+ */
+ fractBits >>>= (expShift+1-nFractBits);
+ B2 -= nFractBits-1;
+ int common2factor = Math.min( B2, S2 );
+ B2 -= common2factor;
+ S2 -= common2factor;
+ M2 -= common2factor;
+
+ /*
+ * HACK!! For exact powers of two, the next smallest number
+ * is only half as far away as we think (because the meaning of
+ * ULP changes at power-of-two bounds) for this reason, we
+ * hack M2. Hope this works.
+ */
+ if ( nFractBits == 1 )
+ M2 -= 1;
+
+ if ( M2 < 0 ){
+ // oops.
+ // since we cannot scale M down far enough,
+ // we must scale the other values up.
+ B2 -= M2;
+ S2 -= M2;
+ M2 = 0;
+ }
+ /*
+ * Construct, Scale, iterate.
+ * Some day, we'll write a stopping test that takes
+ * account of the asymmetry of the spacing of floating-point
+ * numbers below perfect powers of 2
+ * 26 Sept 96 is not that day.
+ * So we use a symmetric test.
+ */
+ char digits[] = this.digits = new char[18];
+ int ndigit = 0;
+ boolean low, high;
+ long lowDigitDifference;
+ int q;
+
+ /*
+ * Detect the special cases where all the numbers we are about
+ * to compute will fit in int or long integers.
+ * In these cases, we will avoid doing FDBigInt arithmetic.
+ * We use the same algorithms, except that we "normalize"
+ * our FDBigInts before iterating. This is to make division easier,
+ * as it makes our fist guess (quotient of high-order words)
+ * more accurate!
+ *
+ * Some day, we'll write a stopping test that takes
+ * account of the asymmetry of the spacing of floating-point
+ * numbers below perfect powers of 2
+ * 26 Sept 96 is not that day.
+ * So we use a symmetric test.
+ */
+ Bbits = nFractBits + B2 + (( B5 < n5bits.length )? n5bits[B5] : ( B5*3 ));
+ tenSbits = S2+1 + (( (S5+1) < n5bits.length )? n5bits[(S5+1)] : ( (S5+1)*3 ));
+ if ( Bbits < 64 && tenSbits < 64){
+ if ( Bbits < 32 && tenSbits < 32){
+ // wa-hoo! They're all ints!
+ int b = ((int)fractBits * small5pow[B5] ) << B2;
+ int s = small5pow[S5] << S2;
+ int m = small5pow[M5] << M2;
+ int tens = s * 10;
+ /*
+ * Unroll the first iteration. If our decExp estimate
+ * was too high, our first quotient will be zero. In this
+ * case, we discard it and decrement decExp.
+ */
+ ndigit = 0;
+ q = b / s;
+ b = 10 * ( b % s );
+ m *= 10;
+ low = (b < m );
+ high = (b+m > tens );
+ assert q < 10 : q; // excessively large digit
+ if ( (q == 0) && ! high ){
+ // oops. Usually ignore leading zero.
+ decExp--;
+ } else {
+ digits[ndigit++] = (char)('0' + q);
+ }
+ /*
+ * HACK! Java spec sez that we always have at least
+ * one digit after the . in either F- or E-form output.
+ * Thus we will need more than one digit if we're using
+ * E-form
+ */
+ if ( decExp < -3 || decExp >= 8 ){
+ high = low = false;
+ }
+ while( ! low && ! high ){
+ q = b / s;
+ b = 10 * ( b % s );
+ m *= 10;
+ assert q < 10 : q; // excessively large digit
+ if ( m > 0L ){
+ low = (b < m );
+ high = (b+m > tens );
+ } else {
+ // hack -- m might overflow!
+ // in this case, it is certainly > b,
+ // which won't
+ // and b+m > tens, too, since that has overflowed
+ // either!
+ low = true;
+ high = true;
+ }
+ digits[ndigit++] = (char)('0' + q);
+ }
+ lowDigitDifference = (b<<1) - tens;
+ } else {
+ // still good! they're all longs!
+ long b = (fractBits * long5pow[B5] ) << B2;
+ long s = long5pow[S5] << S2;
+ long m = long5pow[M5] << M2;
+ long tens = s * 10L;
+ /*
+ * Unroll the first iteration. If our decExp estimate
+ * was too high, our first quotient will be zero. In this
+ * case, we discard it and decrement decExp.
+ */
+ ndigit = 0;
+ q = (int) ( b / s );
+ b = 10L * ( b % s );
+ m *= 10L;
+ low = (b < m );
+ high = (b+m > tens );
+ assert q < 10 : q; // excessively large digit
+ if ( (q == 0) && ! high ){
+ // oops. Usually ignore leading zero.
+ decExp--;
+ } else {
+ digits[ndigit++] = (char)('0' + q);
+ }
+ /*
+ * HACK! Java spec sez that we always have at least
+ * one digit after the . in either F- or E-form output.
+ * Thus we will need more than one digit if we're using
+ * E-form
+ */
+ if ( decExp < -3 || decExp >= 8 ){
+ high = low = false;
+ }
+ while( ! low && ! high ){
+ q = (int) ( b / s );
+ b = 10 * ( b % s );
+ m *= 10;
+ assert q < 10 : q; // excessively large digit
+ if ( m > 0L ){
+ low = (b < m );
+ high = (b+m > tens );
+ } else {
+ // hack -- m might overflow!
+ // in this case, it is certainly > b,
+ // which won't
+ // and b+m > tens, too, since that has overflowed
+ // either!
+ low = true;
+ high = true;
+ }
+ digits[ndigit++] = (char)('0' + q);
+ }
+ lowDigitDifference = (b<<1) - tens;
+ }
+ } else {
+ FDBigInt tenSval;
+ int shiftBias;
+
+ /*
+ * We really must do FDBigInt arithmetic.
+ * Fist, construct our FDBigInt initial values.
+ */
+ Bval = multPow52( new FDBigInt( fractBits ), B5, B2 );
+ Sval = constructPow52( S5, S2 );
+ Mval = constructPow52( M5, M2 );
+
+
+ // normalize so that division works better
+ Bval.lshiftMe( shiftBias = Sval.normalizeMe() );
+ Mval.lshiftMe( shiftBias );
+ tenSval = Sval.mult( 10 );
+ /*
+ * Unroll the first iteration. If our decExp estimate
+ * was too high, our first quotient will be zero. In this
+ * case, we discard it and decrement decExp.
+ */
+ ndigit = 0;
+ q = Bval.quoRemIteration( Sval );
+ Mval = Mval.mult( 10 );
+ low = (Bval.cmp( Mval ) < 0);
+ high = (Bval.add( Mval ).cmp( tenSval ) > 0 );
+ assert q < 10 : q; // excessively large digit
+ if ( (q == 0) && ! high ){
+ // oops. Usually ignore leading zero.
+ decExp--;
+ } else {
+ digits[ndigit++] = (char)('0' + q);
+ }
+ /*
+ * HACK! Java spec sez that we always have at least
+ * one digit after the . in either F- or E-form output.
+ * Thus we will need more than one digit if we're using
+ * E-form
+ */
+ if ( decExp < -3 || decExp >= 8 ){
+ high = low = false;
+ }
+ while( ! low && ! high ){
+ q = Bval.quoRemIteration( Sval );
+ Mval = Mval.mult( 10 );
+ assert q < 10 : q; // excessively large digit
+ low = (Bval.cmp( Mval ) < 0);
+ high = (Bval.add( Mval ).cmp( tenSval ) > 0 );
+ digits[ndigit++] = (char)('0' + q);
+ }
+ if ( high && low ){
+ Bval.lshiftMe(1);
+ lowDigitDifference = Bval.cmp(tenSval);
+ } else
+ lowDigitDifference = 0L; // this here only for flow analysis!
+ }
+ this.decExponent = decExp+1;
+ this.digits = digits;
+ this.nDigits = ndigit;
+ /*
+ * Last digit gets rounded based on stopping condition.
+ */
+ if ( high ){
+ if ( low ){
+ if ( lowDigitDifference == 0L ){
+ // it's a tie!
+ // choose based on which digits we like.
+ if ( (digits[nDigits-1]&1) != 0 ) roundup();
+ } else if ( lowDigitDifference > 0 ){
+ roundup();
+ }
+ } else {
+ roundup();
+ }
+ }
+ }
+
+ public String
+ toString(){
+ // most brain-dead version
+ StringBuffer result = new StringBuffer( nDigits+8 );
+ if ( isNegative ){ result.append( '-' ); }
+ if ( isExceptional ){
+ result.append( digits, 0, nDigits );
+ } else {
+ result.append( "0.");
+ result.append( digits, 0, nDigits );
+ result.append('e');
+ result.append( decExponent );
+ }
+ return new String(result);
+ }
+
+ public String toJavaFormatString() {
+ char result[] = (char[])(perThreadBuffer.get());
+ int i = getChars(result);
+ return new String(result, 0, i);
+ }
+
+ public int getChars(char[] result) {
+ assert nDigits <= 19 : nDigits; // generous bound on size of nDigits
+ int i = 0;
+ if (isNegative) { result[0] = '-'; i = 1; }
+ if (isExceptional) {
+ System.arraycopy(digits, 0, result, i, nDigits);
+ i += nDigits;
+ } else {
+ if (decExponent > 0 && decExponent < 8) {
+ // print digits.digits.
+ int charLength = Math.min(nDigits, decExponent);
+ System.arraycopy(digits, 0, result, i, charLength);
+ i += charLength;
+ if (charLength < decExponent) {
+ charLength = decExponent-charLength;
+ System.arraycopy(zero, 0, result, i, charLength);
+ i += charLength;
+ result[i++] = '.';
+ result[i++] = '0';
+ } else {
+ result[i++] = '.';
+ if (charLength < nDigits) {
+ int t = nDigits - charLength;
+ System.arraycopy(digits, charLength, result, i, t);
+ i += t;
+ } else {
+ result[i++] = '0';
+ }
+ }
+ } else if (decExponent <=0 && decExponent > -3) {
+ result[i++] = '0';
+ result[i++] = '.';
+ if (decExponent != 0) {
+ System.arraycopy(zero, 0, result, i, -decExponent);
+ i -= decExponent;
+ }
+ System.arraycopy(digits, 0, result, i, nDigits);
+ i += nDigits;
+ } else {
+ result[i++] = digits[0];
+ result[i++] = '.';
+ if (nDigits > 1) {
+ System.arraycopy(digits, 1, result, i, nDigits-1);
+ i += nDigits-1;
+ } else {
+ result[i++] = '0';
+ }
+ result[i++] = 'E';
+ int e;
+ if (decExponent <= 0) {
+ result[i++] = '-';
+ e = -decExponent+1;
+ } else {
+ e = decExponent-1;
+ }
+ // decExponent has 1, 2, or 3, digits
+ if (e <= 9) {
+ result[i++] = (char)(e+'0');
+ } else if (e <= 99) {
+ result[i++] = (char)(e/10 +'0');
+ result[i++] = (char)(e%10 + '0');
+ } else {
+ result[i++] = (char)(e/100+'0');
+ e %= 100;
+ result[i++] = (char)(e/10+'0');
+ result[i++] = (char)(e%10 + '0');
+ }
+ }
+ }
+ return i;
+ }
+
+ // Per-thread buffer for string/stringbuffer conversion
+ @SuppressWarnings("rawtypes")
+ private static ThreadLocal perThreadBuffer = new ThreadLocal() {
+ protected synchronized Object initialValue() {
+ return new char[26];
+ }
+ };
+
+ private static final int small5pow[] = {
+ 1,
+ 5,
+ 5*5,
+ 5*5*5,
+ 5*5*5*5,
+ 5*5*5*5*5,
+ 5*5*5*5*5*5,
+ 5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5*5*5*5*5,
+ 5*5*5*5*5*5*5*5*5*5*5*5*5
+ };
+
+
+ private static final long long5pow[] = {
+ 1L,
+ 5L,
+ 5L*5,
+ 5L*5*5,
+ 5L*5*5*5,
+ 5L*5*5*5*5,
+ 5L*5*5*5*5*5,
+ 5L*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ 5L*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5*5,
+ };
+
+ // approximately ceil( log2( long5pow[i] ) )
+ private static final int n5bits[] = {
+ 0,
+ 3,
+ 5,
+ 7,
+ 10,
+ 12,
+ 14,
+ 17,
+ 19,
+ 21,
+ 24,
+ 26,
+ 28,
+ 31,
+ 33,
+ 35,
+ 38,
+ 40,
+ 42,
+ 45,
+ 47,
+ 49,
+ 52,
+ 54,
+ 56,
+ 59,
+ 61,
+ };
+
+ private static final char infinity[] = { 'I', 'n', 'f', 'i', 'n', 'i', 't', 'y' };
+ private static final char notANumber[] = { 'N', 'a', 'N' };
+ private static final char zero[] = { '0', '0', '0', '0', '0', '0', '0', '0' };
+}
+
+/*
+ * A really, really simple bigint package
+ * tailored to the needs of floating base conversion.
+ */
+class FDBigInt {
+ int nWords; // number of words used
+ int data[]; // value: data[0] is least significant
+
+ public FDBigInt( long v ){
+ data = new int[2];
+ data[0] = (int)v;
+ data[1] = (int)(v>>>32);
+ nWords = (data[1]==0) ? 1 : 2;
+ }
+
+ public FDBigInt( FDBigInt other ){
+ data = new int[nWords = other.nWords];
+ System.arraycopy( other.data, 0, data, 0, nWords );
+ }
+
+ private FDBigInt( int [] d, int n ){
+ data = d;
+ nWords = n;
+ }
+
+ /*
+ * Left shift by c bits.
+ * Shifts this in place.
+ */
+ public void
+ lshiftMe( int c )throws IllegalArgumentException {
+ if ( c <= 0 ){
+ if ( c == 0 )
+ return; // silly.
+ else
+ throw new IllegalArgumentException("negative shift count");
+ }
+ int wordcount = c>>5;
+ int bitcount = c & 0x1f;
+ int anticount = 32-bitcount;
+ int t[] = data;
+ int s[] = data;
+ if ( nWords+wordcount+1 > t.length ){
+ // reallocate.
+ t = new int[ nWords+wordcount+1 ];
+ }
+ int target = nWords+wordcount;
+ int src = nWords-1;
+ if ( bitcount == 0 ){
+ // special hack, since an anticount of 32 won't go!
+ System.arraycopy( s, 0, t, wordcount, nWords );
+ target = wordcount-1;
+ } else {
+ t[target--] = s[src]>>>anticount;
+ while ( src >= 1 ){
+ t[target--] = (s[src]<>>anticount);
+ }
+ t[target--] = s[src]<= 0 ){
+ t[target--] = 0;
+ }
+ data = t;
+ nWords += wordcount + 1;
+ // may have constructed high-order word of 0.
+ // if so, trim it
+ while ( nWords > 1 && data[nWords-1] == 0 )
+ nWords--;
+ }
+
+ /*
+ * normalize this number by shifting until
+ * the MSB of the number is at 0x08000000.
+ * This is in preparation for quoRemIteration, below.
+ * The idea is that, to make division easier, we want the
+ * divisor to be "normalized" -- usually this means shifting
+ * the MSB into the high words sign bit. But because we know that
+ * the quotient will be 0 < q < 10, we would like to arrange that
+ * the dividend not span up into another word of precision.
+ * (This needs to be explained more clearly!)
+ */
+ public int
+ normalizeMe() throws IllegalArgumentException {
+ int src;
+ int wordcount = 0;
+ int bitcount = 0;
+ int v = 0;
+ for ( src= nWords-1 ; src >= 0 && (v=data[src]) == 0 ; src--){
+ wordcount += 1;
+ }
+ if ( src < 0 ){
+ // oops. Value is zero. Cannot normalize it!
+ throw new IllegalArgumentException("zero value");
+ }
+ /*
+ * In most cases, we assume that wordcount is zero. This only
+ * makes sense, as we try not to maintain any high-order
+ * words full of zeros. In fact, if there are zeros, we will
+ * simply SHORTEN our number at this point. Watch closely...
+ */
+ nWords -= wordcount;
+ /*
+ * Compute how far left we have to shift v s.t. its highest-
+ * order bit is in the right place. Then call lshiftMe to
+ * do the work.
+ */
+ if ( (v & 0xf0000000) != 0 ){
+ // will have to shift up into the next word.
+ // too bad.
+ for( bitcount = 32 ; (v & 0xf0000000) != 0 ; bitcount-- )
+ v >>>= 1;
+ } else {
+ while ( v <= 0x000fffff ){
+ // hack: byte-at-a-time shifting
+ v <<= 8;
+ bitcount += 8;
+ }
+ while ( v <= 0x07ffffff ){
+ v <<= 1;
+ bitcount += 1;
+ }
+ }
+ if ( bitcount != 0 )
+ lshiftMe( bitcount );
+ return bitcount;
+ }
+
+ /*
+ * Multiply a FDBigInt by an int.
+ * Result is a new FDBigInt.
+ */
+ public FDBigInt
+ mult( int iv ) {
+ long v = iv;
+ int r[];
+ long p;
+
+ // guess adequate size of r.
+ r = new int[ ( v * ((long)data[nWords-1]&0xffffffffL) > 0xfffffffL ) ? nWords+1 : nWords ];
+ p = 0L;
+ for( int i=0; i < nWords; i++ ) {
+ p += v * ((long)data[i]&0xffffffffL);
+ r[i] = (int)p;
+ p >>>= 32;
+ }
+ if ( p == 0L){
+ return new FDBigInt( r, nWords );
+ } else {
+ r[nWords] = (int)p;
+ return new FDBigInt( r, nWords+1 );
+ }
+ }
+
+ /*
+ * Multiply a FDBigInt by another FDBigInt.
+ * Result is a new FDBigInt.
+ */
+ public FDBigInt
+ mult( FDBigInt other ){
+ // crudely guess adequate size for r
+ int r[] = new int[ nWords + other.nWords ];
+ int i;
+ // I think I am promised zeros...
+
+ for( i = 0; i < this.nWords; i++ ){
+ long v = (long)this.data[i] & 0xffffffffL; // UNSIGNED CONVERSION
+ long p = 0L;
+ int j;
+ for( j = 0; j < other.nWords; j++ ){
+ p += ((long)r[i+j]&0xffffffffL) + v*((long)other.data[j]&0xffffffffL); // UNSIGNED CONVERSIONS ALL 'ROUND.
+ r[i+j] = (int)p;
+ p >>>= 32;
+ }
+ r[i+j] = (int)p;
+ }
+ // compute how much of r we actually needed for all that.
+ for ( i = r.length-1; i> 0; i--)
+ if ( r[i] != 0 )
+ break;
+ return new FDBigInt( r, i+1 );
+ }
+
+ /*
+ * Add one FDBigInt to another. Return a FDBigInt
+ */
+ public FDBigInt
+ add( FDBigInt other ){
+ int i;
+ int a[], b[];
+ int n, m;
+ long c = 0L;
+ // arrange such that a.nWords >= b.nWords;
+ // n = a.nWords, m = b.nWords
+ if ( this.nWords >= other.nWords ){
+ a = this.data;
+ n = this.nWords;
+ b = other.data;
+ m = other.nWords;
+ } else {
+ a = other.data;
+ n = other.nWords;
+ b = this.data;
+ m = this.nWords;
+ }
+ int r[] = new int[ n ];
+ for ( i = 0; i < n; i++ ){
+ c += (long)a[i] & 0xffffffffL;
+ if ( i < m ){
+ c += (long)b[i] & 0xffffffffL;
+ }
+ r[i] = (int) c;
+ c >>= 32; // signed shift.
+ }
+ if ( c != 0L ){
+ // oops -- carry out -- need longer result.
+ int s[] = new int[ r.length+1 ];
+ System.arraycopy( r, 0, s, 0, r.length );
+ s[i++] = (int)c;
+ return new FDBigInt( s, i );
+ }
+ return new FDBigInt( r, i );
+ }
+
+ /*
+ * Compare FDBigInt with another FDBigInt. Return an integer
+ * >0: this > other
+ * 0: this == other
+ * <0: this < other
+ */
+ public int
+ cmp( FDBigInt other ){
+ int i;
+ if ( this.nWords > other.nWords ){
+ // if any of my high-order words is non-zero,
+ // then the answer is evident
+ int j = other.nWords-1;
+ for ( i = this.nWords-1; i > j ; i-- )
+ if ( this.data[i] != 0 ) return 1;
+ }else if ( this.nWords < other.nWords ){
+ // if any of other's high-order words is non-zero,
+ // then the answer is evident
+ int j = this.nWords-1;
+ for ( i = other.nWords-1; i > j ; i-- )
+ if ( other.data[i] != 0 ) return -1;
+ } else{
+ i = this.nWords-1;
+ }
+ for ( ; i > 0 ; i-- )
+ if ( this.data[i] != other.data[i] )
+ break;
+ // careful! want unsigned compare!
+ // use brute force here.
+ int a = this.data[i];
+ int b = other.data[i];
+ if ( a < 0 ){
+ // a is really big, unsigned
+ if ( b < 0 ){
+ return a-b; // both big, negative
+ } else {
+ return 1; // b not big, answer is obvious;
+ }
+ } else {
+ // a is not really big
+ if ( b < 0 ) {
+ // but b is really big
+ return -1;
+ } else {
+ return a - b;
+ }
+ }
+ }
+
+ /*
+ * Compute
+ * q = (int)( this / S )
+ * this = 10 * ( this mod S )
+ * Return q.
+ * This is the iteration step of digit development for output.
+ * We assume that S has been normalized, as above, and that
+ * "this" has been lshift'ed accordingly.
+ * Also assume, of course, that the result, q, can be expressed
+ * as an integer, 0 <= q < 10.
+ */
+ public int
+ quoRemIteration( FDBigInt S )throws IllegalArgumentException {
+ // ensure that this and S have the same number of
+ // digits. If S is properly normalized and q < 10 then
+ // this must be so.
+ if ( nWords != S.nWords ){
+ throw new IllegalArgumentException("disparate values");
+ }
+ // estimate q the obvious way. We will usually be
+ // right. If not, then we're only off by a little and
+ // will re-add.
+ int n = nWords-1;
+ long q = ((long)data[n]&0xffffffffL) / (long)S.data[n];
+ long diff = 0L;
+ for ( int i = 0; i <= n ; i++ ){
+ diff += ((long)data[i]&0xffffffffL) - q*((long)S.data[i]&0xffffffffL);
+ data[i] = (int)diff;
+ diff >>= 32; // N.B. SIGNED shift.
+ }
+ if ( diff != 0L ) {
+ // damn, damn, damn. q is too big.
+ // add S back in until this turns +. This should
+ // not be very many times!
+ long sum = 0L;
+ while ( sum == 0L ){
+ sum = 0L;
+ for ( int i = 0; i <= n; i++ ){
+ sum += ((long)data[i]&0xffffffffL) + ((long)S.data[i]&0xffffffffL);
+ data[i] = (int) sum;
+ sum >>= 32; // Signed or unsigned, answer is 0 or 1
+ }
+ /*
+ * Originally the following line read
+ * "if ( sum !=0 && sum != -1 )"
+ * but that would be wrong, because of the
+ * treatment of the two values as entirely unsigned,
+ * it would be impossible for a carry-out to be interpreted
+ * as -1 -- it would have to be a single-bit carry-out, or
+ * +1.
+ */
+ assert sum == 0 || sum == 1 : sum; // carry out of division correction
+ q -= 1;
+ }
+ }
+ // finally, we can multiply this by 10.
+ // it cannot overflow, right, as the high-order word has
+ // at least 4 high-order zeros!
+ long p = 0L;
+ for ( int i = 0; i <= n; i++ ){
+ p += 10*((long)data[i]&0xffffffffL);
+ data[i] = (int)p;
+ p >>= 32; // SIGNED shift.
+ }
+ assert p == 0L : p; // Carry out of *10
+ return (int)q;
+ }
+
+ public String
+ toString() {
+ StringBuffer r = new StringBuffer(30);
+ r.append('[');
+ int i = Math.min( nWords-1, data.length-1) ;
+ if ( nWords > data.length ){
+ r.append( "("+data.length+"<"+nWords+"!)" );
+ }
+ for( ; i> 0 ; i-- ){
+ r.append( Integer.toHexString( data[i] ) );
+ r.append(' ');
+ }
+ r.append( Integer.toHexString( data[0] ) );
+ r.append(']');
+ return new String( r );
+ }
+}
diff --git a/src/main/java/com/jfinal/template/io/FloatingWriter.java b/src/main/java/com/jfinal/template/io/FloatingWriter.java
new file mode 100644
index 0000000..254a6b2
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/FloatingWriter.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.template.io;
+
+import java.io.IOException;
+
+/**
+ * FloatingWriter
+ */
+public class FloatingWriter {
+
+ public static void write(ByteWriter byteWriter, double doubleValue) throws IOException {
+ FloatingDecimal fd = new FloatingDecimal(doubleValue);
+ char[] chars = byteWriter.chars;
+ byte[] bytes = byteWriter.bytes;
+ int len = fd.getChars(chars);
+ for (int i=0; i= 65536) {
+ q = i / 100;
+ // really: r = i - (q * 100);
+ r = i - ((q << 6) + (q << 5) + (q << 2));
+ i = q;
+ buf [--charPos] = DigitOnes[r];
+ buf [--charPos] = DigitTens[r];
+ }
+
+ // Fall thru to fast mode for smaller numbers
+ // assert(i <= 65536, i);
+ for (;;) {
+ q = (i * 52429) >>> (16+3);
+ r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
+ buf [--charPos] = digits [r];
+ i = q;
+ if (i == 0) break;
+ }
+ if (sign != 0) {
+ buf [--charPos] = sign;
+ }
+ }
+}
+
+
diff --git a/src/main/java/com/jfinal/template/io/JdkEncoder.java b/src/main/java/com/jfinal/template/io/JdkEncoder.java
new file mode 100644
index 0000000..cf4371d
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/JdkEncoder.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.template.io;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+
+/**
+ * JdkEncoder
+ */
+public class JdkEncoder extends Encoder {
+
+ private CharsetEncoder ce;
+
+ public JdkEncoder(Charset charset) {
+ this.ce = charset.newEncoder();
+ }
+
+ public float maxBytesPerChar() {
+ return ce.maxBytesPerChar();
+ }
+
+ public int encode(char[] chars, int offset, int len, byte[] bytes) {
+ ce.reset();
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ CharBuffer cb = CharBuffer.wrap(chars, offset, len);
+ try {
+ CoderResult cr = ce.encode(cb, bb, true);
+ if (!cr.isUnderflow())
+ cr.throwException();
+ cr = ce.flush(bb);
+ if (!cr.isUnderflow())
+ cr.throwException();
+ return bb.position();
+ } catch (CharacterCodingException x) {
+ // Substitution is always enabled,
+ // so this shouldn't happen
+ throw new RuntimeException("Encode error: " + x.getMessage(), x);
+ }
+ }
+}
+
+
+
+
+
+
diff --git a/src/main/java/com/jfinal/template/io/LongWriter.java b/src/main/java/com/jfinal/template/io/LongWriter.java
new file mode 100644
index 0000000..d4eb60a
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/LongWriter.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ */
+
+package com.jfinal.template.io;
+
+import java.io.IOException;
+
+public class LongWriter {
+
+ private static final byte[] minValueBytes = "-9223372036854775808".getBytes();
+ private static final char[] minValueChars = "-9223372036854775808".toCharArray();
+
+ public static void write(ByteWriter byteWriter, long value) throws IOException {
+ if (value == Long.MIN_VALUE) {
+ byteWriter.out.write(minValueBytes, 0, minValueBytes.length);
+ return ;
+ }
+
+ int size = (value < 0) ? stringSize(-value) + 1 : stringSize(value);
+ char[] chars = byteWriter.chars;
+ byte[] bytes = byteWriter.bytes;
+ getChars(value, size, chars);
+
+ // int len = Utf8Encoder.me.encode(chars, 0, size, bytes);
+ // byteWriter.out.write(bytes, 0, len);
+
+ for (int j=0; j Integer.MAX_VALUE) {
+ q = i / 100;
+ // really: r = i - (q * 100);
+ r = (int)(i - ((q << 6) + (q << 5) + (q << 2)));
+ i = q;
+ buf[--charPos] = IntegerWriter.DigitOnes[r];
+ buf[--charPos] = IntegerWriter.DigitTens[r];
+ }
+
+ // Get 2 digits/iteration using ints
+ int q2;
+ int i2 = (int)i;
+ while (i2 >= 65536) {
+ q2 = i2 / 100;
+ // really: r = i2 - (q * 100);
+ r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2));
+ i2 = q2;
+ buf[--charPos] = IntegerWriter.DigitOnes[r];
+ buf[--charPos] = IntegerWriter.DigitTens[r];
+ }
+
+ // Fall thru to fast mode for smaller numbers
+ // assert(i2 <= 65536, i2);
+ for (;;) {
+ q2 = (i2 * 52429) >>> (16+3);
+ r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ...
+ buf[--charPos] = IntegerWriter.digits[r];
+ i2 = q2;
+ if (i2 == 0) break;
+ }
+ if (sign != 0) {
+ buf[--charPos] = sign;
+ }
+ }
+}
+
+
diff --git a/src/main/java/com/jfinal/template/io/Utf8Encoder.java b/src/main/java/com/jfinal/template/io/Utf8Encoder.java
new file mode 100644
index 0000000..f7a1e14
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/Utf8Encoder.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.template.io;
+
+import java.nio.charset.MalformedInputException;
+
+/**
+ * Utf8Encoder
+ *
+ * http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/nio/cs/UTF_8.java?av=f
+ * http://grepcode.com/search?query=ArrayEncoder&start=0&entity=type&n=
+ */
+public class Utf8Encoder extends Encoder {
+
+ public static final Utf8Encoder me = new Utf8Encoder();
+
+ public float maxBytesPerChar() {
+ return 3.0F;
+ }
+
+ public int encode(char[] chars, int offset, int len, byte[] bytes) {
+ int sl = offset + len;
+ int dp = 0;
+ int dlASCII = dp + Math.min(len, bytes.length);
+
+ // ASCII only optimized loop
+ while (dp < dlASCII && chars[offset] < '\u0080') {
+ bytes[dp++] = (byte) chars[offset++];
+ }
+
+ while (offset < sl) {
+ char c = chars[offset++];
+ if (c < 0x80) {
+ // Have at most seven bits
+ bytes[dp++] = (byte) c;
+ } else if (c < 0x800) {
+ // 2 bytes, 11 bits
+ bytes[dp++] = (byte) (0xc0 | (c >> 6));
+ bytes[dp++] = (byte) (0x80 | (c & 0x3f));
+ } else if (c >= '\uD800' && c < ('\uDFFF' + 1)) { //Character.isSurrogate(c) but 1.7
+ final int uc;
+ int ip = offset - 1;
+ if (Character.isHighSurrogate(c)) {
+ if (sl - ip < 2) {
+ uc = -1;
+ } else {
+ char d = chars[ip + 1];
+ if (Character.isLowSurrogate(d)) {
+ uc = Character.toCodePoint(c, d);
+ } else {
+ throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
+ }
+ }
+ } else {
+ if (Character.isLowSurrogate(c)) {
+ throw new RuntimeException("encode UTF8 error", new MalformedInputException(1));
+ } else {
+ uc = c;
+ }
+ }
+
+ if (uc < 0) {
+ bytes[dp++] = (byte) '?';
+ } else {
+ bytes[dp++] = (byte) (0xf0 | ((uc >> 18)));
+ bytes[dp++] = (byte) (0x80 | ((uc >> 12) & 0x3f));
+ bytes[dp++] = (byte) (0x80 | ((uc >> 6) & 0x3f));
+ bytes[dp++] = (byte) (0x80 | (uc & 0x3f));
+ offset++; // 2 chars
+ }
+ } else {
+ // 3 bytes, 16 bits
+ bytes[dp++] = (byte) (0xe0 | ((c >> 12)));
+ bytes[dp++] = (byte) (0x80 | ((c >> 6) & 0x3f));
+ bytes[dp++] = (byte) (0x80 | (c & 0x3f));
+ }
+ }
+ return dp;
+ }
+}
+
+
+
+
diff --git a/src/main/java/com/jfinal/template/io/Writer.java b/src/main/java/com/jfinal/template/io/Writer.java
new file mode 100644
index 0000000..86fb10c
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/Writer.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.template.io;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * Writer
+ */
+public abstract class Writer {
+
+ protected DateFormats formats = new DateFormats();
+
+ public abstract void flush() throws IOException;
+
+ public abstract void close();
+
+ public abstract void write(IWritable writable) throws IOException;
+
+ public abstract void write(String string, int offset, int length) throws IOException;
+
+ public abstract void write(String string) throws IOException;
+
+ public abstract void write(StringBuilder stringBuilder, int offset, int length) throws IOException;
+
+ public abstract void write(StringBuilder stringBuilder) throws IOException;
+
+ public abstract void write(boolean booleanValue) throws IOException;
+
+ public abstract void write(int intValue) throws IOException;
+
+ public abstract void write(long longValue) throws IOException;
+
+ public abstract void write(double doubleValue) throws IOException;
+
+ public abstract void write(float floatValue) throws IOException;
+
+ public void write(short shortValue) throws IOException {
+ write((int)shortValue);
+ }
+
+ public void write(Date date, String datePattern) throws IOException {
+ String str = formats.getDateFormat(datePattern).format(date);
+ write(str, 0, str.length());
+ }
+}
+
+
+
+
+
+
+
diff --git a/src/main/java/com/jfinal/template/io/WriterBuffer.java b/src/main/java/com/jfinal/template/io/WriterBuffer.java
new file mode 100644
index 0000000..ba13e4d
--- /dev/null
+++ b/src/main/java/com/jfinal/template/io/WriterBuffer.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.template.io;
+
+/**
+ * WriterBuffer
+ */
+public class WriterBuffer {
+
+ private static final int MIN_BUFFER_SIZE = 64; // 缓冲区最小 64 字节
+ private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 10; // 缓冲区最大 10M 字节
+
+ private int bufferSize = 2048; // 缓冲区大小
+
+ private EncoderFactory encoderFactory = new EncoderFactory();
+
+ private final ThreadLocal byteWriters = new ThreadLocal() {
+ protected ByteWriter initialValue() {
+ return new ByteWriter(encoderFactory.getEncoder(), bufferSize);
+ }
+ };
+
+ private final ThreadLocal charWriters = new ThreadLocal() {
+ protected CharWriter initialValue() {
+ return new CharWriter(bufferSize);
+ }
+ };
+
+ private final ThreadLocal fastStringWriters = new ThreadLocal() {
+ protected FastStringWriter initialValue() {
+ return new FastStringWriter();
+ }
+ };
+
+ public ByteWriter getByteWriter(java.io.OutputStream outputStream) {
+ return byteWriters.get().init(outputStream);
+ }
+
+ public CharWriter getCharWriter(java.io.Writer writer) {
+ return charWriters.get().init(writer);
+ }
+
+ public FastStringWriter getFastStringWriter() {
+ return fastStringWriters.get();
+ }
+
+ public void setBufferSize(int bufferSize) {
+ if (bufferSize < MIN_BUFFER_SIZE || bufferSize > MAX_BUFFER_SIZE) {
+ throw new IllegalArgumentException("bufferSize must between " + (MIN_BUFFER_SIZE-1) + " and " + (MAX_BUFFER_SIZE+1));
+ }
+ this.bufferSize = bufferSize;
+ }
+
+ public void setEncoderFactory(EncoderFactory encoderFactory) {
+ if (encoderFactory == null) {
+ throw new IllegalArgumentException("encoderFactory can not be null");
+ }
+ this.encoderFactory = encoderFactory;
+ }
+
+ public void setEncoding(String encoding) {
+ encoderFactory.setEncoding(encoding);
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/jfinal/template/stat/Lexer.java b/src/main/java/com/jfinal/template/stat/Lexer.java
index 0a65251..3a83cff 100644
--- a/src/main/java/com/jfinal/template/stat/Lexer.java
+++ b/src/main/java/com/jfinal/template/stat/Lexer.java
@@ -95,7 +95,7 @@ class Lexer {
StringBuilder para = null;
Token idToken = null;
Token paraToken = null;
- while(true) {
+ while (true) {
switch (state) {
case 0:
if (peek() == '#') { // #
@@ -141,6 +141,14 @@ class Lexer {
continue ;
}
+ // 在支持 #seleif 的基础上,支持 #else if
+ if (symbol == Symbol.ELSE) {
+ if (foundFollowingIf()) {
+ id = "else if";
+ symbol = Symbol.ELSEIF;
+ }
+ }
+
// 无参关键字指令
if (symbol.noPara()) {
return addNoParaToken(new Token(symbol, id, beginRow));
@@ -199,6 +207,22 @@ class Lexer {
}
}
+ boolean foundFollowingIf() {
+ int p = forward;
+ while (CharTable.isBlank(buf[p])) {p++;}
+ if (buf[p++] == 'i') {
+ if (buf[p++] == 'f') {
+ while (CharTable.isBlank(buf[p])) {p++;}
+ // 要求出现 '(' 才认定解析成功,为了支持这种场景: #else if you ...
+ if (buf[p] == '(') {
+ forward = p;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* 调用者已确定以字母或下划线开头,故一定可以获取到 id值
*/
@@ -405,7 +429,7 @@ class Lexer {
}
void skipBlanks() {
- while(CharTable.isBlank(buf[forward])) {
+ while (CharTable.isBlank(buf[forward])) {
next();
}
}
diff --git a/src/main/java/com/jfinal/template/stat/Parser.java b/src/main/java/com/jfinal/template/stat/Parser.java
index 7a9d47f..4bb009e 100644
--- a/src/main/java/com/jfinal/template/stat/Parser.java
+++ b/src/main/java/com/jfinal/template/stat/Parser.java
@@ -18,6 +18,7 @@ package com.jfinal.template.stat;
import java.util.ArrayList;
import java.util.List;
+import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ExprParser;
import com.jfinal.template.expr.ast.ExprList;
@@ -84,10 +85,10 @@ public class Parser {
throw new ParseException("Can not match the #end of directive #" + name.value(), getLocation(name.row));
}
- public Stat parse() {
+ public StatList parse() {
tokenList = new Lexer(content, fileName).scan();
tokenList.add(EOF);
- Stat statList = statList();
+ StatList statList = statList();
if (peek() != EOF) {
throw new ParseException("Syntax error: can not match " + peek().value(), getLocation(peek().row));
}
@@ -122,7 +123,7 @@ public class Parser {
switch (name.symbol) {
case TEXT:
move();
- return new Text(((TextToken)name).getContent()).setLocation(getLocation(name.row));
+ return new Text(((TextToken)name).getContent(), env.getEngineConfig().getEncoding()).setLocation(getLocation(name.row));
case OUTPUT:
move();
Token para = matchPara(name);
@@ -171,9 +172,9 @@ public class Parser {
String functionName = name.value();
move();
para = matchPara(name);
- Stat stat = statList();
+ statList = statList();
matchEnd(name);
- return new Define(functionName, parseExprList(para), stat, getLocation(name.row));
+ return new Define(functionName, parseExprList(para), statList, getLocation(name.row));
case CALL:
functionName = name.value();
move();
@@ -206,7 +207,7 @@ public class Parser {
move();
return Return.me;
case ID:
- Stat dire = env.getEngineConfig().getDirective(name.value());
+ Class extends Directive> dire = env.getEngineConfig().getDirective(name.value());
if (dire == null) {
throw new ParseException("Directive not found: #" + name.value(), getLocation(name.row));
}
@@ -215,9 +216,9 @@ public class Parser {
para = matchPara(name);
ret.setExprList(parseExprList(para));
- if (dire.hasEnd()) {
+ if (ret.hasEnd()) {
statList = statList();
- ret.setStat(statList);
+ ret.setStat(statList.getActualStat());
matchEnd(name);
}
return ret;
@@ -236,9 +237,9 @@ public class Parser {
return new Location(fileName, row);
}
- private Stat createDirective(Stat dire, Token name) {
+ private Stat createDirective(Class extends Directive> dire, Token name) {
try {
- return dire.getClass().newInstance();
+ return dire.newInstance();
} catch (Exception e) {
throw new ParseException(e.getMessage(), getLocation(name.row), e);
}
diff --git a/src/main/java/com/jfinal/template/stat/Scope.java b/src/main/java/com/jfinal/template/stat/Scope.java
index 95650a6..619aba6 100644
--- a/src/main/java/com/jfinal/template/stat/Scope.java
+++ b/src/main/java/com/jfinal/template/stat/Scope.java
@@ -90,8 +90,19 @@ public class Scope {
*/
public Object get(Object key) {
for (Scope cur=this; cur!=null; cur=cur.parent) {
- if (cur.data != null && cur.data.containsKey(key)) {
- return cur.data.get(key);
+// if (cur.data != null && cur.data.containsKey(key)) {
+// return cur.data.get(key);
+// }
+
+ if (cur.data != null) {
+ Object ret = cur.data.get(key);
+ if (ret != null) {
+ return ret;
+ }
+
+ if (cur.data.containsKey(key)) {
+ return null;
+ }
}
}
// return null;
@@ -229,6 +240,18 @@ public class Scope {
}
}
}
+
+ /**
+ * 自内向外在作用域栈中查找变量是否存在
+ */
+ public boolean exists(Object key) {
+ for (Scope cur=this; cur!=null; cur=cur.parent) {
+ if (cur.data != null && cur.data.containsKey(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/main/java/com/jfinal/template/stat/ast/Break.java b/src/main/java/com/jfinal/template/stat/ast/Break.java
index c8cd24f..319ddd0 100644
--- a/src/main/java/com/jfinal/template/stat/ast/Break.java
+++ b/src/main/java/com/jfinal/template/stat/ast/Break.java
@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
diff --git a/src/main/java/com/jfinal/template/stat/ast/Call.java b/src/main/java/com/jfinal/template/stat/ast/Call.java
index 39af894..9dfcf2b 100644
--- a/src/main/java/com/jfinal/template/stat/ast/Call.java
+++ b/src/main/java/com/jfinal/template/stat/ast/Call.java
@@ -16,10 +16,10 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
diff --git a/src/main/java/com/jfinal/template/stat/ast/Continue.java b/src/main/java/com/jfinal/template/stat/ast/Continue.java
index 713475c..56be097 100644
--- a/src/main/java/com/jfinal/template/stat/ast/Continue.java
+++ b/src/main/java/com/jfinal/template/stat/ast/Continue.java
@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
diff --git a/src/main/java/com/jfinal/template/stat/ast/Define.java b/src/main/java/com/jfinal/template/stat/ast/Define.java
index 2f866d0..f83dda4 100644
--- a/src/main/java/com/jfinal/template/stat/ast/Define.java
+++ b/src/main/java/com/jfinal/template/stat/ast/Define.java
@@ -16,7 +16,6 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.stat.Location;
@@ -25,6 +24,7 @@ import com.jfinal.template.stat.Scope;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Id;
+import com.jfinal.template.io.Writer;
/**
* Define 定义模板函数:
@@ -50,10 +50,10 @@ public class Define extends Stat {
private String[] parameterNames;
private Stat stat;
- public Define(String functionName, ExprList exprList, Stat stat, Location location) {
+ public Define(String functionName, ExprList exprList, StatList statList, Location location) {
setLocation(location);
this.functionName = functionName;
- this.stat = stat;
+ this.stat = statList.getActualStat();
Expr[] exprArray = exprList.getExprArray();
if (exprArray.length == 0) {
diff --git a/src/main/java/com/jfinal/template/stat/ast/Else.java b/src/main/java/com/jfinal/template/stat/ast/Else.java
index e10e8b2..b717c5a 100644
--- a/src/main/java/com/jfinal/template/stat/ast/Else.java
+++ b/src/main/java/com/jfinal/template/stat/ast/Else.java
@@ -16,8 +16,8 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
@@ -27,8 +27,8 @@ public class Else extends Stat {
private Stat stat;
- public Else(Stat stat) {
- this.stat = stat;
+ public Else(StatList statList) {
+ this.stat = statList.getActualStat();
}
public void exec(Env env, Scope scope, Writer writer) {
diff --git a/src/main/java/com/jfinal/template/stat/ast/ElseIf.java b/src/main/java/com/jfinal/template/stat/ast/ElseIf.java
index 2a5db45..7d0ba85 100644
--- a/src/main/java/com/jfinal/template/stat/ast/ElseIf.java
+++ b/src/main/java/com/jfinal/template/stat/ast/ElseIf.java
@@ -16,10 +16,11 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import com.jfinal.template.Env;
+import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.expr.ast.Logic;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Location;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
@@ -29,16 +30,16 @@ import com.jfinal.template.stat.Scope;
*/
public class ElseIf extends Stat {
- private ExprList cond;
+ private Expr cond;
private Stat stat;
private Stat elseIfOrElse;
- public ElseIf(ExprList cond, Stat stat, Location location) {
+ public ElseIf(ExprList cond, StatList statList, Location location) {
if (cond.length() == 0) {
- throw new ParseException("The condition expression of #elseif statement can not be blank", location);
+ throw new ParseException("The condition expression of #else if statement can not be blank", location);
}
- this.cond = cond;
- this.stat = stat;
+ this.cond = cond.getActualExpr();
+ this.stat = statList.getActualStat();
}
/**
diff --git a/src/main/java/com/jfinal/template/stat/ast/For.java b/src/main/java/com/jfinal/template/stat/ast/For.java
index d2b425b..1502ef1 100644
--- a/src/main/java/com/jfinal/template/stat/ast/For.java
+++ b/src/main/java/com/jfinal/template/stat/ast/For.java
@@ -16,12 +16,12 @@
package com.jfinal.template.stat.ast;
-import java.io.Writer;
import java.util.Iterator;
import com.jfinal.template.Env;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ForCtrl;
import com.jfinal.template.expr.ast.Logic;
+import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Ctrl;
import com.jfinal.template.stat.Scope;
@@ -38,12 +38,12 @@ import com.jfinal.template.stat.Scope;
public class For extends Stat {
private ForCtrl forCtrl;
- private StatList statList;
+ private Stat stat;
private Stat _else;
public For(ForCtrl forCtrl, StatList statList, Stat _else) {
this.forCtrl = forCtrl;
- this.statList = statList;
+ this.stat = statList.getActualStat();
this._else = _else;
}
@@ -71,7 +71,7 @@ public class For extends Stat {
String itemName = forCtrl.getId();
while(it.hasNext()) {
scope.setLocal(itemName, it.next());
- statList.exec(env, scope, writer);
+ stat.exec(env, scope, writer);
forIteratorStatus.nextState();
if (ctrl.isJump()) {
@@ -108,7 +108,7 @@ public class For extends Stat {
ctrl.setLocalAssignment();
for (init.eval(scope); cond == null || Logic.isTrue(cond.eval(scope)); update.eval(scope)) {
ctrl.setWisdomAssignment();
- statList.exec(env, scope, writer);
+ stat.exec(env, scope, writer);
ctrl.setLocalAssignment();
forLoopStatus.nextState();
diff --git a/src/main/java/com/jfinal/template/stat/ast/ForEntry.java b/src/main/java/com/jfinal/template/stat/ast/ForEntry.java
index a308d56..fc16d7e 100644
--- a/src/main/java/com/jfinal/template/stat/ast/ForEntry.java
+++ b/src/main/java/com/jfinal/template/stat/ast/ForEntry.java
@@ -25,7 +25,7 @@ public class ForEntry implements Entry