001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.swaps;
018
019import static java.time.temporal.ChronoField.*;
020import static java.time.temporal.TemporalQueries.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.time.*;
024import java.time.format.*;
025import java.time.temporal.*;
026
027/**
028 * Wraps a {@link TemporalAccessor} to provide default values wherever possible instead of throwing unsupported field exceptions.
029 *
030 * <p>
031 * If working correctly, any <c>TemporalAccessor</c> returned by the {@link DateTimeFormatter#parse(CharSequence)} method
032 * should be able to be passed to any <code>Temporal.from(TemporalAccessor)</code> static method (such as {@link ZonedDateTime#from(TemporalAccessor)}).
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SwapBasics">Swap Basics</a>
036 * </ul>
037 */
038public class DefaultingTemporalAccessor implements TemporalAccessor {
039
040   private final TemporalAccessor inner;
041   private final ZoneId zoneId;
042   private ZonedDateTime zdt;
043
044   /**
045    * Constructor.
046    *
047    * @param inner The temporal accessor being wrapped.
048    * @param zoneId The default zone ID if it's not specified in the accessor.
049    */
050   public DefaultingTemporalAccessor(TemporalAccessor inner, ZoneId zoneId) {
051      this.inner = inner;
052      this.zoneId = zoneId;
053   }
054
055   @Override /* Overridden from TemporalAccessor */
056   public int get(TemporalField field) {
057      if (inner.isSupported(field))
058         return inner.get(field);
059      return (int)getLong(field);
060   }
061
062   @Override /* Overridden from TemporalAccessor */
063   public long getLong(TemporalField field) {
064
065      if (isSupported(field))
066         return inner.getLong(field);
067
068      if (field == INSTANT_SECONDS)
069         return zdt().toEpochSecond();
070
071      if (field == EPOCH_DAY)
072         return zdt().toLocalDate().toEpochDay();
073
074      if (field == YEAR) {
075         if (isSupported(INSTANT_SECONDS))
076            return zdt().toLocalDate().getYear();
077         return 1970;
078      }
079
080      if (field == MONTH_OF_YEAR) {
081         if (isSupported(INSTANT_SECONDS))
082            return zdt().toLocalDate().getMonthValue();
083         return 1;
084      }
085
086      return 0;
087   }
088
089   @Override /* Overridden from TemporalAccessor */
090   public boolean isSupported(TemporalField field) {
091      return inner.isSupported(field);
092   }
093
094   @Override /* Overridden from TemporalAccessor */
095   @SuppressWarnings("unchecked")
096   public <R> R query(TemporalQuery<R> query) {
097      var r = inner.query(query);
098
099      if (nn(r))
100         return r;
101
102      if (query == zone() || query == zoneId())
103         return (R)zoneId;
104
105      if (query == localTime()) {
106
107         if (isSupported(INSTANT_SECONDS))
108            return (R)zdt().toLocalTime();
109
110         var hour = 0;
111         if (isSupported(HOUR_OF_DAY))
112            hour = iget(HOUR_OF_DAY);
113         else if (isSupported(HOUR_OF_AMPM))
114            hour = iget(HOUR_OF_AMPM) + 12 * iget(AMPM_OF_DAY);
115
116         var minute = isSupported(MINUTE_OF_HOUR) ? iget(MINUTE_OF_HOUR) : 0;
117         var second = isSupported(SECOND_OF_MINUTE) ? iget(SECOND_OF_MINUTE) : 0;
118         var nano = isSupported(NANO_OF_SECOND) ? iget(NANO_OF_SECOND) : 0;
119
120         return (R)LocalTime.of(hour, minute, second, nano);
121      }
122
123      if (query == localDate()) {
124
125         if (isSupported(INSTANT_SECONDS))
126            return (R)zdt().toLocalDate();
127
128         var year = isSupported(YEAR) ? iget(ChronoField.YEAR) : 1970;
129         var month = isSupported(MONTH_OF_YEAR) ? iget(MONTH_OF_YEAR) : 1;
130         var dayOfMonth = isSupported(DAY_OF_MONTH) ? iget(DAY_OF_MONTH) : 1;
131
132         return (R)LocalDate.of(year, Month.of(month), dayOfMonth);
133      }
134
135      if (query == offset()) {
136         return (R)zoneId.getRules().getOffset(zdt().toInstant());
137      }
138
139      return null;
140   }
141
142   private int iget(TemporalField field) {
143      return inner.get(field);
144   }
145
146   private ZonedDateTime zdt() {
147      if (zdt == null)
148         zdt = ZonedDateTime.from(this);
149      return zdt;
150   }
151}