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