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