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}