001/*
002 * Copyright (c) 2007-2015 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
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  }