001/*
002 * Copyright (c) 2016-2017 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      return (T) OBJECT.coerce( value );
208
209    return coerce.coerce( value );
210    }
211
212  /**
213   * Method coerce will coerce the given value to the given type using the given {@link CoercibleType}.
214   * <p>
215   * If the given CoercibleType is equivalent ({@link #equals(Object)}) to the given Type, the value
216   * is returned. Note the Type can be itself a CoercibleType, so unnecessary work is prevented.
217   *
218   * @param currentType the current Type of the value.
219   * @param value       the value to coerce, may be null.
220   * @param type        the type to coerce to via any mapped CoercibleType
221   * @return the coerced value
222   */
223  public static final Object coerce( CoercibleType currentType, Object value, Type type )
224    {
225    if( currentType.equals( type ) )
226      return value;
227
228    return currentType.coerce( value, type );
229    }
230
231  /**
232   * Method coercibleArray will return an array of {@link CoercibleType} instances based on the
233   * given field type information. Each element of {@link cascading.tuple.Fields#getTypes()}
234   * will be used to lookup the corresponding CoercibleType.
235   *
236   * @param fields an instance of Fields with optional type information.
237   * @return array of CoercibleType
238   */
239  public static CoercibleType[] coercibleArray( Fields fields )
240    {
241    return coercibleArray( fields.size(), fields.getTypes() );
242    }
243
244  /**
245   * Method coercibleArray will return an array of {@link CoercibleType} instances based on the
246   * given type array. Each element of the type array
247   * will be used to lookup the corresponding CoercibleType.
248   *
249   * @param size  the size of the expected array, must equal {@code types.length} if {@code types != null}
250   * @param types an array of types to lookup
251   * @return array of CoercibleType
252   */
253  public static CoercibleType[] coercibleArray( int size, Type[] types )
254    {
255    CoercibleType[] coercions = new CoercibleType[ size ];
256
257    if( types == null )
258      {
259      Arrays.fill( coercions, OBJECT );
260      return coercions;
261      }
262
263    for( int i = 0; i < types.length; i++ )
264      coercions[ i ] = coercibleTypeFor( types[ i ] );
265
266    return coercions;
267    }
268
269  /**
270   * Method asClass is a convenience method for casting the given type to a {@link Class} if an instance of Class
271   * or to {@link Object} if not.
272   *
273   * @param type of type Type
274   * @return of type Class
275   */
276  public static Class asClass( Type type )
277    {
278    if( Class.class.isInstance( type ) )
279      return (Class) type;
280
281    return Object.class;
282    }
283
284  /**
285   * Method asType is a convenience method for looking up a type name (like {@code "int"} or {@code "java.lang.String"}
286   * to its corresponding {@link Class} or instance of CoercibleType.
287   * <p>
288   * If the name is not in the {@link #types} map, the classname will be loaded from the current {@link ClassLoader}.
289   *
290   * @param typeName a string class or type nam.
291   * @return an instance of the requested type class.
292   */
293  public static Type asType( String typeName )
294    {
295    Type type = typesPrivate.get( typeName );
296
297    if( type != null )
298      return type;
299
300    Class typeClass = getType( typeName );
301
302    if( CoercibleType.class.isAssignableFrom( typeClass ) )
303      return getInstance( typeClass );
304
305    return typeClass;
306    }
307
308  public static String[] getTypeNames( Type[] types )
309    {
310    String[] names = new String[ types.length ];
311
312    for( int i = 0; i < types.length; i++ )
313      {
314      if( Class.class.isInstance( types[ i ] ) )
315        names[ i ] = ( (Class) types[ i ] ).getName();
316      else
317        names[ i ] = types[ i ].getClass().getName();
318      }
319
320    return names;
321    }
322
323  public static Type[] getTypes( String[] names )
324    {
325    Type[] types = new Type[ names.length ];
326
327    for( int i = 0; i < names.length; i++ )
328      types[ i ] = asType( names[ i ] );
329
330    return types;
331    }
332
333  public static Class[] getCanonicalTypes( Type[] types )
334    {
335    Class[] canonicalTypes = new Class[ types.length ];
336
337    for( int i = 0; i < types.length; i++ )
338      {
339      if( CoercibleType.class.isInstance( types[ i ] ) )
340        canonicalTypes[ i ] = ( (CoercibleType) types[ i ] ).getCanonicalType();
341      else
342        canonicalTypes[ i ] = (Class) types[ i ];
343      }
344
345    return canonicalTypes;
346    }
347
348  private static CoercibleType getInstance( Class<CoercibleType> typeClass )
349    {
350    try
351      {
352      return typeClass.newInstance();
353      }
354    catch( Exception exception )
355      {
356      throw new CascadeException( "unable to instantiate class: " + Util.getTypeName( typeClass ) );
357      }
358    }
359
360  private static Class<?> getType( String typeName )
361    {
362    try
363      {
364      return Coercions.class.getClassLoader().loadClass( typeName );
365      }
366    catch( ClassNotFoundException exception )
367      {
368      throw new CascadeException( "unable to load class: " + typeName );
369      }
370    }
371  }