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.http.*;
022import org.apache.juneau.*;
023import org.apache.juneau.json.*;
024
025/**
026 * Category of headers that consist of a single parameterized string value.
027 *
028 * <p>
029 * <h5 class='figure'>Example</h5>
030 * <p class='bcode'>
031 *    Content-Type: application/json;charset=utf-8
032 * </p>
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-common">juneau-rest-common</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 BasicMediaTypeHeader 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 MediaType#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 BasicMediaTypeHeader of(String name, String value) {
061      return value == null ? null : new BasicMediaTypeHeader(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 BasicMediaTypeHeader of(String name, MediaType value) {
075      return value == null ? null : new BasicMediaTypeHeader(name, value);
076   }
077
078   //-----------------------------------------------------------------------------------------------------------------
079   // Instance
080   //-----------------------------------------------------------------------------------------------------------------
081
082   private final MediaType value;
083   private final Supplier<MediaType> supplier;
084
085   /**
086    * Constructor.
087    *
088    * @param name The header name.
089    * @param value
090    *    The header value.
091    *    <br>Must be parsable by {@link MediaType#of(String)}.
092    *    <br>Can be <jk>null</jk>.
093    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
094    */
095   public BasicMediaTypeHeader(String name, String value) {
096      super(name, 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 BasicMediaTypeHeader(String name, MediaType value) {
111      super(name, stringify(value));
112      this.value = 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>.
126    * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
127    */
128   public BasicMediaTypeHeader(String name, Supplier<MediaType> value) {
129      super(name, (String)null);
130      this.value = null;
131      this.supplier = value;
132   }
133
134   @Override /* Header */
135   public String getValue() {
136      return stringify(value());
137   }
138
139   /**
140    * Returns the header value as a {@link MediaType} wrapped in an {@link Optional}.
141    *
142    * @return The header value as a {@link MediaType} wrapped in an {@link Optional}.  Never <jk>null</jk>.
143    */
144   public Optional<MediaType> asMediaType() {
145      return optional(value());
146   }
147
148   /**
149    * Returns the header value as a {@link MediaType}.
150    *
151    * @return The header value as a {@link MediaType}.  Can be <jk>null</jk>.
152    */
153   public MediaType toMediaType() {
154      return value();
155   }
156
157   /**
158    * Given a list of media types, returns the best match for this <c>Content-Type</c> header.
159    *
160    * <p>
161    * Note that fuzzy matching is allowed on the media types where the <c>Content-Types</c> header may
162    * contain additional subtype parts.
163    * <br>For example, given a <c>Content-Type</c> value of <js>"text/json+activity"</js>,
164    * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js>
165    * isn't found.
166    * <br>The purpose for this is to allow parsers to match when artifacts such as <c>id</c> properties are
167    * present in the header.
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<MediaType> mediaTypes) {
173      int matchQuant = 0, matchIndex = -1;
174
175      for (int i = 0; i < mediaTypes.size(); i++) {
176         MediaType mt = mediaTypes.get(i);
177         int matchQuant2 = mt.match(orElse(MediaType.EMPTY), true);
178         if (matchQuant2 > matchQuant) {
179            matchQuant = matchQuant2;
180            matchIndex = i;
181         }
182      }
183      return matchIndex;
184   }
185
186   /**
187    * Returns the <js>'type'</js> fragment of the <js>'type/subType'</js> string.
188    *
189    * @return The media type.
190    */
191   public final String getType() {
192      return orElse(MediaType.EMPTY).getType();
193   }
194
195   /**
196    * Returns the <js>'subType'</js> fragment of the <js>'type/subType'</js> string.
197    *
198    * @return The media subtype.
199    */
200   public final String getSubType() {
201      return orElse(MediaType.EMPTY).getSubType();
202   }
203
204   /**
205    * Returns <jk>true</jk> if the subtype contains the specified <js>'+'</js> delimited subtype value.
206    *
207    * @param value
208    *    The subtype string.
209    *    Case is ignored.
210    * @return <jk>true</jk> if the subtype contains the specified subtype string.
211    */
212   public final boolean hasSubType(String value) {
213      return orElse(MediaType.EMPTY).hasSubType(value);
214   }
215
216   /**
217    * Returns the subtypes broken down by fragments delimited by <js>"'"</js>.
218    *
219    * <P>
220    * For example, the media type <js>"text/foo+bar"</js> will return a list of
221    * <code>[<js>'foo'</js>,<js>'bar'</js>]</code>
222    *
223    * @return An unmodifiable list of subtype fragments.  Can be <jk>null</jk>.
224    */
225   public final List<String> getSubTypes() {
226      return orElse(MediaType.EMPTY).getSubTypes();
227   }
228
229   /**
230    * Returns <jk>true</jk> if this media type contains the <js>'*'</js> meta character.
231    *
232    * @return <jk>true</jk> if this media type contains the <js>'*'</js> meta character.
233    */
234   public final boolean isMetaSubtype() {
235      return orElse(MediaType.EMPTY).isMetaSubtype();
236   }
237
238   /**
239    * Returns a match metric against the specified media type where a larger number represents a better match.
240    *
241    * <p>
242    * This media type can contain <js>'*'</js> metacharacters.
243    * <br>The comparison media type must not.
244    *
245    * <ul>
246    *    <li>Exact matches (e.g. <js>"text/json"</js>/</js>"text/json"</js>) should match
247    *       better than meta-character matches (e.g. <js>"text/*"</js>/</js>"text/json"</js>)
248    *    <li>The comparison media type can have additional subtype tokens (e.g. <js>"text/json+foo"</js>)
249    *       that will not prevent a match if the <c>allowExtraSubTypes</c> flag is set.
250    *       The reverse is not true, e.g. the comparison media type must contain all subtype tokens found in the
251    *       comparing media type.
252    *       <ul>
253    *          <li>We want the {@link JsonSerializer} (<js>"text/json"</js>) class to be able to handle requests for <js>"text/json+foo"</js>.
254    *          <li>We want to make sure {@link org.apache.juneau.json.Json5Serializer} (<js>"text/json5"</js>) does not handle
255    *             requests for <js>"text/json"</js>.
256    *       </ul>
257    *       More token matches should result in a higher match number.
258    * </ul>
259    *
260    * The formula is as follows for <c>type/subTypes</c>:
261    * <ul>
262    *    <li>An exact match is <c>100,000</c>.
263    *    <li>Add the following for type (assuming subtype match is &lt;0):
264    *    <ul>
265    *       <li><c>10,000</c> for an exact match (e.g. <js>"text"</js>==<js>"text"</js>).
266    *       <li><c>5,000</c> for a meta match (e.g. <js>"*"</js>==<js>"text"</js>).
267    *    </ul>
268    *    <li>Add the following for subtype (assuming type match is &lt;0):
269    *    <ul>
270    *       <li><c>7,500</c> for an exact match (e.g. <js>"json+foo"</js>==<js>"json+foo"</js> or <js>"json+foo"</js>==<js>"foo+json"</js>)
271    *       <li><c>100</c> for every subtype entry match (e.g. <js>"json"</js>/<js>"json+foo"</js>)
272    *    </ul>
273    * </ul>
274    *
275    * @param o The media type to compare with.
276    * @param allowExtraSubTypes If <jk>true</jk>,
277    * @return <jk>true</jk> if the media types match.
278    */
279   public final int match(MediaType o, boolean allowExtraSubTypes) {
280      return orElse(MediaType.EMPTY).match(o, allowExtraSubTypes);
281   }
282
283   /**
284    * Returns the additional parameters on this media type.
285    *
286    * <p>
287    * For example, given the media type string <js>"text/html;level=1"</js>, will return a map
288    * with the single entry <code>{level:[<js>'1'</js>]}</code>.
289    *
290    * @return The map of additional parameters, or an empty map if there are no parameters.
291    */
292   public List<NameValuePair> getParameters() {
293      return orElse(MediaType.EMPTY).getParameters();
294   }
295
296   /**
297    * Returns a parameterized value of the header.
298    *
299    * <p class='bjava'>
300    *    ContentType <jv>contentType</jv> = ContentType.<jsm>of</jsm>(<js>"application/json;charset=foo"</js>);
301    *    <jsm>assertEquals</jsm>(<js>"foo"</js>, <jv>contentType</jv>.getParameter(<js>"charset"</js>);
302    * </p>
303    *
304    * @param name The header name.
305    * @return The header value, or <jk>null</jk> if the parameter is not present.
306    */
307   public String getParameter(String name) {
308      return orElse(MediaType.EMPTY).getParameter(name);
309   }
310
311   /**
312    * Return the value if present, otherwise return <c>other</c>.
313    *
314    * <p>
315    * This is a shortened form for calling <c>asMediaType().orElse(<jv>other</jv>)</c>.
316    *
317    * @param other The value to be returned if there is no value present, can be <jk>null</jk>.
318    * @return The value, if present, otherwise <c>other</c>.
319    */
320   public MediaType orElse(MediaType other) {
321      MediaType x = value();
322      return x != null ? x : other;
323   }
324
325   private MediaType parse(String value) {
326      // If this happens to be a multi-value, use the last value.
327      if (value != null) {
328         int i = value.indexOf(',');
329         if (i != -1)
330            value = value.substring(i+1);
331      }
332      return MediaType.of(value);
333   }
334
335   private MediaType value() {
336      if (supplier != null)
337         return supplier.get();
338      return value;
339   }
340}