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