001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.http.header;
014
015import static org.apache.juneau.common.internal.StringUtils.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017
018import java.util.*;
019import java.util.function.*;
020
021import org.apache.juneau.*;
022
023/**
024 * Category of headers that consist of multiple parameterized string values.
025 *
026 * <p>
027 * <h5 class='figure'>Example</h5>
028 * <p class='bcode'>
029 *    Accept: application/json;q=0.9,text/xml;q=0.1
030 * </p>
031 *
032 * <h5 class='section'>See Also:</h5><ul>
033 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</a>
034 *    <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a>
035 * </ul>
036 *
037 * @serial exclude
038 */
039public class BasicMediaRangesHeader extends BasicStringHeader {
040
041   //-----------------------------------------------------------------------------------------------------------------
042   // Static
043   //-----------------------------------------------------------------------------------------------------------------
044
045   private static final long serialVersionUID = 1L;
046
047   /**
048    * Static creator.
049    *
050    * @param name The header name.
051    * @param value
052    *    The header value.
053    *    <br>Must be parsable by {@link MediaRanges#of(String)}.
054    *    <br>Can be <jk>null</jk>.
055    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
056    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
057    */
058   public static BasicMediaRangesHeader of(String name, String value) {
059      return value == null ? null : new BasicMediaRangesHeader(name, value);
060   }
061
062   /**
063    * Static creator.
064    *
065    * @param name The header name.
066    * @param value
067    *    The header value.
068    *    <br>Can be <jk>null</jk>.
069    * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>.
070    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
071    */
072   public static BasicMediaRangesHeader of(String name, MediaRanges value) {
073      return value == null ? null : new BasicMediaRangesHeader(name, value);
074   }
075
076   //-----------------------------------------------------------------------------------------------------------------
077   // Instance
078   //-----------------------------------------------------------------------------------------------------------------
079
080   private final String stringValue;
081   private final MediaRanges value;
082   private final Supplier<MediaRanges> supplier;
083
084   /**
085    * Constructor.
086    *
087    * @param name The header name.
088    * @param value
089    *    The header value.
090    *    <br>Must be parsable by {@link MediaRanges#of(String)}.
091    *    <br>Can be <jk>null</jk>.
092    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
093    */
094   public BasicMediaRangesHeader(String name, String value) {
095      super(name, value);
096      this.stringValue = value;
097      this.value = parse(value);
098      this.supplier = null;
099   }
100
101   /**
102    * Constructor.
103    *
104    * @param name The header name.
105    * @param value
106    *    The header value.
107    *    <br>Can be <jk>null</jk>.
108    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
109    */
110   public BasicMediaRangesHeader(String name, MediaRanges value) {
111      super(name, stringify(value));
112      this.stringValue = null;
113      this.value = value;
114      this.supplier = null;
115   }
116
117   /**
118    * Constructor with delayed value.
119    *
120    * <p>
121    * Header value is re-evaluated on each call to {@link #getValue()}.
122    *
123    * @param name The header name.
124    * @param value
125    *    The supplier of the header value.
126    *    <br>Can be <jk>null</jk>.
127    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
128    */
129   public BasicMediaRangesHeader(String name, Supplier<MediaRanges> value) {
130      super(name, (String)null);
131      this.stringValue = null;
132      this.value = null;
133      this.supplier = value;
134   }
135
136   /**
137    * Returns the header value as a {@link MediaRanges} wrapped in an {@link Optional}.
138    *
139    * @return The header value as a {@link MediaRanges} wrapped in an {@link Optional}.  Never <jk>null</jk>.
140    */
141   public Optional<MediaRanges> asMediaRanges() {
142      return optional(value());
143   }
144
145   /**
146    * Returns the header value as a {@link MediaRanges}.
147    *
148    * @return The header value as a {@link MediaRanges}.  Can be <jk>null</jk>.
149    */
150   public MediaRanges toMediaRanges() {
151      return value();
152   }
153
154   /**
155    * Given a list of media types, returns the best match for this <c>Accept</c> header.
156    *
157    * <p>
158    * Note that fuzzy matching is allowed on the media types where the <c>Accept</c> header may
159    * contain additional subtype parts.
160    * <br>For example, given identical q-values and an <c>Accept</c> value of <js>"text/json+activity"</js>,
161    * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js>
162    * isn't found.
163    * <br>The purpose for this is to allow serializers to match when artifacts such as <c>id</c> properties are
164    * present in the header.
165    *
166    * <p>
167    * See <a class="doclink" href="https://www.w3.org/TR/activitypub/#retrieving-objects">ActivityPub / Retrieving Objects</a>
168    *
169    * @param mediaTypes The media types to match against.
170    * @return The index into the array of the best match, or <c>-1</c> if no suitable matches could be found.
171    */
172   public int match(List<? extends MediaType> mediaTypes) {
173      MediaRanges x = value();
174      return x == null ? -1 : x.match(mediaTypes);
175   }
176
177   /**
178    * Returns the {@link MediaRange} at the specified index.
179    *
180    * @param index The index position of the media range.
181    * @return The {@link MediaRange} at the specified index or <jk>null</jk> if the index is out of range.
182    */
183   public MediaRange getRange(int index) {
184      MediaRanges x = value();
185      return x == null ? null : x.getRange(index);
186   }
187
188   /**
189    * Convenience method for searching through all of the subtypes of all the media ranges in this header for the
190    * presence of a subtype fragment.
191    *
192    * <p>
193    * For example, given the header <js>"text/json+activity"</js>, calling
194    * <code>hasSubtypePart(<js>"activity"</js>)</code> returns <jk>true</jk>.
195    *
196    * @param part The media type subtype fragment.
197    * @return <jk>true</jk> if subtype fragment exists.
198    */
199   public boolean hasSubtypePart(String part) {
200      MediaRanges x = value();
201      return x == null ? false : x.hasSubtypePart(part);
202   }
203
204   @Override /* Header */
205   public String getValue() {
206      return stringValue != null ? stringValue : stringify(value());
207   }
208
209   /**
210    * Return the value if present, otherwise return <c>other</c>.
211    *
212    * <p>
213    * This is a shortened form for calling <c>asMediaRanges().orElse(<jv>other</jv>)</c>.
214    *
215    * @param other The value to be returned if there is no value present, can be <jk>null</jk>.
216    * @return The value, if present, otherwise <c>other</c>.
217    */
218   public MediaRanges orElse(MediaRanges other) {
219      MediaRanges x = value();
220      return x != null ? x : other;
221   }
222
223   private MediaRanges parse(String value) {
224      return value == null ? null : MediaRanges.of(value);
225   }
226
227   private MediaRanges value() {
228      if (supplier != null)
229         return supplier.get();
230      return value;
231   }
232}