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>// => "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}