Package com.slmn.buildin.money

Source Code of com.slmn.buildin.money.MoneyFactory$ByteArraySeq

package com.slmn.buildin.money;
import java.math.BigDecimal;
import java.math.BigInteger;

/**
* Converter from String/double/float/integer types into Money instances.
* Requies the precision to be specified for double->Money conversion. Precision is usually based
* on the smallest tick in your exchange data.
*/
public class MoneyFactory {
    static final int MAX_LONG_LENGTH = Long.toString( Long.MAX_VALUE ).length();

    public static final int MAX_ALLOWED_PRECISION = 15;
    //needed for overflow checking during conversion
    private static final long MAX_LONG_DIVIDED_BY_10 = Long.MAX_VALUE / 10;

    /** Non-negative powers of 10 */
    static final long[] MULTIPLIERS = new long[ MoneyFactory.MAX_ALLOWED_PRECISION + 1 ];
    /** Non-positive powers of 10 */
    static final double[] MULTIPLIERS_NEG = new double[ MoneyFactory.MAX_ALLOWED_PRECISION + 1 ];
    static
    {
        long val = 1;
        for ( int i = 0; i <= MoneyFactory.MAX_ALLOWED_PRECISION; ++i )
        {
            MULTIPLIERS[ i ] = val;
            MULTIPLIERS_NEG[ i ] = 1.0 / val;
            val *= 10;
        }
    }

    static void checkPrecision(int precision) {
        if ( precision < 0 || precision > MAX_ALLOWED_PRECISION )
            throw new IllegalArgumentException( "Precision must be between 0 and " + MAX_ALLOWED_PRECISION );
    }

    /**
     * Convert from currency units and their precision into Money object.
     * @param units Currency units (cents, for example)
     * @param precision Number of digits after decimal point in your smallest possible currency unit. Should be between
     *                  0 and <code>MAX_ALLOWED_PRECISION</code> (inclusive).
     * @return Money object
     * @throws java.lang.IllegalArgumentException In case of invalid precision
     */
    public static Money fromUnits( final long units, final int precision )
    {
        checkPrecision( precision );
        return new MoneyLong( units, precision );
    }

    /**
     * Same as <code>fromString</code>, but characters are extracted from the given part of char array
     * @param chars Char array
     * @param offset Start position
     * @param length number of characters to process
     * @return Money object
     */
    public static Money fromCharArray( final char[] chars, final int offset, final int length )
    {
        return fromCharSequence( new CharArraySeq( chars, offset, length ) );
    }

    private static class CharArraySeq implements CharSequence
    {
        private final char[] chars;
        private final int offset;
        private final int length;

        private CharArraySeq(char[] chars, int offset, int length) {
            this.chars = chars;
            this.offset = offset;
            this.length = length;
        }

        public int length() {
            return length;
        }

        public char charAt( final int index ) {
            return chars[ offset + index ];
        }

        public CharSequence subSequence(int start, int end) {
            //not needed for this implemenration
            return null;
        }
    }

    /**
     * Same as <code>fromString</code>, but characters are extracted from the given part of byte array.
     * We expect the data to be ASCII-encoded in the given part of the array.
     * @param bytes Byte array
     * @param offset Start position
     * @param length number of characters to process
     * @return Money object
     */
    public static Money fromByteArray( final byte[] bytes, final int offset, final int length )
    {
        return fromCharSequence( new ByteArraySeq( bytes, offset, length));
    }

    private static class ByteArraySeq implements CharSequence
    {
        private final byte[] bytes;
        private final int offset;
        private final int length;

        private ByteArraySeq(byte[] bytes, int offset, int length) {
            this.bytes = bytes;
            this.offset = offset;
            this.length = length;
        }

        public int length() {
            return length;
        }

        public char charAt(int index) {
            return (char) bytes[offset + index];
        }

        public CharSequence subSequence(int start, int end) {
            return null; //not needed for this implementation
        }
    }

    /**
     * Same as <code>fromString</code>, but characters are extracted from the <code>CharSequence</code>
     * @param seq <code>CharSequence</code> object, may be more convenient in some situations.
     * @return Money object
     */
    public static Money fromCharSequence( final CharSequence seq )
    {
        final Money fast = parseFast( seq );
        if ( fast != null )
            return fast;
        return fromString0( seq.toString() ); //slow path, convert to String
    }

    /**
     * Convert a String monetary value into a Money object. We support a single format - dot-separated decimal part without
     * the scientific notation support ( valid example - 355.56 ).
     * @param value Value to parse
     * @return Money
     * @throws java.lang.IllegalArgumentException In case of any conversion errors
     */
    public static Money fromString( final String value )
    {
        //fast single pass parser first
        final Money fast = parseFast( value );
        if ( fast != null )
            return fast;

        return fromString0(value);
    }

    private static Money fromString0( final String value )
    {
        final int dotPos = value.indexOf('.');
        final int precision = dotPos == -1 ? 0 : value.length() - dotPos - 1;
        if ( precision > MAX_ALLOWED_PRECISION ) //too high precision
            return new MoneyBigDecimal( value );
        if ( dotPos != -1 && value.indexOf( '.', dotPos + 1 ) != -1 )
            throw new IllegalArgumentException( "Unparseable String value has more than 1 decimal point: " + value );
        try
        {
            final long units = Long.parseLong( value.replace(".", "") );
            return new MoneyLong( units, precision ); //actual precision, not the maximal one
        }
        catch ( NumberFormatException ex )
        {
            try
            {
                return new MoneyBigDecimal( value );
            }
            catch ( NumberFormatException ex2 )
            {
                throw new IllegalArgumentException( "Unparseable value provided: " + value, ex2 );
            }
        }
    }

