    001/*
    002 * Copyright (c) 2007-2017 Xplenty, 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
    021package cascading.tuple.coerce;
    022
    023import java.lang.reflect.Type;
    024import java.math.BigDecimal;
    025import java.util.Arrays;
    026import java.util.Collections;
    027import java.util.HashMap;
    028import java.util.IdentityHashMap;
    029import java.util.Map;
    030
    031import cascading.cascade.CascadeException;
    032import cascading.tuple.Fields;
    033import cascading.tuple.type.CoercibleType;
    034import 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 */
    049public 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  }