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.function.*;
022
023/**
024 * Represents an HTTP <c>Content-Range</c> header.
025 *
026 * <p>
027 * Where in a full body message this partial message belongs.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
031 * </ul>
032 *
033 * @since 9.2.1
034 */
035public class ContentRange extends HttpStringHeader {
036
037   /** The header name */
038   public static final String NAME = "Content-Range";
039
040   /**
041    * Constructor with an eager value.
042    *
043    * @param value The header value. May be <jk>null</jk>.
044    */
045   public ContentRange(String value) {
046      super(NAME, value);
047   }
048
049   /**
050    * Constructor with a lazy value supplier.
051    *
052    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
053    */
054   public ContentRange(Supplier<String> valueSupplier) {
055      super(NAME, valueSupplier);
056   }
057
058   /**
059    * Static factory method with an eager value.
060    *
061    * @param value The header value. May be <jk>null</jk>.
062    * @return A new instance. Never <jk>null</jk>.
063    */
064   public static ContentRange of(String value) {
065      return new ContentRange(value);
066   }
067
068   /**
069    * Static factory method with a lazy value supplier.
070    *
071    * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>.
072    * @return A new instance. Never <jk>null</jk>.
073    */
074   public static ContentRange of(Supplier<String> valueSupplier) {
075      return new ContentRange(valueSupplier);
076   }
077
078   /**
079    * Creates a new {@link Builder} defaulting to the <c>bytes</c> range unit.
080    *
081    * @return A new builder. Never <jk>null</jk>.
082    */
083   public static Builder create() {
084      return new Builder();
085   }
086
087   //------------------------------------------------------------------------------------------------------------------
088   // Inner types
089   //------------------------------------------------------------------------------------------------------------------
090
091   /**
092    * Fluent builder for assembling an HTTP <c>Content-Range</c> header value.
093    *
094    * <p>
095    * Composes the three fields defined by
096    * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233#section-4.2'>RFC 7233 §4.2</a> — the range unit
097    * (default <c>bytes</c>), the resolved <c>first-last</c> byte range (or <c>*</c> for an unsatisfied range), and the
098    * <c>complete-length</c> (or <c>*</c> when unknown) — into forms such as <c>bytes 0-1023/2048</c>,
099    * <c>bytes 0-1023/&#42;</c>, and <c>bytes &#42;/2048</c>.
100    *
101    * <h5 class='section'>Example:</h5>
102    * <p class='bjava'>
103    *    String <jv>value</jv> = ContentRange.<jsm>create</jsm>()
104    *       .range(0, 1023)
105    *       .length(2048)
106    *       .build();
107    *    <jc>// =&gt; "bytes 0-1023/2048"</jc>
108    * </p>
109    *
110    * <h5 class='section'>See Also:</h5><ul>
111    *    <li class='jc'>{@link ContentRange}
112    *    <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233'>RFC 7233 - HTTP/1.1 Range Requests</a>
113    * </ul>
114    *
115    * @since 10.0.0
116    */
117   public static class Builder {
118
119      private String unit = "bytes";
120      private Long start;
121      private Long end;
122      private boolean rangeUnsatisfied;
123      private Long completeLength;
124      private boolean lengthUnknown;
125
126      /**
127       * Sets the range unit (default <c>bytes</c>).
128       *
129       * @param value The range unit. Must not be <jk>null</jk> or blank.
130       * @return This object.
131       * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank.
132       */
133      public Builder unit(String value) {
134         reqnn("value", value);
135         var v = value.trim();
136         if (v.isEmpty())
137            throw iaex("range unit must not be blank");
138         unit = v;
139         return this;
140      }
141
142      /**
143       * Sets the resolved <c>first-last</c> byte range.
144       *
145       * <p>
146       * Clears any previously-set {@link #unsatisfiedRange() unsatisfied} marker.
147       *
148       * @param startValue The first byte position (inclusive). Must be non-negative.
149       * @param endValue The last byte position (inclusive). Must be greater than or equal to {@code startValue}.
150       * @return This object.
151       * @throws IllegalArgumentException If the positions are negative or out of order.
152       */
153      public Builder range(long startValue, long endValue) {
154         if (startValue < 0)
155            throw iaex("range start must be non-negative: %s", startValue);
156         if (endValue < startValue)
157            throw iaex("range end %s must be >= start %s", endValue, startValue);
158         start = startValue;
159         end = endValue;
160         rangeUnsatisfied = false;
161         return this;
162      }
163
164      /**
165       * Marks the range portion as unsatisfied, rendering it as <c>*</c> (used with a known complete length).
166       *
167       * @return This object.
168       */
169      public Builder unsatisfiedRange() {
170         rangeUnsatisfied = true;
171         start = null;
172         end = null;
173         return this;
174      }
175
176      /**
177       * Sets the <c>complete-length</c> (the total size of the representation).
178       *
179       * <p>
180       * Clears any previously-set {@link #unknownLength() unknown-length} marker.
181       *
182       * @param value The complete length in units. Must be non-negative.
183       * @return This object.
184       * @throws IllegalArgumentException If {@code value} is negative.
185       */
186      public Builder length(long value) {
187         if (value < 0)
188            throw iaex("complete-length must be non-negative: %s", value);
189         completeLength = value;
190         lengthUnknown = false;
191         return this;
192      }
193
194      /**
195       * Marks the complete length as unknown, rendering it as <c>*</c>.
196       *
197       * @return This object.
198       */
199      public Builder unknownLength() {
200         lengthUnknown = true;
201         completeLength = null;
202         return this;
203      }
204
205      /**
206       * Builds the rendered <c>Content-Range</c> header value.
207       *
208       * @return The header value. Never <jk>null</jk>.
209       */
210      public String build() {
211         var rangePart = rangeUnsatisfied || start == null ? "*" : start + "-" + end;
212         var lengthPart = lengthUnknown || completeLength == null ? "*" : completeLength.toString();
213         return unit + " " + rangePart + "/" + lengthPart;
214      }
215
216      /**
217       * Builds a {@link ContentRange} header bean directly from this builder.
218       *
219       * @return A {@link ContentRange} carrying {@link #build()}. Never <jk>null</jk>.
220       */
221      public ContentRange toHeader() {
222         return ContentRange.of(build());
223      }
224
225      @Override
226      public String toString() {
227         return build();
228      }
229   }
230}