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