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 (the
006 * "License"); you may not use this file except in compliance with the
007 * 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.http.header;
018
019import static java.time.format.DateTimeFormatter.*;
020import static java.time.temporal.ChronoUnit.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022
023import java.time.*;
024import java.util.*;
025import java.util.function.*;
026
027
028/**
029 * Represents an HTTP <c>If-Range</c> header.
030 *
031 * <p>
032 * If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity.
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
036 * </ul>
037 *
038 * @since 9.2.1
039 */
040@SuppressWarnings({
041   "java:S2160" // equals() on HttpHeaderBean uses name + getValue()
042})
043public class IfRange extends HttpHeaderBean {
044
045   /** The header name */
046   public static final String NAME = "If-Range";
047
048   private static boolean isEtag(String s) {
049      return s != null && (s.startsWith("\"") || s.startsWith("W/"));
050   }
051
052   private final EntityTag etagValue;
053   private final ZonedDateTime zonedValue;
054   private final Supplier<?> supplier;
055
056   /**
057    * Constructor with an entity-tag value.
058    *
059    * @param value The entity tag. May be <jk>null</jk>.
060    */
061   public IfRange(EntityTag value) {
062      super(NAME, (String)null);
063      this.etagValue = value;
064      this.zonedValue = null;
065      this.supplier = null;
066   }
067
068   /**
069    * Constructor with a wire string (HTTP-date or entity-tag).
070    *
071    * @param value The header value. May be <jk>null</jk>.
072    */
073   public IfRange(String value) {
074      super(NAME, (String)null);
075      if (value != null && isEtag(value)) {
076         this.etagValue = EntityTag.of(value);
077         this.zonedValue = null;
078      } else {
079         this.etagValue = null;
080         this.zonedValue = ie(value) ? null : parseHttpDate(value);
081      }
082      this.supplier = null;
083   }
084
085   /**
086    * Constructor with a lazy supplier of {@link EntityTag} or {@link ZonedDateTime}.
087    *
088    * @param valueSupplier Supplier for the value. Must not be <jk>null</jk>.
089    */
090   public IfRange(Supplier<?> valueSupplier) {
091      super(NAME, (String)null);
092      this.etagValue = null;
093      this.zonedValue = null;
094      this.supplier = valueSupplier;
095   }
096
097   /**
098    * Constructor with an HTTP-date.
099    *
100    * @param value The date/time. May be <jk>null</jk>.
101    */
102   public IfRange(ZonedDateTime value) {
103      super(NAME, (String)null);
104      this.etagValue = null;
105      this.zonedValue = value;
106      this.supplier = null;
107   }
108
109   /**
110    * @return This header as an {@link EntityTag}.
111    */
112   public Optional<EntityTag> asEntityTag() {
113      if (nn(supplier)) {
114         var o = supplier.get();
115         return o(o instanceof EntityTag o2 ? o2 : null);
116      }
117      return o(etagValue);
118   }
119
120   /**
121    * @return This header as a {@link ZonedDateTime} when the value is an HTTP-date.
122    */
123   public Optional<ZonedDateTime> asZonedDateTime() {
124      if (nn(supplier)) {
125         var o = supplier.get();
126         return o(o instanceof ZonedDateTime o2 ? o2 : null);
127      }
128      return o(zonedValue);
129   }
130
131   @Override /* HttpHeader */
132   public String getValue() {
133      if (nn(supplier)) {
134         var o = supplier.get();
135         if (o == null)
136            return null;
137         if (o instanceof EntityTag o2)
138            return o2.toString();
139         if (o instanceof ZonedDateTime o2)
140            return RFC_1123_DATE_TIME.format(o2);
141         throw rex("Invalid object type returned by supplier: %s", cn(o));
142      }
143      if (nn(etagValue))
144         return s(etagValue);
145      if (nn(zonedValue))
146         return RFC_1123_DATE_TIME.format(zonedValue);
147      return null;
148   }
149
150   /**
151    * Static factory with an entity-tag.
152    *
153    * @param value The tag. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
154    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
155    */
156   public static IfRange of(EntityTag value) {
157      return value == null ? null : new IfRange(value);
158   }
159
160   /**
161    * Static factory with a wire string.
162    *
163    * @param value The value. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
164    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
165    */
166   public static IfRange of(String value) {
167      return value == null ? null : new IfRange(value);
168   }
169
170   /**
171    * Static factory with a lazy supplier.
172    *
173    * @param valueSupplier Supplier of {@link EntityTag} or {@link ZonedDateTime}. Must not be <jk>null</jk>.
174    * @return A new instance. Never <jk>null</jk>.
175    */
176   public static IfRange of(Supplier<?> valueSupplier) {
177      return new IfRange(valueSupplier);
178   }
179
180   /**
181    * Static factory with an HTTP-date.
182    *
183    * @param value The date/time. May be <jk>null</jk>, in which case <jk>null</jk> is returned.
184    * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>.
185    */
186   public static IfRange of(ZonedDateTime value) {
187      return value == null ? null : new IfRange(value);
188   }
189
190   private static ZonedDateTime parseHttpDate(String value) {
191      return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(value)).truncatedTo(SECONDS);
192   }
193}