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;
014
015import static org.apache.juneau.internal.ObjectUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.util.*;
019
020import org.apache.http.*;
021import org.apache.http.message.*;
022import org.apache.juneau.annotation.*;
023import org.apache.juneau.collections.*;
024
025/**
026 * Represents a single value in a comma-delimited header value that optionally contains a quality metric for
027 * comparison and extension parameters.
028 *
029 * <p>
030 * Similar in concept to {@link MediaRanges} except instead of media types (e.g. <js>"text/json"</js>),
031 * it's a simple type (e.g. <js>"iso-8601"</js>).
032 *
033 * <p>
034 * An example of a type range is a value in an <c>Accept-Encoding</c> header.
035 *
036 * <ul class='seealso'>
037 *    <li class='extlink'>{@doc ExtRFC2616}
038 * </ul>
039 */
040@BeanIgnore
041public class StringRange {
042
043   private final NameValuePair[] extensions;
044   private final Float qValue;
045   private final String name;
046   private final String string;
047
048   /**
049    * Constructor.
050    *
051    * @param value
052    *    The raw string range string.
053    *    <br>A value of <jk>null</jk> gets interpreted as matching anything (e.g. <js>"*"</js>).
054    */
055   public StringRange(String value) {
056      this(parse(value));
057   }
058
059   /**
060    * Constructor.
061    *
062    * @param e The parsed string range element.
063    */
064   public StringRange(HeaderElement e) {
065      Float qValue = 1f;
066
067      // The media type consists of everything up to the q parameter.
068      // The q parameter and stuff after is part of the range.
069      List<NameValuePair> extensions = AList.of();
070      for (NameValuePair p : e.getParameters()) {
071         if (p.getName().equals("q")) {
072            qValue = Float.parseFloat(p.getValue());
073         } else {
074            extensions.add(BasicNameValuePair.of(p.getName(), p.getValue()));
075         }
076      }
077
078      this.qValue = qValue;
079      this.extensions = extensions.toArray(new NameValuePair[extensions.size()]);
080      this.name = e.getName();
081
082      StringBuffer sb = new StringBuffer();
083      sb.append(name);
084
085      // '1' is equivalent to specifying no qValue. If there's no extensions, then we won't include a qValue.
086      if (Float.compare(qValue.floatValue(), 1f) == 0) {
087         if (this.extensions.length > 0) {
088            sb.append(";q=").append(qValue);
089            for (NameValuePair p : extensions)
090               sb.append(';').append(p.getName()).append('=').append(p.getValue());
091         }
092      } else {
093         sb.append(";q=").append(qValue);
094         for (NameValuePair p : extensions)
095            sb.append(';').append(p.getName()).append('=').append(p.getValue());
096      }
097      string = sb.toString();
098   }
099
100   /**
101    * Returns the name of this string range.
102    *
103    * <p>
104    * This is the primary value minus the quality or other parameters.
105    *
106    * @return The name of this string range.
107    */
108   public String getName() {
109      return name;
110   }
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() {
125      return qValue;
126   }
127
128   /**
129    * Returns the optional set of custom extensions defined for this type.
130    *
131    * <p>
132    * Values are lowercase and never <jk>null</jk>.
133    *
134    * @return The optional list of extensions, never <jk>null</jk>.
135    */
136   public List<NameValuePair> getExtensions() {
137      return Collections.unmodifiableList(Arrays.asList(extensions));
138   }
139
140   /**
141    * Returns <jk>true</jk> if the specified object is also a <c>StringRange</c>, and has the same qValue, type,
142    * parameters, and extensions.
143    *
144    * @return <jk>true</jk> if object is equivalent.
145    */
146   @Override /* Object */
147   public boolean equals(Object o) {
148      return (o instanceof StringRange) && eq(this, (StringRange)o, (x,y)->eq(x.string, y.string));
149   }
150
151   /**
152    * Returns a hash based on this instance's <c>media-type</c>.
153    *
154    * @return A hash based on this instance's <c>media-type</c>.
155    */
156   @Override /* Object */
157   public int hashCode() {
158      return string.hashCode();
159   }
160
161   /**
162    * Performs a match of this string range against the specified name.
163    *
164    * @param name The name being compared against.
165    * @return
166    *    0 = no match, 100 = perfect match, 50 = meta-match.
167    */
168   public int match(String name) {
169      if (qValue == 0)
170         return 0;
171      if (eq(this.name, name))
172         return 100;
173      if (eq(this.name, "*"))
174         return 50;
175      return 0;
176   }
177
178   private static HeaderElement parse(String value) {
179      HeaderElement[] elements = BasicHeaderValueParser.parseElements(emptyIfNull(trim(value)), null);
180      return (elements.length > 0 ? elements[0] : new BasicHeaderElement("*", ""));
181   }
182
183   @Override /* Object */
184   public String toString() {
185      return string;
186   }
187}