    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}