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