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