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