NumberConverter at revision 29615

While the idea to provide a converter from numbers to other types was found in GeoTools, the code has been totally rewritten and share very few in common. For example the String inner class is declared as a derivative of Justin's work by an @author javadoc tags. But the only code that share some similarity with the original work is:

public String convert(Number source) throws NonconvertibleObjectException {
    return (source != null) ? source.toString() : null;
}

The original work was rather providing the above functionality inside relatively large sequences of ifelse statements, which is a different approach than the Geotoolkit.org one (which separates all possible conversions into single methods). Even the "(source != null) ? source.toString() : null" part was not written that way in the original code, and such simple statement is in about every Java softwares.

The table below compares the GeoTools code with the Geotoolkit.org one.

Command line:

svn cat -r29615 https://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/util/NumericConverterFactory.java
Revision 29147Geotoolkit.org
/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.NumberFormat;

import org.geotools.factory.Hints;

/**
 * ConverterFactory which converts between the "standard" numeric types.
 * <p>
 * Supported types:
 * <ul>
 * <li>{@link java.lang.Long}
 * <li>{@link java.lang.Integer}
 * <li>{@link java.lang.Short}
 * <li>{@link java.lang.Byte}
 * <li>{@link java.lang.BigInteger}
 * <li>{@link java.lang.Double}
 * <li>{@link java.lang.Float}
 * <li>{@link java.lang.BigDecimal}
 * </ul>
 * </p>
 *
 * @author Justin Deoliveira, The Open Planning Project
 * @since 2.4
 */
public class NumericConverterFactory implements ConverterFactory {

    public Converter createConverter(Class source, Class target, Hints hints) {

        //check if source is a number or a string.  We can't convert to a number
        // from anything else.
        if ( !(Number.class.isAssignableFrom( source )) &&
             !(String.class.isAssignableFrom( source )) )
                return null;

        //check if target is one of supported
        if (
            Long.class.equals( target ) ||
            Integer.class.equals( target ) ||
            Short.class.equals( target ) ||
            Byte.class.equals( target ) ||
            BigInteger.class.equals( target ) ||
            BigDecimal.class.equals( target ) ||
            Double.class.equals( target ) ||
            Float.class.equals( target ) ||
            Number.class.equals( target )
        ) {
            return new NumericConverter();
        }

        return null;
    }

    class NumericConverter implements Converter {

        public Object convert(Object source, Class target) throws Exception {
            if (source instanceof Number) {
                Number s = (Number) source;

                //integral
                if ( Long.class.equals( target ) ) {
                    return new Long( s.longValue() );
                }
                if ( Integer.class.equals( target ) ) {
                    return new Integer( s.intValue() );
                }
                if ( Short.class.equals( target ) ) {
                    return new Short( s.shortValue() );
                }
                if ( Byte.class.equals( target ) ) {
                    return new Byte( s.byteValue() );
                }
                if ( BigInteger.class.equals( target ) ) {
                    return BigInteger.valueOf( s.longValue() );
                }

                //floating point
                // JD: we use the string reprensentation to avoid coordinate
                // drift due to precision issues, there could be some
                // performance issues with this.
                if ( Double.class.equals( target ) ) {
                    return new Double( s.toString() );
                }
                if ( Float.class.equals( target ) ) {
                    return new Float( s.toString() );
                }
                if ( BigDecimal.class.equals( target ) ) {
                    return new BigDecimal( s.toString() );
                }

                if (Number.class.equals( target )) {
                    try {
                        return new Integer(s.toString());
                    } catch (Exception e) {
                    }

                    try {
                        return new BigInteger(s.toString());
                    } catch (Exception e) {
                    }

                    try {
                        return new Double(s.toString());
                    } catch (Exception e) {
                    }

                    try {
                        return new BigDecimal(s.toString());
                    } catch (Exception e) {
                    }
                }
            } else if (source instanceof String) {
                String s = (String) source;
                //ensure we trim any space off the string
                s = s.trim();

            String integral = toIntegral(s);

            //floating point
            if ( Double.class.equals( target ) ) {
                return new Double(s);
            }
            if ( Float.class.equals( target ) ) {
                return new Float(s);
            }
            if ( BigDecimal.class.equals( target ) ) {
                return new BigDecimal(s);
            }



            //textual
            if ( Long.class.equals( target ) ) {
                return new Long(integral);
            }
            if ( Integer.class.equals( target ) ) {
                return new Integer(integral);
            }
            if ( Short.class.equals( target ) ) {
                return new Short(integral);
            }
            if ( Byte.class.equals( target ) ) {
                return new Byte(integral);
            }
            if ( BigInteger.class.equals( target ) ) {
                return new BigInteger(integral);
            }

            // fallback.  If you ask for Number, you get our 'best guess'
            if (Number.class.equals( target )) {
                if( integral.equals(s)){
                    // we think this is an integer of some sort
                    try {
                        return new Integer(integral);
                    } catch (Exception e) {
                    }

                    try {
                        return new BigInteger(integral);
                    } catch (Exception e) {
                    }
                }
                try {
                    return new Double(s);
                } catch (Exception e) {
                }

                try {
                    return new BigDecimal(s);
                } catch (Exception e) {
                }
            }
                }
                //nothing matched.  Return null.
                return null;
            }

	}

