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