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}