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.classic.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.http.*;
025
026
027/**
028 * Category of headers that consist of multiple parameterized string values.
029 *
030 * <p>
031 * <h5 class='figure'>Example</h5>
032 * <p class='bcode'>
033 *    Accept: application/json;q=0.9,text/xml;q=0.1
034 * </p>
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
038 *    <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a>
039 * </ul>
040 *
041 * @serial exclude
042 */
043@SuppressWarnings({
044   "java:S2160", // equals() inherited from BasicHeader compares name+value; typed field is accessed via getValue()
045   "java:S9149" // Public Juneau DSL factory; hiding parent of()/create() is intentional.
046})
047public class BasicMediaRangesHeader extends BasicStringHeader {
048   private static final long serialVersionUID = 1L;
049
050   /**
051    * Static creator.
052    *
053    * @param name The header name.
054    * @param value
055    *    The header value.
056    *    <br>Can be <jk>null</jk>.
057    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
058    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
059    */
060   public static BasicMediaRangesHeader of(String name, MediaRanges value) {
061      return value == null ? null : new BasicMediaRangesHeader(name, value);
062   }
063
064   /**
065    * Static creator.
066    *
067    * @param name The header name.
068    * @param value
069    *    The header value.
070    *    <br>Must be parsable by {@link MediaRanges#of(String)}.
071    *    <br>Can be <jk>null</jk>.
072    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
073    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
074    */
075   public static BasicMediaRangesHeader of(String name, String value) {
076      return value == null ? null : new BasicMediaRangesHeader(name, value);
077   }
078
079   private final String stringValue;
080   private final transient MediaRanges value;
081   private final transient Supplier<MediaRanges> supplier;
082
083   /**
084    * Constructor.
085    *
086    * @param name The header name.
087    * @param value
088    *    The header value.
089    *    <br>Can be <jk>null</jk> to create a header with no value.
090    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
091    */
092   public BasicMediaRangesHeader(String name, MediaRanges value) {
093      super(name, s(value));
094      this.stringValue = null;
095      this.value = value;
096      this.supplier = null;
097   }
098
099   /**
100    * Constructor.
101    *
102    * @param name The header name.
103    * @param value
104    *    The header value.
105    *    <br>Must be parsable by {@link MediaRanges#of(String)}.
106    *    <br>Can be <jk>null</jk> to create a header with no value.
107    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
108    */
109   public BasicMediaRangesHeader(String name, String value) {
110      super(name, value);
111      stringValue = value;
112      this.value = parse(value);
113      this.supplier = null;
114   }
115
116   /**
117    * Constructor with delayed value.
118    *
119    * <p>
120    * Header value is re-evaluated on each call to {@link #getValue()}.
121    *
122    * @param name The header name.
123    * @param value
124    *    The supplier of the header value.
125    *    <br>Can be <jk>null</jk> to create a header with no value.
126    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
127    */
128   public BasicMediaRangesHeader(String name, Supplier<MediaRanges> value) {
129      super(name, (String)null);
130      this.stringValue = null;
131      this.value = null;
132      supplier = value;
133   }
134
135   /**
136    * Returns the header value as a {@link MediaRanges} wrapped in an {@link Optional}.
137    *
138    * @return The header value as a {@link MediaRanges} wrapped in an {@link Optional}.  Never <jk>null</jk>.
139    */
140   public Optional<MediaRanges> asMediaRanges() {
141      return o(value());
142   }
143
144   /**
145    * Returns the {@link MediaRange} at the specified index.
146    *
147    * @param index The index position of the media range.
148    * @return The {@link MediaRange} at the specified index or <jk>null</jk> if the index is out of range.
149    */
150   public MediaRange getRange(int index) {
151      MediaRanges x = value();
152      return x == null ? null : x.getRange(index);
153   }
154
155   @Override /* Overridden from Header */
156   public String getValue() { return nn(stringValue) ? stringValue : s(value()); }
157
158   /**
159    * Convenience method for searching through all of the subtypes of all the media ranges in this header for the
160    * presence of a subtype fragment.
161    *
162    * <p>
163    * For example, given the header <js>"text/json+activity"</js>, calling
164    * <code>hasSubtypePart(<js>"activity"</js>)</code> returns <jk>true</jk>.
165    *
166    * @param part The media type subtype fragment.
167    * @return <jk>true</jk> if subtype fragment exists.
168    */
169   public boolean hasSubtypePart(String part) {
170      MediaRanges x = value();
171      return x != null && x.hasSubtypePart(part);
172   }
173
174   /**
175    * Given a list of media types, returns the best match for this <c>Accept</c> header.
176    *
177    * <p>
178    * Note that fuzzy matching is allowed on the media types where the <c>Accept</c> header may
179    * contain additional subtype parts.
180    * <br>For example, given identical q-values and an <c>Accept</c> value of <js>"text/json+activity"</js>,
181    * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js>
182    * isn't found.
183    * <br>The purpose for this is to allow serializers to match when artifacts such as <c>id</c> properties are
184    * present in the header.
185    *
186    * <p>
187    * See <a class="doclink" href="https://www.w3.org/TR/activitypub/#retrieving-objects">ActivityPub / Retrieving Objects</a>
188    *
189    * @param mediaTypes The media types to match against.
190    * @return The index into the array of the best match, or <c>-1</c> if no suitable matches could be found.
191    */
192   public int match(List<? extends MediaType> mediaTypes) {
193      MediaRanges x = value();
194      return x == null ? -1 : x.match(mediaTypes);
195   }
196
197   /**
198    * Return the value if present, otherwise return <c>other</c>.
199    *
200    * <p>
201    * This is a shortened form for calling <c>asMediaRanges().orElse(<jv>other</jv>)</c>.
202    *
203    * @param other The value to be returned if there is no value present, can be <jk>null</jk>.
204    * @return The value, if present, otherwise <c>other</c>.
205    */
206   public MediaRanges orElse(MediaRanges other) {
207      MediaRanges x = value();
208      return nn(x) ? x : other;
209   }
210
211   /**
212    * Returns the header value as a {@link MediaRanges}.
213    *
214    * @return The header value as a {@link MediaRanges}, or <jk>null</jk> if the header has no value.
215    */
216   public MediaRanges toMediaRanges() {
217      return value();
218   }
219
220   private static MediaRanges parse(String value) {
221      return value == null ? null : MediaRanges.of(value);
222   }
223
224   @SuppressWarnings({
225      "java:S2177" // Intentional: subclass provides its own private value() with a different return type (MediaRanges vs String)
226   })
227   private MediaRanges value() {
228      if (nn(supplier))
229         return supplier.get();
230      return value;
231   }
232}