	/**
	 * Extract the integral part out of a decimal format string.
	 * @param s
	 * @return integral component of decimal representation
	 */
	static String toIntegral( String s ) {
	    //NumberFormat format = NumberFormat.getInstance();

	    int radex = -1; // last non numeric character to account for "." vs "," seperators
        for( int i=s.length()-1; i>0; i--){
            char ch = s.charAt(i);
            if( !Character.isDigit(ch) && '-' != ch){
                radex = i;
                break;
            }
        }
        if( radex != -1 ){
           // text is formatted in decimal but floating point format supplied
            return s.substring(0, radex );
        }
        else {
            return s;
        }
    }
}
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2007-2012, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2012, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotoolkit.util.converter;

import java.io.Serializable;
import java.io.ObjectStreamException;
import net.jcip.annotations.Immutable;


/**
 * Handles conversions from {@link java.lang.Number} to various objects.
 *
 * @author Justin Deoliveira (TOPP)
 * @author Martin Desruisseaux (Geomatys)
 * @version 3.02
 *
 * @since 2.4
 * @module
 */
@Immutable
abstract class NumberConverter<T> extends SimpleConverter<Number,T> implements Serializable {
    /**
     * For cross-version compatibility.
     */
    private static final long serialVersionUID = -8715054480508622025L;

    /**
     * Returns the source class, which is always {@link Number}.
     */
    @Override
    public final Class<Number> getSourceClass() {
        return Number.class;
    }

    /**
     * Returns {@code false} since subclasses do not preserve order.
     */
    @Override
    public boolean isOrderPreserving() {
        return false;
    }


    /**
     * Converter from numbers to comparables. This special case exists because {@link Number}
     * does not implement {@link java.lang.Comparable} directly, but all known subclasses do.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.01
     *
     * @since 3.01
     */
    @Immutable
    static final class Comparable extends NumberConverter<java.lang.Comparable<?>> {
        private static final long serialVersionUID = 3716134638218072176L;
        public static final Comparable INSTANCE = new Comparable();
        private Comparable() {
        }

        @Override
        @SuppressWarnings({"rawtypes","unchecked"})
        public Class<java.lang.Comparable<?>> getTargetClass() {
            return (Class) java.lang.Comparable.class;
        }

