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}