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}