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}