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 }