001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  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 distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.transforms;
014
015import java.lang.reflect.*;
016import java.time.*;
017import java.time.chrono.*;
018import java.time.format.*;
019import java.time.temporal.*;
020import java.util.*;
021import java.util.concurrent.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.internal.*;
025import org.apache.juneau.reflect.*;
026import org.apache.juneau.transform.*;
027
028/**
029 * Swap that converts {@link Temporal} objects to strings.
030 *
031 * <p>
032 * Uses the {@link DateTimeFormatter} class for converting {@link Temporal} objects to and from strings.
033 *
034 * <p>
035 * Supports any of the following temporal objects:
036 * <ul class='javatree'>
037 *    <li class='jc'>{@link HijrahDate}
038 *    <li class='jc'>{@link Instant}
039 *    <li class='jc'>{@link JapaneseDate}
040 *    <li class='jc'>{@link LocalDate}
041 *    <li class='jc'>{@link LocalDateTime}
042 *    <li class='jc'>{@link LocalTime}
043 *    <li class='jc'>{@link MinguoDate}
044 *    <li class='jc'>{@link OffsetDateTime}
045 *    <li class='jc'>{@link OffsetTime}
046 *    <li class='jc'>{@link ThaiBuddhistDate}
047 *    <li class='jc'>{@link Year}
048 *    <li class='jc'>{@link YearMonth}
049 *    <li class='jc'>{@link ZonedDateTime}
050 * </ul>
051 */
052public class TemporalSwap extends StringSwap<Temporal> {
053
054   /**
055    * Default swap to {@link DateTimeFormatter#BASIC_ISO_DATE}.
056    * <p>
057    * Example: <js>"20111203"</js>
058    */
059   public static class BasicIsoDate extends TemporalSwap {
060
061      /** Default instance.*/
062      public static final TemporalSwap DEFAULT = new BasicIsoDate();
063
064      /** Constructor.*/
065      public BasicIsoDate() {
066         super("BASIC_ISO_DATE", true);
067      }
068   }
069
070   /**
071    * Default swap to {@link DateTimeFormatter#ISO_DATE}.
072    * <p>
073    * Example: <js>"2011-12-03+01:00"</js> or <js>"2011-12-03"</js>
074    */
075   public static class IsoDate extends TemporalSwap {
076
077      /** Default instance.*/
078      public static final TemporalSwap DEFAULT = new IsoDate();
079
080      /** Constructor.*/
081      public IsoDate() {
082         super("ISO_DATE", true);
083      }
084   }
085
086   /**
087    * Default swap to {@link DateTimeFormatter#ISO_DATE_TIME}.
088    * <p>
089    * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>
090    */
091   public static class IsoDateTime extends TemporalSwap {
092
093      /** Default instance.*/
094      public static final TemporalSwap DEFAULT = new IsoDateTime();
095
096      /** Constructor.*/
097      public IsoDateTime() {
098         super("ISO_DATE_TIME", true);
099      }
100   }
101
102   /**
103    * Default swap to {@link DateTimeFormatter#ISO_INSTANT}.
104    * <p>
105    * Example: <js>"2011-12-03T10:15:30Z"</js>
106    */
107   public static class IsoInstant extends TemporalSwap {
108
109      /** Default instance.*/
110      public static final TemporalSwap DEFAULT = new IsoInstant();
111
112      /** Constructor.*/
113      public IsoInstant() {
114         super("ISO_INSTANT", false);
115      }
116   }
117
118   /**
119    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE}.
120    * <p>
121    * Example: <js>"2011-12-03"</js>
122    */
123   public static class IsoLocalDate extends TemporalSwap {
124
125      /** Default instance.*/
126      public static final TemporalSwap DEFAULT = new IsoLocalDate();
127
128      /** Constructor.*/
129      public IsoLocalDate() {
130         super("ISO_LOCAL_DATE", false);
131      }
132   }
133
134   /**
135    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}.
136    * <p>
137    * Example: <js>"2011-12-03T10:15:30"</js>
138    */
139   public static class IsoLocalDateTime extends TemporalSwap {
140
141      /** Default instance.*/
142      public static final TemporalSwap DEFAULT = new IsoLocalDateTime();
143
144      /** Constructor.*/
145      public IsoLocalDateTime() {
146         super("ISO_LOCAL_DATE_TIME", true);
147      }
148   }
149
150   /**
151    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_TIME}.
152    * <p>
153    * Example: <js>"10:15:30"</js>
154    */
155   public static class IsoLocalTime extends TemporalSwap {
156
157      /** Default instance.*/
158      public static final TemporalSwap DEFAULT = new IsoLocalTime();
159
160      /** Constructor.*/
161      public IsoLocalTime() {
162         super("ISO_LOCAL_TIME", true);
163      }
164   }
165
166   /**
167    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE}.
168    * <p>
169    * Example: <js>"2011-12-03"</js>
170    */
171   public static class IsoOffsetDate extends TemporalSwap {
172
173      /** Default instance.*/
174      public static final TemporalSwap DEFAULT = new IsoOffsetDate();
175
176      /** Constructor.*/
177      public IsoOffsetDate() {
178         super("ISO_OFFSET_DATE", false);
179      }
180   }
181
182   /**
183    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}.
184    * <p>
185    * Example: <js>"2011-12-03T10:15:30+01:00"</js>
186    */
187   public static class IsoOffsetDateTime extends TemporalSwap {
188
189      /** Default instance.*/
190      public static final TemporalSwap DEFAULT = new IsoOffsetDateTime();
191
192      /** Constructor.*/
193      public IsoOffsetDateTime() {
194         super("ISO_OFFSET_DATE_TIME", false);
195      }
196   }
197
198   /**
199    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_TIME}.
200    * <p>
201    * Example: <js>"10:15:30+01:00"</js>
202    */
203   public static class IsoOffsetTime extends TemporalSwap {
204
205      /** Default instance.*/
206      public static final TemporalSwap DEFAULT = new IsoOffsetTime();
207
208      /** Constructor.*/
209      public IsoOffsetTime() {
210         super("ISO_OFFSET_TIME", false);
211      }
212   }
213
214   /**
215    * Default swap to {@link DateTimeFormatter#ISO_ORDINAL_DATE}.
216    * <p>
217    * Example: <js>"2012-337"</js>
218    */
219   public static class IsoOrdinalDate extends TemporalSwap {
220
221      /** Default instance.*/
222      public static final TemporalSwap DEFAULT = new IsoOrdinalDate();
223
224      /** Constructor.*/
225      public IsoOrdinalDate() {
226         super("ISO_ORDINAL_DATE", true);
227      }
228   }
229
230   /**
231    * Default swap to {@link DateTimeFormatter#ISO_TIME}.
232    * <p>
233    * Example: <js>"10:15:30+01:00"</js> or <js>"10:15:30"</js>
234    */
235   public static class IsoTime extends TemporalSwap {
236
237      /** Default instance.*/
238      public static final TemporalSwap DEFAULT = new IsoTime();
239
240      /** Constructor.*/
241      public IsoTime() {
242         super("ISO_TIME", true);
243      }
244   }
245
246   /**
247    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
248    * <p>
249    * Example: <js>"2012-W48-6"</js>
250    */
251   public static class IsoWeekDate extends TemporalSwap {
252
253      /** Default instance.*/
254      public static final TemporalSwap DEFAULT = new IsoWeekDate();
255
256      /** Constructor.*/
257      public IsoWeekDate() {
258         super("ISO_WEEK_DATE", true);
259      }
260   }
261
262   /**
263    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
264    * <p>
265    * Example: <js>"2011"</js>
266    */
267   public static class IsoYear extends TemporalSwap {
268
269      /** Default instance.*/
270      public static final TemporalSwap DEFAULT = new IsoYear();
271
272      /** Constructor.*/
273      public IsoYear() {
274         super("uuuu", true);
275      }
276   }
277
278   /**
279    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
280    * <p>
281    * Example: <js>"2011-12"</js>
282    */
283   public static class IsoYearMonth extends TemporalSwap {
284
285      /** Default instance.*/
286      public static final TemporalSwap DEFAULT = new IsoYearMonth();
287
288      /** Constructor.*/
289      public IsoYearMonth() {
290         super("uuuu-MM", true);
291      }
292   }
293
294   /**
295    * Default swap to {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}.
296    * <p>
297    * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>
298    */
299   public static class IsoZonedDateTime extends TemporalSwap {
300
301      /** Default instance.*/
302      public static final TemporalSwap DEFAULT = new IsoZonedDateTime();
303
304      /** Constructor.*/
305      public IsoZonedDateTime() {
306         super("ISO_ZONED_DATE_TIME", false);
307      }
308   }
309
310   /**
311    * Default swap to {@link DateTimeFormatter#RFC_1123_DATE_TIME}.
312    * <p>
313    * Example: <js>"Tue, 3 Jun 2008 11:05:30 GMT"</js>
314    */
315   public static class Rfc1123DateTime extends TemporalSwap {
316
317      /** Default instance.*/
318      public static final TemporalSwap DEFAULT = new Rfc1123DateTime();
319
320      /** Constructor.*/
321      public Rfc1123DateTime() {
322         super("RFC_1123_DATE_TIME", false);
323      }
324   }
325
326   private static final ZoneId Z = ZoneId.of("Z");
327   private static final Map<Class<? extends Temporal>,Method> FROM_METHODS = new ConcurrentHashMap<>();
328
329   private static Method findParseMethod(Class<? extends Temporal> c) throws ExecutableException {
330      Method m = FROM_METHODS.get(c);
331      if (m == null) {
332         m = ClassInfo.of(c).getStaticPublicMethodInner("from", c, TemporalAccessor.class);
333         if (m == null)
334            throw new ExecutableException("Parse method not found on temporal class ''{0}''", c.getSimpleName());
335         FROM_METHODS.put(c, m);
336      }
337      return m;
338   }
339
340   private final DateTimeFormatter formatter;
341   private final boolean zoneOptional;
342
343   /**
344    * Constructor.
345    *
346    * @param pattern The timestamp format or name of predefined {@link DateTimeFormatter}.
347    * @param zoneOptional <jk>true</jk> if the time zone on the pattern is optional.
348    */
349   public TemporalSwap(String pattern, boolean zoneOptional) {
350      super(Temporal.class);
351      this.formatter = DateUtils.getFormatter(pattern);
352      this.zoneOptional = zoneOptional;
353   }
354
355   /**
356    * Returns <jk>true</jk> if the time zone on the pattern is optional.
357    *
358    * <p>
359    * If it's not optional, then local dates/times must be converted into zoned times using the session time zone.
360    * Otherwise, local date/times are fine.
361    *
362    * @return <jk>true</jk> if the time zone on the pattern is optional.
363    */
364   protected boolean zoneOptional() {
365      return zoneOptional;
366   }
367
368   @Override /* PojoSwap */
369   public String swap(BeanSession session, Temporal o) throws Exception {
370      if (o == null)
371         return null;
372      o = convertToSerializable(session, o);
373      return formatter.format(o);
374   }
375
376   /**
377    * Converts the specified temporal object to a form suitable to be serialized using any pattern.
378    *
379    * @param session The current bean session.
380    * @param t The temporal object to convert.
381    * @return The converted temporal object.
382    */
383   protected Temporal convertToSerializable(BeanSession session, Temporal t) {
384
385      ZoneId zoneId = session.getTimeZoneId();
386      Class<? extends Temporal> tc = t.getClass();
387
388      // Instant is always serialized in GMT.
389      if (tc == Instant.class)
390         return ZonedDateTime.from(defaulting(t, Z));
391
392      // These can handle any pattern.
393      if (tc == ZonedDateTime.class || tc == OffsetDateTime.class)
394         return t;
395
396      // Pattern optionally includes a time zone, so zoned and local date-times are good.
397      if (zoneOptional()) {
398         if (tc == LocalDateTime.class)
399            return t;
400         if (tc == OffsetTime.class)
401            return ZonedDateTime.from(defaulting(t, zoneId));
402         return LocalDateTime.from(defaulting(t, zoneId));
403      }
404
405      return ZonedDateTime.from(defaulting(t, zoneId));
406   }
407
408   @SuppressWarnings("unchecked")
409   @Override /* PojoSwap */
410   public Temporal unswap(BeanSession session, String f, ClassMeta<?> hint) throws Exception {
411      if (hint == null)
412         hint = session.getClassMeta(Instant.class);
413      Class<? extends Temporal> tc = (Class<? extends Temporal>)hint.getInnerClass();
414
415      ZoneId offset = session.getTimeZoneId();
416
417      if (tc == Instant.class)
418         offset = Z;
419
420      Method parseMethod = findParseMethod(tc);
421
422      TemporalAccessor ta = defaulting(formatter.parse(f), offset);
423      return (Temporal)parseMethod.invoke(null, ta);
424   }
425
426   private final TemporalAccessor defaulting(TemporalAccessor t, ZoneId zoneId) {
427      return new DefaultingTemporalAccessor(t, zoneId);
428   }
429}