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.*;
021
022import java.time.*;
023import java.time.format.*;
024import java.time.temporal.*;
025
026/**
027 * Wraps a {@link TemporalAccessor} to provide default values wherever possible instead of throwing unsupported field exceptions.
028 *
029 * <p>
030 * If working correctly, any <c>TemporalAccessor</c> returned by the {@link DateTimeFormatter#parse(CharSequence)} method
031 * should be able to be passed to any <code>Temporal.from(TemporalAccessor)</code> static method (such as {@link ZonedDateTime#from(TemporalAccessor)}).
032 *
033 * <h5 class='section'>See Also:</h5><ul>
034 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SwapBasics">Swap Basics</a>
035 * </ul>
036 */
037public class DefaultingTemporalAccessor implements TemporalAccessor {
038
039   private final TemporalAccessor inner;
040   private final ZoneId zoneId;
041   private ZonedDateTime zdt;
042
043   /**
044    * Constructor.
045    *
046    * @param inner The temporal accessor being wrapped.
047    * @param zoneId The default zone ID if it's not specified in the accessor.
048    */
049   public DefaultingTemporalAccessor(TemporalAccessor inner, ZoneId zoneId) {
050      this.inner = inner;
051      this.zoneId = zoneId;
052   }
053
054   @Override /* TemporalAccessor */
055   public boolean isSupported(TemporalField field) {
056      return inner.isSupported(field);
057   }
058
059   @Override /* TemporalAccessor */
060   @SuppressWarnings("unchecked")
061   public <R> R query(TemporalQuery<R> query) {
062      R r = inner.query(query);
063
064      if (r != null)
065         return r;
066
067      if (query == zone() || query == zoneId())
068         return (R)zoneId;
069
070      if (query == localTime()) {
071
072         if (isSupported(INSTANT_SECONDS))
073            return (R)zdt().toLocalTime();
074
075         int hour = 0;
076         if (isSupported(HOUR_OF_DAY))
077            hour = iget(HOUR_OF_DAY);
078         else if (isSupported(HOUR_OF_AMPM))
079            hour = iget(HOUR_OF_AMPM) + 12 * iget(AMPM_OF_DAY);
080
081         int minute = isSupported(MINUTE_OF_HOUR) ? iget(MINUTE_OF_HOUR) : 0;
082         int second = isSupported(SECOND_OF_MINUTE) ? iget(SECOND_OF_MINUTE) : 0;
083         int nano = isSupported(NANO_OF_SECOND) ? iget(NANO_OF_SECOND) : 0;
084
085         return (R)LocalTime.of(hour, minute, second, nano);
086      }
087
088      if (query == localDate()) {
089
090         if (isSupported(INSTANT_SECONDS))
091            return (R)zdt().toLocalDate();
092
093         int year = isSupported(YEAR) ? iget(ChronoField.YEAR) : 1970;
094         int month = isSupported(MONTH_OF_YEAR) ? iget(MONTH_OF_YEAR) : 1;
095         int dayOfMonth = isSupported(DAY_OF_MONTH) ? iget(DAY_OF_MONTH) : 1;
096
097         return (R)LocalDate.of(year, Month.of(month), dayOfMonth);
098      }
099
100      if (query == offset()) {
101         return (R)zoneId.getRules().getOffset(zdt().toInstant());
102      }
103
104      return null;
105   }
106
107   @Override /* TemporalAccessor */
108   public long getLong(TemporalField field) {
109
110      if (isSupported(field))
111         return inner.getLong(field);
112
113      if (field == INSTANT_SECONDS)
114         return zdt().toEpochSecond();
115
116      if (field == EPOCH_DAY)
117         return zdt().toLocalDate().toEpochDay();
118
119      if (field == YEAR) {
120         if (isSupported(INSTANT_SECONDS))
121            return zdt().toLocalDate().getYear();
122         return 1970;
123      }
124
125      if (field == MONTH_OF_YEAR) {
126         if (isSupported(INSTANT_SECONDS))
127            return zdt().toLocalDate().getMonthValue();
128         return 1;
129      }
130
131      return 0;
132   }
133
134   @Override /* TemporalAccessor */
135   public int get(TemporalField field) {
136      if (inner.isSupported(field))
137         return inner.get(field);
138      return (int)getLong(field);
139   }
140
141   private int iget(TemporalField field) {
142      return inner.get(field);
143   }
144
145   private ZonedDateTime zdt() {
146      if (zdt == null)
147         zdt = ZonedDateTime.from(this);
148      return zdt;
149   }
150}