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.commons.http;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.util.*;
023import java.util.function.*;
024
025/**
026 * Describes a single type used in content negotiation between an HTTP client and server, as described in
027 * Section 14.1 and 14.7 of RFC2616 (the HTTP/1.1 specification).
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
031 *    <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a>
032 * </ul>
033 */
034public class MediaRange extends MediaType {
035
036   private final NameValuePair[] extensions;
037   private final Float qValue;
038   private final String string;
039
040   /**
041    * Constructor.
042    *
043    * @param e The parsed media range element.
044    */
045   public MediaRange(HeaderElement e) {
046      super(e);
047
048      Float qValue2 = 1f;
049
050      // The media type consists of everything up to the q parameter.
051      // The q parameter and stuff after is part of the range.
052      List<NameValuePair> extensions2 = list();
053      boolean foundQ = false;
054      for (var p : e.getParameters()) {
055         if (eq(p.getName(), "q")) {
056            qValue2 = Float.parseFloat(p.getValue());
057            foundQ = true;
058         } else if (foundQ) {
059            extensions2.add(new BasicNameValuePair(p.getName(), p.getValue()));
060         }
061      }
062
063      this.qValue = qValue2;
064      this.extensions = extensions2.toArray(NameValuePair.EMPTY_ARRAY);
065
066      var sb = new StringBuilder().append(super.toString());
067
068      // '1' is equivalent to specifying no qValue. If there's no extensions, then we won't include a qValue.
069      if (qValue2.floatValue() == 1.0) {
070         if (this.extensions.length > 0) {
071            sb.append(";q=").append(qValue);
072            Arrays.stream(extensions).forEach(x -> sb.append(';').append(x.getName()).append('=').append(x.getValue()));
073         }
074      } else {
075         sb.append(";q=").append(qValue);
076         Arrays.stream(extensions).forEach(x -> sb.append(';').append(x.getName()).append('=').append(x.getValue()));
077      }
078      string = sb.toString();
079   }
080
081   /**
082    * Performs an action on the optional set of custom extensions defined for this type.
083    *
084    * <p>
085    * Values are lowercase and never <jk>null</jk>.
086    *
087    * @param action The action to perform.
088    * @return This object.
089    */
090   public MediaRange forEachExtension(Consumer<NameValuePair> action) {
091      for (var r : extensions)
092         action.accept(r);
093      return this;
094   }
095
096   @Override /* Overridden from MediaType */
097   public MediaRange forEachParameter(Consumer<NameValuePair> action) {
098      super.forEachParameter(action);
099      return this;
100   }
101
102   /**
103    * Returns the optional set of custom extensions defined for this type.
104    *
105    * <p>
106    * Values are lowercase and never <jk>null</jk>.
107    *
108    * @return The optional list of extensions, never <jk>null</jk>.
109    */
110   public List<NameValuePair> getExtensions() { return u(l(extensions)); }
111
112   /**
113    * Returns the <js>'q'</js> (quality) value for this type, as described in Section 3.9 of RFC2616.
114    *
115    * <p>
116    * The quality value is a float between <c>0.0</c> (unacceptable) and <c>1.0</c> (most acceptable).
117    *
118    * <p>
119    * If 'q' value doesn't make sense for the context (e.g. this range was extracted from a <js>"content-*"</js>
120    * header, as opposed to <js>"accept-*"</js> header, its value will always be <js>"1"</js>.
121    *
122    * @return The 'q' value for this type, never <jk>null</jk>.
123    */
124   public Float getQValue() { return qValue; }
125
126   @Override /* Overridden from Object */
127   public String toString() {
128      return string;
129   }
130
131   @Override /* Overridden from Object */
132   public boolean equals(Object o) {
133      return o instanceof MediaRange o2 && eq(this, o2, (x, y) -> eq(x.string, y.string));
134   }
135
136   @Override /* Overridden from Object */
137   public int hashCode() {
138      return Objects.hashCode(string);
139   }
140}