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      /** Constructor.*/
061      public BasicIsoDate() {
062         super("BASIC_ISO_DATE", true);
063      }
064   }
065
066   /**
067    * Default swap to {@link DateTimeFormatter#ISO_DATE}.
068    * <p>
069    * Example: <js>"2011-12-03+01:00"</js> or <js>"2011-12-03"</js>
070    */
071   public static class IsoDate extends TemporalSwap {
072      /** Constructor.*/
073      public IsoDate() {
074         super("ISO_DATE", true);
075      }
076   }
077
078   /**
079    * Default swap to {@link DateTimeFormatter#ISO_DATE_TIME}.
080    * <p>
081    * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>
082    */
083   public static class IsoDateTime extends TemporalSwap {
084      /** Constructor.*/
085      public IsoDateTime() {
086         super("ISO_DATE_TIME", true);
087      }
088   }
089
090   /**
091    * Default swap to {@link DateTimeFormatter#ISO_INSTANT}.
092    * <p>
093    * Example: <js>"2011-12-03T10:15:30Z"</js>
094    */
095   public static class IsoInstant extends TemporalSwap {
096      /** Constructor.*/
097      public IsoInstant() {
098         super("ISO_INSTANT", false);
099      }
100   }
101
102   /**
103    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE}.
104    * <p>
105    * Example: <js>"2011-12-03"</js>
106    */
107   public static class IsoLocalDate extends TemporalSwap {
108      /** Constructor.*/
109      public IsoLocalDate() {
110         super("ISO_LOCAL_DATE", false);
111      }
112   }
113
114   /**
115    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}.
116    * <p>
117    * Example: <js>"2011-12-03T10:15:30"</js>
118    */
119   public static class IsoLocalDateTime extends TemporalSwap {
120      /** Constructor.*/
121      public IsoLocalDateTime() {
122         super("ISO_LOCAL_DATE_TIME", true);
123      }
124   }
125
126   /**
127    * Default swap to {@link DateTimeFormatter#ISO_LOCAL_TIME}.
128    * <p>
129    * Example: <js>"10:15:30"</js>
130    */
131   public static class IsoLocalTime extends TemporalSwap {
132      /** Constructor.*/
133      public IsoLocalTime() {
134         super("ISO_LOCAL_TIME", true);
135      }
136   }
137
138   /**
139    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE}.
140    * <p>
141    * Example: <js>"2011-12-03"</js>
142    */
143   public static class IsoOffsetDate extends TemporalSwap {
144      /** Constructor.*/
145      public IsoOffsetDate() {
146         super("ISO_OFFSET_DATE", false);
147      }
148   }
149
150   /**
151    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}.
152    * <p>
153    * Example: <js>"2011-12-03T10:15:30+01:00"</js>
154    */
155   public static class IsoOffsetDateTime extends TemporalSwap {
156      /** Constructor.*/
157      public IsoOffsetDateTime() {
158         super("ISO_OFFSET_DATE_TIME", false);
159      }
160   }
161
162   /**
163    * Default swap to {@link DateTimeFormatter#ISO_OFFSET_TIME}.
164    * <p>
165    * Example: <js>"10:15:30+01:00"</js>
166    */
167   public static class IsoOffsetTime extends TemporalSwap {
168      /** Constructor.*/
169      public IsoOffsetTime() {
170         super("ISO_OFFSET_TIME", false);
171      }
172   }
173
174   /**
175    * Default swap to {@link DateTimeFormatter#ISO_ORDINAL_DATE}.
176    * <p>
177    * Example: <js>"2012-337"</js>
178    */
179   public static class IsoOrdinalDate extends TemporalSwap {
180      /** Constructor.*/
181      public IsoOrdinalDate() {
182         super("ISO_ORDINAL_DATE", true);
183      }
184   }
185
186   /**
187    * Default swap to {@link DateTimeFormatter#ISO_TIME}.
188    * <p>
189    * Example: <js>"10:15:30+01:00"</js> or <js>"10:15:30"</js>
190    */
191   public static class IsoTime extends TemporalSwap {
192      /** Constructor.*/
193      public IsoTime() {
194         super("ISO_TIME", true);
195      }
196   }
197
198   /**
199    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
200    * <p>
201    * Example: <js>"2012-W48-6"</js>
202    */
203   public static class IsoWeekDate extends TemporalSwap {
204      /** Constructor.*/
205      public IsoWeekDate() {
206         super("ISO_WEEK_DATE", true);
207      }
208   }
209
210   /**
211    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
212    * <p>
213    * Example: <js>"2011"</js>
214    */
215   public static class IsoYear extends TemporalSwap {
216      /** Constructor.*/
217      public IsoYear() {
218         super("uuuu", true);
219      }
220   }
221
222   /**
223    * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}.
224    * <p>
225    * Example: <js>"2011-12"</js>
226    */
227   public static class IsoYearMonth extends TemporalSwap {
228      /** Constructor.*/
229      public IsoYearMonth() {
230         super("uuuu-MM", true);
231      }
232   }
233
234   /**
235    * Default swap to {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}.
236    * <p>
237    * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>
238    */
239   public static class IsoZonedDateTime extends TemporalSwap {
240      /** Constructor.*/
241      public IsoZonedDateTime() {
242         super("ISO_ZONED_DATE_TIME", false);
243      }
244   }
245
246   /**
247    * Default swap to {@link DateTimeFormatter#RFC_1123_DATE_TIME}.
248    * <p>
249    * Example: <js>"Tue, 3 Jun 2008 11:05:30 GMT"</js>
250    */
251   public static class Rfc1123DateTime extends TemporalSwap {
252      /** Constructor.*/
253      public Rfc1123DateTime() {
254         super("RFC_1123_DATE_TIME", false);
255      }
256   }
257
258   private static final ZoneId Z = ZoneId.of("Z");
259   private static final Map<Class<? extends Temporal>,Method> FROM_METHODS = new ConcurrentHashMap<>();
260
261   private static Method findParseMethod(Class<? extends Temporal> c) throws ExecutableException {
262      Method m = FROM_METHODS.get(c);
263      if (m == null) {
264         m = ClassInfo.of(c).getStaticPublicMethodInner("from", c, TemporalAccessor.class);
265         if (m == null)
266            throw new ExecutableException("Parse method not found on temporal class ''{0}''", c.getSimpleName());
267         FROM_METHODS.put(c, m);
268      }
269      return m;
270   }
271
272   private final DateTimeFormatter formatter;
273   private final boolean zoneOptional;
274
275   /**
276    * Constructor.
277    *
278    * @param pattern The timestamp format or name of predefined {@link DateTimeFormatter}.
279    * @param zoneOptional <jk>true</jk> if the time zone on the pattern is optional.
280    */
281   public TemporalSwap(String pattern, boolean zoneOptional) {
282      super(Temporal.class);
283      this.formatter = DateUtils.getFormatter(pattern);
284      this.zoneOptional = zoneOptional;
285   }
286
287   /**
288    * Returns <jk>true</jk> if the time zone on the pattern is optional.
289    *
290    * <p>
291    * If it's not optional, then local dates/times must be converted into zoned times using the session time zone.
292    * Otherwise, local date/times are fine.
293    *
294    * @return <jk>true</jk> if the time zone on the pattern is optional.
295    */
296   protected boolean zoneOptional() {
297      return zoneOptional;
298   }
299
300   @Override /* PojoSwap */
301   public String swap(BeanSession session, Temporal o) throws Exception {
302      if (o == null)
303         return null;
304      o = convertToSerializable(session, o);
305      return formatter.format(o);
306   }
307
308   /**
309    * Converts the specified temporal object to a form suitable to be serialized using any pattern.
310    *
311    * @param session The current bean session.
312    * @param t The temporal object to convert.
313    * @return The converted temporal object.
314    */
315   protected Temporal convertToSerializable(BeanSession session, Temporal t) {
316
317      ZoneId zoneId = session.getTimeZoneId();
318      Class<? extends Temporal> tc = t.getClass();
319
320      // Instant is always serialized in GMT.
321      if (tc == Instant.class)
322         return ZonedDateTime.from(defaulting(t, Z));
323
324      // These can handle any pattern.
325      if (tc == ZonedDateTime.class || tc == OffsetDateTime.class)
326         return t;
327
328      // Pattern optionally includes a time zone, so zoned and local date-times are good.
329      if (zoneOptional()) {
330         if (tc == LocalDateTime.class)
331            return t;
332         if (tc == OffsetTime.class)
333            return ZonedDateTime.from(defaulting(t, zoneId));
334         return LocalDateTime.from(defaulting(t, zoneId));
335      }
336
337      return ZonedDateTime.from(defaulting(t, zoneId));
338   }
339
340   @SuppressWarnings("unchecked")
341   @Override /* PojoSwap */
342   public Temporal unswap(BeanSession session, String f, ClassMeta<?> hint) throws Exception {
343      if (hint == null)
344         hint = session.getClassMeta(Instant.class);
345      Class<? extends Temporal> tc = (Class<? extends Temporal>)hint.getInnerClass();
346
347      ZoneId offset = session.getTimeZoneId();
348
349      if (tc == Instant.class)
350         offset = Z;
351
352      Method parseMethod = findParseMethod(tc);
353
354      TemporalAccessor ta = defaulting(formatter.parse(f), offset);
355      return (Temporal)parseMethod.invoke(null, ta);
356   }
357
358   private final TemporalAccessor defaulting(TemporalAccessor t, ZoneId zoneId) {
359      return new DefaultingTemporalAccessor(t, zoneId);
360   }
361}