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