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 }