    /**
     * Fast single pass parser, should correctly process most of positive/negative values fitting into <code>long</code>
     * @param str Floating point number to parse
     * @return Money object or null (if can't parse)
     * @throws java.lang.IllegalArgumentException If a value has more than one decimal digit
     */
    private static Money parseFast( final CharSequence str )
    {
        if ( str.length() >= MAX_LONG_LENGTH )
            return null;
        long res = 0;
        int start = 0;
        long sign = 1;
        int precision = 0;
        if ( str.charAt( 0 ) == '-' )
        {
            sign = -1;
            start = 1;
        }
        else if ( str.charAt( 0 ) == '+' )
        {
            sign = 1;
            start = 1;
        }
        for ( int i = start; i < str.length(); ++i )
        {
            final char c = str.charAt( i );
            if ( c == '.' )
            {
                if ( precision > 0 )
                    throw new IllegalArgumentException( "Unparseable String value has more than 1 decimal point: " + str );
                precision = str.length() - i - 1;
            }
            else if ( c >= '0' && c <= '9' )
                res = res * 10 + ( c - '0' );
            else //unsupported char, handle in the caller
                return null;
        }
        return new MoneyLong( res * sign, precision ).normalize();
    }

    /**
     * <p>
     *     Convert a double monetary value into a Money object. You will end up with the most efficient Money type
     *     if you have no more than <code>MAX_ALLOWED_PRECISION</code> decimal digits in your value.
     * </p>
     * <p>
     *     This method will attempt to look by ulp in both directions during conversions to cater for already slightly
     *     incorrect values - results of <code>double</code> operations outside this library.
     * </p>
     * @param value Double monetary value
     * @return Money object
     */
    public static Money fromDouble( final double value )
    {
        return fromDouble( value, MAX_ALLOWED_PRECISION );
    }
    /**
     * <p>
     *     Convert a double monetary value into a Money object. You will end up with the most efficient Money type
     *     if you have no more than <code>precision</code> decimal digits in your value.
     * </p>
     * <p>
     *     This method will attempt to look by ulp in both directions during conversions to cater for already slightly
     *     incorrect values - results of <code>double</code> operations outside this library.
     * </p>
     * <p>
     *     Do not try to set too high precision for this method - it may prevent you from correcting a slightly
     *     incorrect value (off by ulp) into a correct one. As a result, you will end up with BigDecimal-based
     *     implementation, which requires more memory and which is much slower to calculate.
     * </p>
     * @param value Double monetary value
     * @param precision Number of digits after decimal point in your smallest possible currency unit.
     *                  Should be between 0 and <code>MAX_ALLOWED_PRECISION</code> (inclusive).
     * @return Money object
     */
    public static Money fromDouble( final double value, final int precision )
    {
        checkPrecision( precision );
        //attempt direct
        final Money direct = fromDoubleNoFallback( value, precision );
        if ( direct != null )
            return direct;

        return new MoneyBigDecimal( value );
    }

    static MoneyLong fromDoubleNoFallback( final double value, final int precision )
    {
        //attempt direct
        final MoneyLong direct = fromDouble0( value, precision );
        if ( direct != null )
            return direct;
        //ulp down
        final MoneyLong down = fromDouble0( Math.nextAfter( value, -Double.MAX_VALUE ), precision );
        if ( down != null )
            return down;
        //ulp up
        final MoneyLong up = fromDouble0( Math.nextAfter( value, Double.MAX_VALUE ), precision );
        if ( up != null )
            return up;
        return null;
    }

    private static MoneyLong fromDouble0( final double value, final int precision )
    {
        final double multiplied = value * MULTIPLIERS[ precision ];
        final long converted = (long) multiplied;
        if ( multiplied == converted ) //here is an implicit conversion from long to double
            return new MoneyLong( converted, precision ).normalize();
        return null;
    }

    /**
     * <p>
     *     Convert a given BigDecimal value into money. Conversion is similar to <code>toDouble</code>,
     *     though this method does not attempt to make any corrections: it assumes that BigDecimal is a result
     *     of exact calcualtions.
     * </p>
     * <p>
     *     This method will try to use the most efficient representation if possible.
     * </p>
     * @param value BigDecimal value to convert
     * @return Money object
     */
    public static Money fromBigDecimal( final BigDecimal value )
    {
        final BigDecimal cleaned = value.stripTrailingZeros();

        //try to convert to double using a fixed precision = 3, which will cover most of currencies
        //it is required to get rid of rounding issues
        final double dbl = value.doubleValue();
        final Money res = fromDoubleNoFallback( dbl, 3 );
        if ( res != null )
            return res;

        final int scale = cleaned.scale();
        if ( scale > MAX_ALLOWED_PRECISION || scale < -MAX_ALLOWED_PRECISION )
            return new MoneyBigDecimal( cleaned );
        //we may not fit into the Long, but we should try
        //this value may be truncated!
        final BigInteger unscaledBigInt = cleaned.unscaledValue();
        final long unscaledUnits = unscaledBigInt.longValue();
        //check that it was not
        if ( !BigInteger.valueOf(unscaledUnits).equals( unscaledBigInt ) )
            return new MoneyBigDecimal( cleaned );
        //scale could be negative here - we must multiply in that case
        if ( scale >= 0 )
            return new MoneyLong( unscaledUnits, scale );
        //multiply by 10 and each time check that sign did not change
        //scale is negative
        long units = unscaledUnits;
        for ( int i = 0; i < -scale; ++i )
        {
            units *= 10;
            if ( units >= MAX_LONG_DIVIDED_BY_10 )
                return new MoneyBigDecimal( value );
        }
        return new MoneyLong( units, 0 );

    }

}
TOP

Related Classes of com.slmn.buildin.money.MoneyFactory$ByteArraySeq

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.