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