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}