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    
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      }