001// Copyright 2014 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014package org.apache.tapestry5.commons.internal; 015 016import java.io.File; 017import java.lang.reflect.Array; 018import java.math.BigDecimal; 019import java.math.BigInteger; 020import java.time.DayOfWeek; 021import java.time.Duration; 022import java.time.Instant; 023import java.time.LocalDate; 024import java.time.LocalDateTime; 025import java.time.LocalTime; 026import java.time.Month; 027import java.time.MonthDay; 028import java.time.OffsetDateTime; 029import java.time.OffsetTime; 030import java.time.Period; 031import java.time.Year; 032import java.time.YearMonth; 033import java.time.ZoneId; 034import java.time.ZoneOffset; 035import java.time.ZonedDateTime; 036import java.util.Arrays; 037import java.util.Collection; 038import java.util.Collections; 039import java.util.Date; 040import java.util.List; 041 042import org.apache.tapestry5.commons.Configuration; 043import org.apache.tapestry5.commons.MappedConfiguration; 044import org.apache.tapestry5.commons.services.Coercion; 045import org.apache.tapestry5.commons.services.CoercionTuple; 046import org.apache.tapestry5.commons.services.TypeCoercer; 047import org.apache.tapestry5.commons.util.StringToEnumCoercion; 048import org.apache.tapestry5.commons.util.TimeInterval; 049import org.apache.tapestry5.func.Flow; 050 051/** 052 * Class that provides Tapestry-IoC's basic type coercions. 053 * @see TypeCoercer 054 * @see Coercion 055 */ 056public class BasicTypeCoercions 057{ 058 /** 059 * Provides the basic type coercions to a {@link MappedConfiguration} instance. 060 */ 061 public static void provideBasicTypeCoercions( 062 MappedConfiguration<CoercionTuple.Key, CoercionTuple> configuration) 063 { 064 add(configuration, Object.class, String.class, new Coercion<Object, String>() 065 { 066 @Override 067 public String coerce(Object input) 068 { 069 return input.toString(); 070 } 071 }); 072 073 add(configuration, Object.class, Boolean.class, new Coercion<Object, Boolean>() 074 { 075 @Override 076 public Boolean coerce(Object input) 077 { 078 return input != null; 079 } 080 }); 081 082 add(configuration, String.class, Double.class, new Coercion<String, Double>() 083 { 084 @Override 085 public Double coerce(String input) 086 { 087 return Double.valueOf(input); 088 } 089 }); 090 091 // String to BigDecimal is important, as String->Double->BigDecimal would lose 092 // precision. 093 094 add(configuration, String.class, BigDecimal.class, new Coercion<String, BigDecimal>() 095 { 096 @Override 097 public BigDecimal coerce(String input) 098 { 099 return new BigDecimal(input); 100 } 101 }); 102 103 add(configuration, BigDecimal.class, Double.class, new Coercion<BigDecimal, Double>() 104 { 105 @Override 106 public Double coerce(BigDecimal input) 107 { 108 return input.doubleValue(); 109 } 110 }); 111 112 add(configuration, String.class, BigInteger.class, new Coercion<String, BigInteger>() 113 { 114 @Override 115 public BigInteger coerce(String input) 116 { 117 return new BigInteger(input); 118 } 119 }); 120 121 add(configuration, String.class, Long.class, new Coercion<String, Long>() 122 { 123 @Override 124 public Long coerce(String input) 125 { 126 return Long.valueOf(input); 127 } 128 }); 129 130 add(configuration, String.class, Integer.class, Integer::valueOf); 131 132 add(configuration, Long.class, Byte.class, new Coercion<Long, Byte>() 133 { 134 @Override 135 public Byte coerce(Long input) 136 { 137 return input.byteValue(); 138 } 139 }); 140 141 add(configuration, Long.class, Short.class, new Coercion<Long, Short>() 142 { 143 @Override 144 public Short coerce(Long input) 145 { 146 return input.shortValue(); 147 } 148 }); 149 150 add(configuration, Long.class, Integer.class, new Coercion<Long, Integer>() 151 { 152 @Override 153 public Integer coerce(Long input) 154 { 155 return input.intValue(); 156 } 157 }); 158 159 add(configuration, Number.class, Long.class, new Coercion<Number, Long>() 160 { 161 @Override 162 public Long coerce(Number input) 163 { 164 return input.longValue(); 165 } 166 }); 167 168 add(configuration, Double.class, Float.class, new Coercion<Double, Float>() 169 { 170 @Override 171 public Float coerce(Double input) 172 { 173 return input.floatValue(); 174 } 175 }); 176 177 add(configuration, Long.class, Double.class, new Coercion<Long, Double>() 178 { 179 @Override 180 public Double coerce(Long input) 181 { 182 return input.doubleValue(); 183 } 184 }); 185 186 add(configuration, String.class, Boolean.class, new Coercion<String, Boolean>() 187 { 188 @Override 189 public Boolean coerce(String input) 190 { 191 String trimmed = input == null ? "" : input.trim(); 192 193 if (trimmed.equalsIgnoreCase("false") || trimmed.length() == 0) 194 return false; 195 196 // Any non-blank string but "false" 197 198 return true; 199 } 200 }); 201 202 add(configuration, Number.class, Boolean.class, new Coercion<Number, Boolean>() 203 { 204 @Override 205 public Boolean coerce(Number input) 206 { 207 return input.longValue() != 0; 208 } 209 }); 210 211 add(configuration, Void.class, Boolean.class, new Coercion<Void, Boolean>() 212 { 213 @Override 214 public Boolean coerce(Void input) 215 { 216 return false; 217 } 218 }); 219 220 add(configuration, Collection.class, Boolean.class, new Coercion<Collection, Boolean>() 221 { 222 @Override 223 public Boolean coerce(Collection input) 224 { 225 return !input.isEmpty(); 226 } 227 }); 228 229 add(configuration, Object.class, List.class, new Coercion<Object, List>() 230 { 231 @Override 232 public List coerce(Object input) 233 { 234 return Collections.singletonList(input); 235 } 236 }); 237 238 add(configuration, Object[].class, List.class, new Coercion<Object[], List>() 239 { 240 @Override 241 public List coerce(Object[] input) 242 { 243 return Arrays.asList(input); 244 } 245 }); 246 247 add(configuration, Object[].class, Boolean.class, new Coercion<Object[], Boolean>() 248 { 249 @Override 250 public Boolean coerce(Object[] input) 251 { 252 return input != null && input.length > 0; 253 } 254 }); 255 256 add(configuration, Float.class, Double.class, new Coercion<Float, Double>() 257 { 258 @Override 259 public Double coerce(Float input) 260 { 261 return input.doubleValue(); 262 } 263 }); 264 265 Coercion primitiveArrayCoercion = new Coercion<Object, List>() 266 { 267 @Override 268 public List<Object> coerce(Object input) 269 { 270 int length = Array.getLength(input); 271 Object[] array = new Object[length]; 272 for (int i = 0; i < length; i++) 273 { 274 array[i] = Array.get(input, i); 275 } 276 return Arrays.asList(array); 277 } 278 }; 279 280 add(configuration, byte[].class, List.class, primitiveArrayCoercion); 281 add(configuration, short[].class, List.class, primitiveArrayCoercion); 282 add(configuration, int[].class, List.class, primitiveArrayCoercion); 283 add(configuration, long[].class, List.class, primitiveArrayCoercion); 284 add(configuration, float[].class, List.class, primitiveArrayCoercion); 285 add(configuration, double[].class, List.class, primitiveArrayCoercion); 286 add(configuration, char[].class, List.class, primitiveArrayCoercion); 287 add(configuration, boolean[].class, List.class, primitiveArrayCoercion); 288 289 add(configuration, String.class, File.class, new Coercion<String, File>() 290 { 291 @Override 292 public File coerce(String input) 293 { 294 return new File(input); 295 } 296 }); 297 298 add(configuration, String.class, TimeInterval.class, new Coercion<String, TimeInterval>() 299 { 300 @Override 301 public TimeInterval coerce(String input) 302 { 303 return new TimeInterval(input); 304 } 305 }); 306 307 add(configuration, TimeInterval.class, Long.class, new Coercion<TimeInterval, Long>() 308 { 309 @Override 310 public Long coerce(TimeInterval input) 311 { 312 return input.milliseconds(); 313 } 314 }); 315 316 add(configuration, Object.class, Object[].class, new Coercion<Object, Object[]>() 317 { 318 @Override 319 public Object[] coerce(Object input) 320 { 321 return new Object[] 322 {input}; 323 } 324 }); 325 326 add(configuration, Collection.class, Object[].class, new Coercion<Collection, Object[]>() 327 { 328 @Override 329 public Object[] coerce(Collection input) 330 { 331 return input.toArray(); 332 } 333 }); 334 335 CoercionTuple<Flow, List> flowToListCoercion = CoercionTuple.create(Flow.class, List.class, Flow::toList); 336 configuration.add(flowToListCoercion.getKey(), flowToListCoercion); 337 338 CoercionTuple<Flow, Boolean> flowToBooleanCoercion = CoercionTuple.create(Flow.class, Boolean.class, (i) -> !i.isEmpty()); 339 configuration.add(flowToBooleanCoercion.getKey(), flowToBooleanCoercion); 340 341 342 } 343 344 /** 345 * Provides the basic type coercions for JSR310 (java.time.*) to a {@link Configuration} 346 * instance. 347 * TAP5-2645 348 */ 349 public static void provideJSR310TypeCoercions( 350 MappedConfiguration<CoercionTuple.Key, CoercionTuple> configuration) 351 { 352 { 353 add(configuration, Year.class, Integer.class, Year::getValue); 354 add(configuration, Integer.class, Year.class, Year::of); 355 } 356 357 { 358 add(configuration, Month.class, Integer.class, Month::getValue); 359 add(configuration, Integer.class, Month.class, Month::of); 360 361 add(configuration, String.class, Month.class, StringToEnumCoercion.create(Month.class)); 362 } 363 364 { 365 add(configuration, String.class, YearMonth.class, YearMonth::parse); 366 367 add(configuration, YearMonth.class, Year.class, input -> Year.of(input.getYear())); 368 add(configuration, YearMonth.class, Month.class, YearMonth::getMonth); 369 } 370 371 { 372 add(configuration, String.class, MonthDay.class, MonthDay::parse); 373 374 add(configuration, MonthDay.class, Month.class, MonthDay::getMonth); 375 } 376 377 { 378 add(configuration, DayOfWeek.class, Integer.class, DayOfWeek::getValue); 379 add(configuration, Integer.class, DayOfWeek.class, DayOfWeek::of); 380 381 add(configuration, String.class, DayOfWeek.class, 382 StringToEnumCoercion.create(DayOfWeek.class)); 383 } 384 385 { 386 add(configuration, LocalDate.class, Instant.class, input -> { 387 return input.atStartOfDay(ZoneId.systemDefault()).toInstant(); 388 }); 389 add(configuration, Instant.class, LocalDate.class, input -> { 390 return input.atZone(ZoneId.systemDefault()).toLocalDate(); 391 }); 392 393 add(configuration, String.class, LocalDate.class, LocalDate::parse); 394 395 add(configuration, LocalDate.class, YearMonth.class, input -> { 396 return YearMonth.of(input.getYear(), input.getMonth()); 397 }); 398 399 add(configuration, LocalDate.class, MonthDay.class, input -> { 400 return MonthDay.of(input.getMonth(), input.getDayOfMonth()); 401 }); 402 } 403 404 { 405 add(configuration, LocalTime.class, Long.class, LocalTime::toNanoOfDay); 406 add(configuration, Long.class, LocalTime.class, LocalTime::ofNanoOfDay); 407 408 add(configuration, String.class, LocalTime.class, LocalTime::parse); 409 } 410 411 { 412 add(configuration, String.class, LocalDateTime.class, LocalDateTime::parse); 413 414 add(configuration, LocalDateTime.class, Instant.class, input -> { 415 return input.atZone(ZoneId.systemDefault()).toInstant(); 416 }); 417 add(configuration, Instant.class, LocalDateTime.class, input -> { 418 return LocalDateTime.ofInstant(input, ZoneId.systemDefault()); 419 }); 420 421 add(configuration, LocalDateTime.class, LocalDate.class, LocalDateTime::toLocalDate); 422 } 423 424 { 425 add(configuration, String.class, OffsetDateTime.class, OffsetDateTime::parse); 426 427 add(configuration, OffsetDateTime.class, Instant.class, OffsetDateTime::toInstant); 428 429 add(configuration, OffsetDateTime.class, OffsetTime.class, 430 OffsetDateTime::toOffsetTime); 431 } 432 433 { 434 add(configuration, String.class, ZoneId.class, ZoneId::of); 435 } 436 437 { 438 add(configuration, String.class, ZoneOffset.class, ZoneOffset::of); 439 } 440 441 { 442 add(configuration, String.class, ZonedDateTime.class, ZonedDateTime::parse); 443 444 add(configuration, ZonedDateTime.class, Instant.class, ZonedDateTime::toInstant); 445 446 add(configuration, ZonedDateTime.class, ZoneId.class, ZonedDateTime::getZone); 447 } 448 449 { 450 add(configuration, Instant.class, Long.class, Instant::toEpochMilli); 451 add(configuration, Long.class, Instant.class, Instant::ofEpochMilli); 452 453 add(configuration, Instant.class, Date.class, Date::from); 454 add(configuration, Date.class, Instant.class, Date::toInstant); 455 } 456 457 { 458 add(configuration, Duration.class, Long.class, Duration::toNanos); 459 add(configuration, Long.class, Duration.class, Duration::ofNanos); 460 } 461 462 { 463 add(configuration, String.class, Period.class, Period::parse); 464 } 465 } 466 467 private static <S, T> void add(MappedConfiguration<CoercionTuple.Key, CoercionTuple> configuration, Class<S> sourceType, 468 Class<T> targetType, Coercion<S, T> coercion) 469 { 470 CoercionTuple<S, T> tuple = CoercionTuple.create(sourceType, targetType, coercion); 471 configuration.add(tuple.getKey(), tuple); 472 } 473 474 475 476}