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.internal.StringUtils.*;
016
017import java.util.*;
018import java.util.function.*;
019
020import org.apache.http.*;
021import org.apache.juneau.http.*;
022import org.apache.juneau.json.*;
023
024/**
025 * Category of headers that consist of a single parameterized string value.
026 *
027 * <p>
028 * <h5 class='figure'>Example</h5>
029 * <p class='bcode w800'>
030 *    Content-Type: application/json;charset=utf-8
031 * </p>
032 *
033 * <ul class='seealso'>
034 *    <li class='extlink'>{@doc ExtRFC2616}
035 * </ul>
036*/
037public class BasicMediaTypeHeader extends BasicStringHeader {
038
039   private static final long serialVersionUID = 1L;
040
041   /**
042    * Convenience creator.
043    *
044    * @param name The header name.
045    * @param value
046    *    The header value.
047    *    <br>Can be any of the following:
048    *    <ul>
049    *       <li>{@link String}
050    *       <li>Anything else - Converted to <c>String</c> then parsed.
051    *    </ul>
052    * @return A new {@link BasicMediaTypeHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>.
053    */
054   public static BasicMediaTypeHeader of(String name, Object value) {
055      if (isEmpty(name) || value == null)
056         return null;
057      return new BasicMediaTypeHeader(name, value);
058   }
059
060   /**
061    * Convenience creator using supplier.
062    *
063    * <p>
064    * Header value is re-evaluated on each call to {@link #getValue()}.
065    *
066    * @param name The header name.
067    * @param value
068    *    The header value supplier.
069    *    <br>Can be any of the following:
070    *    <ul>
071    *       <li>{@link String}
072    *       <li>Anything else - Converted to <c>String</c> then parsed.
073    *    </ul>
074    * @return A new {@link BasicMediaTypeHeader} object, or <jk>null</jk> if the name or value is <jk>null</jk>.
075    */
076   public static BasicMediaTypeHeader of(String name, Supplier<?> value) {
077      if (isEmpty(name) || value == null)
078         return null;
079      return new BasicMediaTypeHeader(name, value);
080   }
081
082   private MediaType parsed;
083
084   /**
085    * Constructor
086    *
087    * @param name The header name.
088    * @param value
089    *    <br>Can be any of the following:
090    *    <ul>
091    *       <li>{@link String}
092    *       <li>Anything else - Converted to <c>String</c> then parsed.
093    *       <li>A {@link Supplier} of anything on this list.
094    *    </ul>
095    */
096   public BasicMediaTypeHeader(String name, Object value) {
097      super(name, value);
098      if (! isSupplier(value))
099         parsed = parse();
100   }
101
102   /**
103    * Returns this header as a {@link MediaType} object.
104    *
105    * @return This header as a {@link MediaType} object.
106    */
107   public MediaType asMediaType() {
108      return parse();
109   }
110
111   /**
112    * Given a list of media types, returns the best match for this <c>Content-Type</c> header.
113    *
114    * <p>
115    * Note that fuzzy matching is allowed on the media types where the <c>Content-Types</c> header may
116    * contain additional subtype parts.
117    * <br>For example, given a <c>Content-Type</c> value of <js>"text/json+activity"</js>,
118    * the media type <js>"text/json"</js> will match if <js>"text/json+activity"</js> or <js>"text/activity+json"</js>
119    * isn't found.
120    * <br>The purpose for this is to allow parsers to match when artifacts such as <c>id</c> properties are
121    * present in the header.
122    *
123    * @param mediaTypes The media types to match against.
124    * @return The index into the array of the best match, or <c>-1</c> if no suitable matches could be found.
125    */
126   public int match(List<MediaType> mediaTypes) {
127      int matchQuant = 0, matchIndex = -1;
128
129      for (int i = 0; i < mediaTypes.size(); i++) {
130         MediaType mt = mediaTypes.get(i);
131         int matchQuant2 = mt.match(asMediaType(), true);
132         if (matchQuant2 > matchQuant) {
133            matchQuant = matchQuant2;
134            matchIndex = i;
135         }
136      }
137      return matchIndex;
138   }
139
140   /**
141    * Returns the <js>'type'</js> fragment of the <js>'type/subType'</js> string.
142    *
143    * @return The media type.
144    */
145   public final String getType() {
146      return asMediaType().getType();
147   }
148
149   /**
150    * Returns the <js>'subType'</js> fragment of the <js>'type/subType'</js> string.
151    *
152    * @return The media subtype.
153    */
154   public final String getSubType() {
155      return asMediaType().getSubType();
156   }
157
158   /**
159    * Returns <jk>true</jk> if the subtype contains the specified <js>'+'</js> delimited subtype value.
160    *
161    * @param st
162    *    The subtype string.
163    *    Case is ignored.
164    * @return <jk>true</jk> if the subtype contains the specified subtype string.
165    */
166   public final boolean hasSubType(String st) {
167      return asMediaType().hasSubType(st);
168   }
169
170   /**
171    * Returns the subtypes broken down by fragments delimited by <js>"'"</js>.
172    *
173    * <P>
174    * For example, the media type <js>"text/foo+bar"</js> will return a list of
175    * <code>[<js>'foo'</js>,<js>'bar'</js>]</code>
176    *
177    * @return An unmodifiable list of subtype fragments.  Never <jk>null</jk>.
178    */
179   public final List<String> getSubTypes() {
180      return asMediaType().getSubTypes();
181   }
182
183   /**
184    * Returns <jk>true</jk> if this media type contains the <js>'*'</js> meta character.
185    *
186    * @return <jk>true</jk> if this media type contains the <js>'*'</js> meta character.
187    */
188   public final boolean isMetaSubtype() {
189      return asMediaType().isMetaSubtype();
190   }
191
192   /**
193    * Returns a match metric against the specified media type where a larger number represents a better match.
194    *
195    * <p>
196    * This media type can contain <js>'*'</js> metacharacters.
197    * <br>The comparison media type must not.
198    *
199    * <ul>
200    *    <li>Exact matches (e.g. <js>"text/json"</js>/</js>"text/json"</js>) should match
201    *       better than meta-character matches (e.g. <js>"text/*"</js>/</js>"text/json"</js>)
202    *    <li>The comparison media type can have additional subtype tokens (e.g. <js>"text/json+foo"</js>)
203    *       that will not prevent a match if the <c>allowExtraSubTypes</c> flag is set.
204    *       The reverse is not true, e.g. the comparison media type must contain all subtype tokens found in the
205    *       comparing media type.
206    *       <ul>
207    *          <li>We want the {@link JsonSerializer} (<js>"text/json"</js>) class to be able to handle requests for <js>"text/json+foo"</js>.
208    *          <li>We want to make sure {@link org.apache.juneau.json.SimpleJsonSerializer} (<js>"text/json+simple"</js>) does not handle
209    *             requests for <js>"text/json"</js>.
210    *       </ul>
211    *       More token matches should result in a higher match number.
212    * </ul>
213    *
214    * The formula is as follows for <c>type/subTypes</c>:
215    * <ul>
216    *    <li>An exact match is <c>100,000</c>.
217    *    <li>Add the following for type (assuming subtype match is &lt;0):
218    *    <ul>
219    *       <li><c>10,000</c> for an exact match (e.g. <js>"text"</js>==<js>"text"</js>).
220    *       <li><c>5,000</c> for a meta match (e.g. <js>"*"</js>==<js>"text"</js>).
221    *    </ul>
222    *    <li>Add the following for subtype (assuming type match is &lt;0):
223    *    <ul>
224    *       <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>)
225    *       <li><c>100</c> for every subtype entry match (e.g. <js>"json"</js>/<js>"json+foo"</js>)
226    *    </ul>
227    * </ul>
228    *
229    * @param o The media type to compare with.
230    * @param allowExtraSubTypes If <jk>true</jk>,
231    * @return <jk>true</jk> if the media types match.
232    */
233   public final int match(MediaType o, boolean allowExtraSubTypes) {
234      return asMediaType().match(o, allowExtraSubTypes);
235   }
236
237   /**
238    * Returns the additional parameters on this media type.
239    *
240    * <p>
241    * For example, given the media type string <js>"text/html;level=1"</js>, will return a map
242    * with the single entry <code>{level:[<js>'1'</js>]}</code>.
243    *
244    * @return The map of additional parameters, or an empty map if there are no parameters.
245    */
246   public List<NameValuePair> getParameters() {
247      return asMediaType().getParameters();
248   }
249
250   /**
251    * Returns a parameterized value of the header.
252    *
253    * <p class='bcode w800'>
254    *    ContentType ct = ContentType.<jsm>of</jsm>(<js>"application/json;charset=foo"</js>);
255    *    assertEquals(<js>"foo"</js>, ct.getParameter(<js>"charset"</js>);
256    * </p>
257    *
258    * @param name The header name.
259    * @return The header value, or <jk>null</jk> if the parameter is not present.
260    */
261   public String getParameter(String name) {
262      return asMediaType().getParameter(name);
263   }
264
265   @Override /* Header */
266   public String getValue() {
267      Object o = getRawValue();
268      if (o == null)
269         return null;
270      return stringify(asMediaType());
271   }
272
273   private MediaType parse() {
274      if (parsed != null)
275         return parsed;
276      Object o = getRawValue();
277      if (o == null)
278         o = "";
279      if (o instanceof MediaType)
280         return (MediaType)o;
281      return new MediaType(o.toString());
282   }
283}