    001    /*
    002     * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved.
    003     *
    004     * Project and contact information: http://www.cascading.org/
    005     *
    006     * This file is part of the Cascading project.
    007     *
    008     * Licensed under the Apache License, Version 2.0 (the "License");
    009     * you may not use this file except in compliance with the License.
    010     * You may obtain a copy of the License at
    011     *
    012     *     http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing, software
    015     * distributed under the License is distributed on an "AS IS" BASIS,
    016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    017     * See the License for the specific language governing permissions and
    018     * limitations under the License.
    019     */
    020
    021    package cascading.tuple.coerce;
    022
    023    import java.lang.reflect.Type;
    024    import java.math.BigDecimal;
    025    import java.util.Arrays;
    026    import java.util.Collections;
    027    import java.util.HashMap;
    028    import java.util.IdentityHashMap;
    029    import java.util.Map;
    030
    031    import cascading.cascade.CascadeException;
    032    import cascading.tuple.Fields;
    033    import cascading.tuple.type.CoercibleType;
    034    import cascading.util.Util;
    035
    036    /**
    037     * Coercions class is a helper class for managing primitive value coercions.
    038     * <p/>
    039     * The {@link Coerce} constants are the default coercions for the specified type.
    040     * <p/>
    041     * To override the behavior, you must create a new {@link CoercibleType} and assign it to the {@link Fields}
    042     * field position/name the custom behavior should apply.
    043     * <p/>
    044     * Coercions are always used if {@link cascading.tuple.Tuple} elements are accessed via a {@link cascading.tuple.TupleEntry}
    045     * wrapper instance.
    046     *
    047     * @see CoercibleType
    048     */
    049    public final class Coercions
    050      {
    051      public static abstract class Coerce<T> implements CoercibleType<T>
    052        {
    053        protected Coerce( Map<Type, Coerce> map )
    054          {
    055          if( map.containsKey( getCanonicalType() ) )
    056            throw new IllegalStateException( "type already exists in map: " + getCanonicalType() );
    057
    058          map.put( getCanonicalType(), this );
    059          }
    060
    061        @Override
    062        public T canonical( Object value )
    063          {
    064          return coerce( value );
    065          }
    066
    067        @Override
    068        public <Coerce> Coerce coerce( Object value, Type to )
    069          {
    070          return Coercions.coerce( value, to );
    071          }
    072
    073        public abstract T coerce( Object value );
    074
    075        @Override
    076        public int hashCode()
    077          {
    078          return getCanonicalType().hashCode();
    079          }
    080
    081        @Override
    082        public boolean equals( Object object )
    083          {
    084          if( this == object )
    085            return true;
    086
    087          if( !( object instanceof CoercibleType ) )
    088            return false;
    089
    090          return getCanonicalType().equals( ( (CoercibleType) object ).getCanonicalType() );
    091          }
    092        }
    093
    094      private static final Map<Type, Coerce> coercionsPrivate = new IdentityHashMap<Type, Coerce>();
    095      public static final Map<Type, Coerce> coercions = Collections.unmodifiableMap( coercionsPrivate );
    096
    097      private static final Map<String, Type> typesPrivate = new HashMap<String, Type>();
    098      public static final Map<String, Type> types = Collections.unmodifiableMap( typesPrivate );
    099
    100      public static final Coerce<Object> OBJECT = new ObjectCoerce( coercionsPrivate );
    101      public static final Coerce<String> STRING = new StringCoerce( coercionsPrivate );
    102      public static final Coerce<Character> CHARACTER = new CharacterCoerce( coercionsPrivate );
    103      public static final Coerce<Character> CHARACTER_OBJECT = new CharacterObjectCoerce( coercionsPrivate );
    104      public static final Coerce<Short> SHORT = new ShortCoerce( coercionsPrivate );
    105      public static final Coerce<Short> SHORT_OBJECT = new ShortObjectCoerce( coercionsPrivate );
    106      public static final Coerce<Integer> INTEGER = new IntegerCoerce( coercionsPrivate );
    107      public static final Coerce<Integer> INTEGER_OBJECT = new IntegerObjectCoerce( coercionsPrivate );
    108      public static final Coerce<Double> DOUBLE = new DoubleCoerce( coercionsPrivate );
    109      public static final Coerce<Double> DOUBLE_OBJECT = new DoubleObjectCoerce( coercionsPrivate );
    110      public static final Coerce<Long> LONG = new LongCoerce( coercionsPrivate );
    111      public static final Coerce<Long> LONG_OBJECT = new LongObjectCoerce( coercionsPrivate );
    112      public static final Coerce<Float> FLOAT = new FloatCoerce( coercionsPrivate );
    113      public static final Coerce<Float> FLOAT_OBJECT = new FloatObjectCoerce( coercionsPrivate );
    114      public static final Coerce<Boolean> BOOLEAN = new BooleanCoerce( coercionsPrivate );
    115      public static final Coerce<Boolean> BOOLEAN_OBJECT = new BooleanObjectCoerce( coercionsPrivate );
    116      public static final Coerce<BigDecimal> BIG_DECIMAL = new BigDecimalCoerce( coercionsPrivate );
    117
    118      static
    119        {
    120        for( Type type : coercionsPrivate.keySet() )
    121          typesPrivate.put( Util.getTypeName( type ), type );
    122        }
    123
    124      private static final Map<Class, Class> primitivesPrivate = new IdentityHashMap<Class, Class>();
    125      public static final Map<Class, Class> primitives = Collections.unmodifiableMap( primitivesPrivate );
    126
    127      static
    128        {
    129        primitivesPrivate.put( Boolean.TYPE, Boolean.class );
    130        primitivesPrivate.put( Byte.TYPE, Byte.class );
    131        primitivesPrivate.put( Short.TYPE, Short.class );
    132        primitivesPrivate.put( Integer.TYPE, Integer.class );
    133        primitivesPrivate.put( Long.TYPE, Long.class );
    134        primitivesPrivate.put( Float.TYPE, Float.class );
    135        primitivesPrivate.put( Double.TYPE, Double.class );
    136        }
    137
    138      /**
    139       * Returns the primitive wrapper fo the given type, if the given type represents a primitive, otherwise
    140       * the type is returned.
    141       *
    142       * @param type of type Class
    143       * @return a Class
    144       */
    145      public static Class asNonPrimitive( Class type )
    146        {
    147        if( type.isPrimitive() )
    148          return primitives.get( type );
    149
    150        return type;
    151        }
    152
    153      public static Class[] asNonPrimitive( Class[] types )
    154        {
    155        Class[] results = new Class[ types.length ];
    156
    157        for( int i = 0; i < types.length; i++ )
    158          results[ i ] = asNonPrimitive( types[ i ] );
    159
    160        return results;
    161        }
    162
    163      /**
    164       * Method coercibleTypeFor returns the {@link CoercibleType} for the given {@link Type} instance.
    165       * <p/>
    166       * If type is null, the {@link #OBJECT} CoercibleType is returned.
    167       * <p/>
    168       * If type is an instance of CoercibleType, the given type is returned.
    169       * <p/>
    170       * If no mapping is found, an {@link IllegalStateException} will be thrown.
    171       *
    172       * @param type the type to look up
    173       * @return a CoercibleType for the given type
    174       */
    175      public static CoercibleType coercibleTypeFor( Type type )
    176        {
    177        if( type == null )
    178          return OBJECT;
    179
    180        if( CoercibleType.class.isInstance( type ) )
    181          return (CoercibleType) type;
    182
    183        Coerce coerce = coercionsPrivate.get( type );
    184
    185        if( coerce == null )
    186          return OBJECT;
    187
    188        return coerce;
    189        }
    190
    191      /**
    192       * Method coerce will coerce the given value to the given type using any {@link CoercibleType} mapping available.
    193       * <p/>
    194       * If no mapping is found, the {@link #OBJECT} CoercibleType will be use.
    195       *
    196       * @param value the value to coerce, may be null.
    197       * @param type  the type to coerce to via any mapped CoercibleType
    198       * @param <T>   the type expected
    199       * @return the coerced value
    200       */
    201      public static final <T> T coerce( Object value, Type type )
    202        {
    203        Coerce<T> coerce = coercionsPrivate.get( type );
    204
    205        if( coerce == null )
    206          return (T) OBJECT.coerce( value );
    207
    208        return coerce.coerce( value );
    209        }
    210
    211      /**
    212       * Method coerce will coerce the given value to the given type using the given {@link CoercibleType}.
    213       * <p/>
    214       * If the given CoercibleType is equivalent ({@link #equals(Object)}) to the given Type, the value
    215       * is returned. Note the Type can be itself a CoercibleType, so unnecessary work is prevented.
    216       *
    217       * @param currentType the current Type of the value.
    218       * @param value       the value to coerce, may be null.
    219       * @param type        the type to coerce to via any mapped CoercibleType
    220       * @return the coerced value
    221       */
    222      public static final Object coerce( CoercibleType currentType, Object value, Type type )
    223        {
    224        if( currentType.equals( type ) )
    225          return value;
    226
    227        return currentType.coerce( value, type );
    228        }
    229
    230      /**
    231       * Method coercibleArray will return an array of {@link CoercibleType} instances based on the
    232       * given field type information. Each element of {@link cascading.tuple.Fields#getTypes()}
    233       * will be used to lookup the corresponding CoercibleType.
    234       *
    235       * @param fields an instance of Fields with optional type information.
    236       * @return array of CoercibleType
    237       */
    238      public static CoercibleType[] coercibleArray( Fields fields )
    239        {
    240        return coercibleArray( fields.size(), fields.getTypes() );
    241        }
    242
    243      /**
    244       * Method coercibleArray will return an array of {@link CoercibleType} instances based on the
    245       * given type array. Each element of the type array
    246       * will be used to lookup the corresponding CoercibleType.
    247       *
    248       * @param size  the size of the expected array, must equal {@code types.length} if {@code types != null}
    249       * @param types an array of types to lookup
    250       * @return array of CoercibleType
    251       */
    252      public static CoercibleType[] coercibleArray( int size, Type[] types )
    253        {
    254        CoercibleType[] coercions = new CoercibleType[ size ];
    255
    256        if( types == null )
    257          {
    258          Arrays.fill( coercions, OBJECT );
    259          return coercions;
    260          }
    261
    262        for( int i = 0; i < types.length; i++ )
    263          coercions[ i ] = coercibleTypeFor( types[ i ] );
    264
    265        return coercions;
    266        }
    267
    268      /**
    269       * Method asClass is a convenience method for casting the given type to a {@link Class} if an instance of Class
    270       * or to {@link Object} if not.
    271       *
    272       * @param type of type Type
    273       * @return of type Class
    274       */
    275      public static Class asClass( Type type )
    276        {
    277        if( Class.class.isInstance( type ) )
    278          return (Class) type;
    279
    280        return Object.class;
    281        }
    282
    283      /**
    284       * Method asType is a convenience method for looking up a type name (like {@code "int"} or {@code "java.lang.String"}
    285       * to its corresponding {@link Class} or instance of CoercibleType.
    286       * <p/>
    287       * If the name is not in the {@link #types} map, the classname will be loaded from the current {@link ClassLoader}.
    288       *
    289       * @param typeName a string class or type nam.
    290       * @return an instance of the requested type class.
    291       */
    292      public static Type asType( String typeName )
    293        {
    294        Type type = typesPrivate.get( typeName );
    295
    296        if( type != null )
    297          return type;
    298
    299        Class typeClass = getType( typeName );
    300
    301        if( CoercibleType.class.isAssignableFrom( typeClass ) )
    302          return getInstance( typeClass );
    303
    304        return typeClass;
    305        }
    306
    307      public static String[] getTypeNames( Type[] types )
    308        {
    309        String[] names = new String[ types.length ];
    310
    311        for( int i = 0; i < types.length; i++ )
    312          {
    313          if( Class.class.isInstance( types[ i ] ) )
    314            names[ i ] = ( (Class) types[ i ] ).getName();
    315          else
    316            names[ i ] = types[ i ].getClass().getName();
    317          }
    318
    319        return names;
    320        }
    321
    322      public static Type[] getTypes( String[] names )
    323        {
    324        Type[] types = new Type[ names.length ];
    325
    326        for( int i = 0; i < names.length; i++ )
    327          types[ i ] = asType( names[ i ] );
    328
    329        return types;
    330        }
    331
    332      public static Class[] getCanonicalTypes( Type[] types )
    333        {
    334        Class[] canonicalTypes = new Class[ types.length ];
    335
    336        for( int i = 0; i < types.length; i++ )
    337          {
    338          if( CoercibleType.class.isInstance( types[ i ] ) )
    339            canonicalTypes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType();
    340          else
    341            canonicalTypes[ i ] = (Class) types[ i ];
    342          }
    343
    344        return canonicalTypes;
    345        }
    346
    347      private static CoercibleType getInstance( Class<CoercibleType> typeClass )
    348        {
    349        try
    350          {
    351          return typeClass.newInstance();
    352          }
    353        catch( Exception exception )
    354          {
    355          throw new CascadeException( "unable to instantiate class: " + Util.getTypeName( typeClass ) );
    356          }
    357        }
    358
    359      private static Class<?> getType( String typeName )
    360        {
    361        try
    362          {
    363          return Coercions.class.getClassLoader().loadClass( typeName );
    364          }
    365        catch( ClassNotFoundException exception )
    366          {
    367          throw new CascadeException( "unable to load class: " + typeName );
    368          }
    369        }
    370      }