        @Override
        public java.lang.Comparable<?> convert(final Number source) throws NonconvertibleObjectException {
            if (source == null || source instanceof java.lang.Comparable<?>) {
                return (java.lang.Comparable<?>) source;
            }
            return source.doubleValue();
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to strings.
     *
     * @author Justin Deoliveira (TOPP)
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 2.4
     */
    @Immutable
    static final class String extends NumberConverter<java.lang.String> {
        private static final long serialVersionUID = 1460382215827540172L;
        public static final String INSTANCE = new String();
        private String() {
        }

        @Override
        public Class<java.lang.String> getTargetClass() {
            return java.lang.String.class;
        }

        @Override
        public java.lang.String convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? source.toString() : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to doubles.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Double extends NumberConverter<java.lang.Double> {
        private static final long serialVersionUID = 1643009985070268985L;
        public static final Double INSTANCE = new Double();
        private Double() {
        }

        @Override
        public Class<java.lang.Double> getTargetClass() {
            return java.lang.Double.class;
        }

        @Override
        public java.lang.Double convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Double.valueOf(source.doubleValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to floats.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Float extends NumberConverter<java.lang.Float> {
        private static final long serialVersionUID = -5900985555014433974L;
        public static final Float INSTANCE = new Float();
        private Float() {
        }

        @Override
        public Class<java.lang.Float> getTargetClass() {
            return java.lang.Float.class;
        }

        @Override
        public java.lang.Float convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Float.valueOf(source.floatValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to longs.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Long extends NumberConverter<java.lang.Long> {
        private static final long serialVersionUID = -5320144566275003574L;
        public static final Long INSTANCE = new Long();
        private Long() {
        }

        @Override
        public Class<java.lang.Long> getTargetClass() {
            return java.lang.Long.class;
        }

        @Override
        public java.lang.Long convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Long.valueOf(source.longValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to integers.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Integer extends NumberConverter<java.lang.Integer> {
        private static final long serialVersionUID = 2661178278691398269L;
        public static final Integer INSTANCE = new Integer();
        private Integer() {
        }

        @Override
        public Class<java.lang.Integer> getTargetClass() {
            return java.lang.Integer.class;
        }

        @Override
        public java.lang.Integer convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Integer.valueOf(source.intValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to shorts.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Short extends NumberConverter<java.lang.Short> {
        private static final long serialVersionUID = -5943559376400249179L;
        public static final Short INSTANCE = new Short();
        private Short() {
        }

        @Override
        public Class<java.lang.Short> getTargetClass() {
            return java.lang.Short.class;
        }

        @Override
        public java.lang.Short convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Short.valueOf(source.shortValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to shorts.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 3.00
     */
    @Immutable
    static final class Byte extends NumberConverter<java.lang.Byte> {
        private static final long serialVersionUID = 1381038535870541045L;
        public static final Byte INSTANCE = new Byte();
        private Byte() {
        }

        @Override
        public Class<java.lang.Byte> getTargetClass() {
            return java.lang.Byte.class;
        }

        @Override
        public java.lang.Byte convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Byte.valueOf(source.byteValue()) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to booleans. Values in the range (-1..1) exclusive
     * and NaN values are understood as "false", and all other values as "true".
     *
     * @author Justin Deoliveira (TOPP)
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 2.4
     */
    @Immutable
    static final class Boolean extends NumberConverter<java.lang.Boolean> {
        private static final long serialVersionUID = -7522980351031833731L;
        public static final Boolean INSTANCE = new Boolean();
        private Boolean() {
        }

        @Override
        public Class<java.lang.Boolean> getTargetClass() {
            return java.lang.Boolean.class;
        }

        @Override
        public java.lang.Boolean convert(final Number source) throws NonconvertibleObjectException {
            return (source != null) ? java.lang.Boolean.valueOf(source.intValue() != 0) : null;
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to {@link java.math.BigDecimal}.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.02
     *
     * @since 3.02
     */
    @Immutable
    static final class BigDecimal extends NumberConverter<java.math.BigDecimal> {
        private static final long serialVersionUID = -6318144992861058878L;
        public static final BigDecimal INSTANCE = new BigDecimal();
        private BigDecimal() {
        }

        @Override
        public Class<java.math.BigDecimal> getTargetClass() {
            return java.math.BigDecimal.class;
        }

        @Override
        public java.math.BigDecimal convert(final Number source) throws NonconvertibleObjectException {
            if (source == null) {
                return null;
            }
            if (source instanceof java.math.BigDecimal) {
                return (java.math.BigDecimal) source;
            }
            if (source instanceof java.math.BigInteger) {
                return new java.math.BigDecimal((java.math.BigInteger) source);
            }
            if (Numbers.isInteger(source.getClass())) {
                return java.math.BigDecimal.valueOf(source.longValue());
            }
            return java.math.BigDecimal.valueOf(source.doubleValue());
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to {@link java.math.BigInteger}.
     *
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.02
     *
     * @since 3.02
     */
    @Immutable
    static final class BigInteger extends NumberConverter<java.math.BigInteger> {
        private static final long serialVersionUID = 5940724099300523246L;
        public static final BigInteger INSTANCE = new BigInteger();
        private BigInteger() {
        }

        @Override
        public Class<java.math.BigInteger> getTargetClass() {
            return java.math.BigInteger.class;
        }

        @Override
        public java.math.BigInteger convert(final Number source) throws NonconvertibleObjectException {
            if (source == null) {
                return null;
            }
            if (source instanceof java.math.BigInteger) {
                return (java.math.BigInteger) source;
            }
            if (source instanceof java.math.BigDecimal) {
                return ((java.math.BigDecimal) source).toBigInteger();
            }
            return java.math.BigInteger.valueOf(source.longValue());
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }


    /**
     * Converter from numbers to colors. Colors with an alpha of 0 (which would normally
     * be fully transparent pixel) are interpreted as color without alpha component.
     *
     * @author Justin Deoliveira (TOPP)
     * @author Martin Desruisseaux (Geomatys)
     * @version 3.00
     *
     * @since 2.4
     */
    @Immutable
    static final class Color extends NumberConverter<java.awt.Color> {
        private static final long serialVersionUID = 8866612442279600953L;
        public static final Color INSTANCE = new Color();
        private Color() {
        }

        @Override
        public Class<java.awt.Color> getTargetClass() {
            return java.awt.Color.class;
        }

        @Override
        public java.awt.Color convert(final Number source) throws NonconvertibleObjectException {
            if (source == null) {
                return null;
            }
            final int rgba = source.intValue();
            final int alpha = rgba & 0xFF000000;
            return new java.awt.Color(rgba, alpha != 0);
        }

        /** Returns the singleton instance on deserialization. */
        protected Object readResolve() throws ObjectStreamException {
            return INSTANCE;
        }
    }
}