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.http.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024/**
025 * Represents an HTTP <c>Range</c> header.
026 *
027 * <p>
028 * Request only part of an entity. Bytes are numbered from 0.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
032 * </ul>
033 *
034 * @since 9.2.1
035 */
036public class Range extends HttpStringHeader {
037
038   /** The header name */
039   public static final String NAME = "Range";
040
041   /**
042    * Constructor with an eager value.
043    *
044    * @param value The header value. May be <jk>null</jk>.
045    */
046   public Range(String value) {
047      super(NAME, value);
048   }
049
050   /**
051    * Constructor with a lazy value supplier.
052    *
053    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
054    */
055   public Range(Supplier<String> valueSupplier) {
056      super(NAME, valueSupplier);
057   }
058
059   /**
060    * Static factory method with an eager value.
061    *
062    * @param value The header value. May be <jk>null</jk>.
063    * @return A new instance. Never <jk>null</jk>.
064    */
065   public static Range of(String value) {
066      return new Range(value);
067   }
068
069   /**
070    * Static factory method with a lazy value supplier.
071    *
072    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
073    * @return A new instance. Never <jk>null</jk>.
074    */
075   public static Range of(Supplier<String> valueSupplier) {
076      return new Range(valueSupplier);
077   }
078
079   /**
080    * Creates a new {@link Builder} defaulting to the <c>bytes</c> range unit.
081    *
082    * @return A new builder. Never <jk>null</jk>.
083    */
084   public static Builder create() {
085      return new Builder();
086   }
087
088   //------------------------------------------------------------------------------------------------------------------
089   // Inner types
090   //------------------------------------------------------------------------------------------------------------------
091
092   /**
093    * Fluent builder for assembling an HTTP <c>Range</c> header value.
094    *
095    * <p>
096    * Composes the range unit (default <c>bytes</c>) and one or more range specs defined by
097    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233#section-3.1'>RFC 7233 §3.1</a> into the
098    * <c>unit=spec,spec,...</c> wire format.  Typed setters cover the three byte-range-spec forms — a closed range
099    * (<c>0-499</c>) via {@link #range(long, long)}, an open-ended range (<c>500-</c>) via {@link #from(long)}, and a
100    * suffix range (<c>-500</c>) via {@link #suffix(long)} — and {@link #spec(String)} is a generic escape hatch.
101    *
102    * <h5 class='section'>Example:</h5>
103    * <p class='bjava'>
104    *    String <jv>value</jv> = Range.<jsm>create</jsm>()
105    *       .range(0, 499)
106    *       .from(9500)
107    *       .build();
108    *    <jc>// =&gt; "bytes=0-499,9500-"</jc>
109    * </p>
110    *
111    * <h5 class='section'>See Also:</h5><ul>
112    *    <li class='jc'>{@link Range}
113    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233'>RFC 7233 - HTTP/1.1 Range Requests</a>
114    * </ul>
115    *
116    * @since 10.0.0
117    */
118   public static class Builder {
119
120      private String unit = "bytes";
121      private final List<String> specs = l();
122
123      /**
124       * Sets the range unit (default <c>bytes</c>).
125       *
126       * @param value The range unit. Must not be <jk>null</jk> or blank.
127       * @return This object.
128       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
129       */
130      public Builder unit(String value) {
131         reqnn("value", value);
132         var v = value.trim();
133         if (v.isEmpty())
134            throw iaex("range unit must not be blank");
135         unit = v;
136         return this;
137      }
138
139      /**
140       * Adds a closed byte-range spec of the form <c>start-end</c>.
141       *
142       * @param startValue The first byte position (inclusive). Must be non-negative.
143       * @param endValue The last byte position (inclusive). Must be greater than or equal to {@code startValue}.
144       * @return This object.
145       * @throws IllegalArgumentException If the positions are negative or out of order.
146       */
147      public Builder range(long startValue, long endValue) {
148         if (startValue < 0)
149            throw iaex("range start must be non-negative: %s", startValue);
150         if (endValue < startValue)
151            throw iaex("range end %s must be >= start %s", endValue, startValue);
152         return spec(startValue + "-" + endValue);
153      }
154
155      /**
156       * Adds an open-ended byte-range spec of the form <c>start-</c> (from the position to the end).
157       *
158       * @param startValue The first byte position (inclusive). Must be non-negative.
159       * @return This object.
160       * @throws IllegalArgumentException If {@code startValue} is negative.
161       */
162      public Builder from(long startValue) {
163         if (startValue < 0)
164            throw iaex("range start must be non-negative: %s", startValue);
165         return spec(startValue + "-");
166      }
167
168      /**
169       * Adds a suffix byte-range spec of the form <c>-length</c> (the final {@code length} bytes).
170       *
171       * @param length The number of trailing bytes. Must be non-negative.
172       * @return This object.
173       * @throws IllegalArgumentException If {@code length} is negative.
174       */
175      public Builder suffix(long length) {
176         if (length < 0)
177            throw iaex("suffix length must be non-negative: %s", length);
178         return spec("-" + length);
179      }
180
181      /**
182       * Generic escape hatch for adding any range spec verbatim.
183       *
184       * @param value The range spec (e.g. <js>"0-499"</js>). Must not be <jk>null</jk> or blank.
185       * @return This object.
186       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
187       */
188      public Builder spec(String value) {
189         reqnn("value", value);
190         var v = value.trim();
191         if (v.isEmpty())
192            throw iaex("range spec must not be blank");
193         specs.add(v);
194         return this;
195      }
196
197      /**
198       * Builds the rendered <c>Range</c> header value.
199       *
200       * <p>
201       * Returns an empty string when no range specs have been registered.
202       *
203       * @return The header value. Never <jk>null</jk>.
204       */
205      public String build() {
206         if (specs.isEmpty())
207            return "";
208         return unit + "=" + String.join(",", specs);
209      }
210
211      /**
212       * Builds a {@link Range} header bean directly from this builder.
213       *
214       * @return A {@link Range} carrying {@link #build()}. Never <jk>null</jk>.
215       */
216      public Range toHeader() {
217         return Range.of(build());
218      }
219
220      @Override
221      public String toString() {
222         return build();
223      }
224   }
225}