    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}