001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.types; 020 021import java.text.ParseException; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.function.BiFunction; 028 029import org.jdrupes.httpcodec.util.ListItemizer; 030 031/** 032 * Represents a parameterized value 033 * such as `value; param1=value1; param2=value2`. 034 * 035 * @param <U> the type of the unparameterized value 036 */ 037public class ParameterizedValue<U> { 038 039 public static Comparator<ParameterizedValue<?>> WEIGHT_COMPARATOR 040 = Comparator.nullsFirst( 041 Comparator.comparing(mt -> mt.parameter("q"), 042 Comparator.nullsFirst( 043 Comparator.comparing(Float::parseFloat) 044 .reversed()))); 045 046 private U value; 047 private Map<String, String> params; 048 049 /** 050 * Creates a new object with the given value and parameters. 051 * 052 * @param value the value 053 * @param parameters the parameters 054 */ 055 public ParameterizedValue(U value, Map<String,String> parameters) { 056 this.value = value; 057 this.params = parameters; 058 } 059 060 /** 061 * For builder only. 062 */ 063 private ParameterizedValue() { 064 params = new HashMap<>(); 065 } 066 067 /** 068 * Returns the value. 069 * 070 * @return the value 071 */ 072 public U value() { 073 return value; 074 } 075 076 /** 077 * Returns the parameters. 078 * 079 * @return the parameters as unmodifiable map 080 */ 081 public Map<String, String> parameters() { 082 return Collections.unmodifiableMap(params); 083 } 084 085 /** 086 * Return the value of the parameter with the given name. 087 * 088 * @param name the name 089 * @return the value or `null` if there is no parameter with this name 090 */ 091 public String parameter(String name) { 092 return params.get(name); 093 } 094 095 /** 096 * Creates a new builder for a parameterized value. 097 * 098 * @return the builder 099 */ 100 public static <T> Builder<ParameterizedValue<T>, T> builder() { 101 return new Builder<ParameterizedValue<T>, T>(new ParameterizedValue<>()); 102 } 103 104 /* (non-Javadoc) 105 * @see java.lang.Object#hashCode() 106 */ 107 @Override 108 public int hashCode() { 109 final int prime = 31; 110 int result = 1; 111 result = prime * result + ((params == null) ? 0 : params.hashCode()); 112 result = prime * result + ((value == null) ? 0 : value.hashCode()); 113 return result; 114 } 115 116 /* (non-Javadoc) 117 * @see java.lang.Object#equals(java.lang.Object) 118 */ 119 @Override 120 public boolean equals(Object obj) { 121 if (this == obj) { 122 return true; 123 } 124 if (obj == null) { 125 return false; 126 } 127 if (getClass() != obj.getClass()) { 128 return false; 129 } 130 @SuppressWarnings("rawtypes") 131 ParameterizedValue other = (ParameterizedValue) obj; 132 if (params == null) { 133 if (other.params != null) { 134 return false; 135 } 136 } else if (!params.equals(other.params)) { 137 return false; 138 } 139 if (value == null) { 140 if (other.value != null) { 141 return false; 142 } 143 } else if (!value.equals(other.value)) { 144 return false; 145 } 146 return true; 147 } 148 149 /* (non-Javadoc) 150 * @see java.lang.Object#toString() 151 */ 152 @SuppressWarnings("unchecked") 153 @Override 154 public String toString() { 155 return new ParameterizedValueConverter<>(new Converter<Object>() { 156 157 @Override 158 public String asFieldValue(Object value) { 159 return value.toString(); 160 } 161 162 @Override 163 public Object fromFieldValue(String text) throws ParseException { 164 throw new UnsupportedOperationException(); 165 } 166 167 }).asFieldValue((ParameterizedValue<Object>)this); 168 } 169 170 /** 171 * A builder for the (immutable) parameterized type. 172 * 173 * @param <R> the type of the parameterized value 174 * @param <T> the type of the unparameterized value 175 */ 176 public static class Builder<R extends ParameterizedValue<T>, T> { 177 178 private R value; 179 180 protected Builder(R value) { 181 this.value = value; 182 } 183 184 /** 185 * Initialize the builder from an existing value. 186 * 187 * @param existing the existing value, assumed to be immutable 188 * @return the builder for easy chaining 189 */ 190 public Builder<R, T> from(ParameterizedValue<T> existing) { 191 ((ParameterizedValue<T>)value).value = existing.value(); 192 ((ParameterizedValue<T>)value).params.clear(); 193 ((ParameterizedValue<T>)value).params 194 .putAll(existing.parameters()); 195 return this; 196 } 197 198 /** 199 * Returns the object built. 200 * 201 * @return the object 202 */ 203 public R build() { 204 R result = value; 205 value = null; 206 return result; 207 } 208 209 /** 210 * Set a new value. 211 * 212 * @param value the value 213 * @return the builder for easy chaining 214 */ 215 public Builder<R, T> setValue(T value) { 216 ((ParameterizedValue<T>)this.value).value = value; 217 return this; 218 } 219 220 /** 221 * Set a parameter. 222 * 223 * @param name the parameter name 224 * @param value the value 225 * @return the builder for easy chaining 226 */ 227 public Builder<R, T> setParameter(String name, String value) { 228 ((ParameterizedValue<T>)this.value).params.put(name, value); 229 return this; 230 } 231 232 /** 233 * Remove a parameter 234 * 235 * @param name the parameter name 236 * @return the builder for easy chaining 237 */ 238 public Builder<R, T> remove(String name) { 239 ((ParameterizedValue<T>)this.value).params.remove(name); 240 return this; 241 } 242 } 243 244 /** 245 * A base class for converters for parameterized values. 246 * Converts field values such as `value; param1=value1; param2=value2`. 247 * 248 * @param <P> the parameterized type 249 * @param <U> the unparameterized type 250 */ 251 public static class 252 ParamValueConverterBase<P extends ParameterizedValue<U>, U> 253 implements Converter<P> { 254 255 private Converter<U> valueConverter; 256 private Converter<String> paramValueConverter; 257 private BiFunction<U, Map<String,String>, P> paramValueConstructor; 258 259 /** 260 * Creates a new converter by extending the given value converter 261 * with functionality for handling the parameters. Parameter 262 * values are used literally (no quoting). 263 * 264 * @param valueConverter the converter for a value (without parameters) 265 * @param paramValueConstructor a method that creates the result 266 * from an instance of the type and a map of parameters 267 * (used by {@link #fromFieldValue(String)}). 268 */ 269 public ParamValueConverterBase(Converter<U> valueConverter, 270 BiFunction<U, Map<String,String>, P> paramValueConstructor) { 271 this(valueConverter, Converters.UNQUOTED_STRING, 272 paramValueConstructor); 273 } 274 275 /** 276 * Creates a new converter by extending the given value converter 277 * with functionality for handling the parameters. 278 * 279 * @param valueConverter the converter for a value (without parameters) 280 * @param paramValueConverter the converter for parameterValues 281 * @param paramValueConstructor a method that creates the result 282 * from an instance of the type and a map of parameters 283 * (used by {@link #fromFieldValue(String)}). 284 */ 285 public ParamValueConverterBase( Converter<U> valueConverter, 286 Converter<String> paramValueConverter, 287 BiFunction<U, Map<String,String>, P> paramValueConstructor) { 288 if (valueConverter == null) { 289 throw new IllegalArgumentException( 290 "Value converter may not be null."); 291 } 292 this.valueConverter = valueConverter; 293 this.paramValueConverter = paramValueConverter; 294 this.paramValueConstructor = paramValueConstructor; 295 } 296 297 public String asFieldValue(P value) { 298 StringBuilder result = new StringBuilder(); 299 result.append(valueConverter.asFieldValue(value.value())); 300 for (Entry<String, String> e: value.parameters().entrySet()) { 301 result.append("; "); 302 result.append(e.getKey()); 303 result.append('='); 304 result.append(paramValueConverter.asFieldValue(e.getValue())); 305 } 306 return result.toString(); 307 } 308 309 public P fromFieldValue(String text) throws ParseException { 310 ListItemizer li = new ListItemizer(text, ";"); 311 String valueRepr = li.next(); 312 if (valueRepr == null) { 313 throw new ParseException("Value may not be empty", 0); 314 } 315 U value = valueConverter.fromFieldValue(valueRepr); 316 Map<String,String> params = new HashMap<>(); 317 while (li.hasNext()) { 318 ListItemizer pi = new ListItemizer(li.next(), "="); 319 if (!pi.hasNext()) { 320 throw new ParseException("parameter may not be empty", 0); 321 } 322 String paramKey = pi.next().trim().toLowerCase(); 323 String paramValue = null; 324 if (pi.hasNext()) { 325 paramValue = paramValueConverter.fromFieldValue(pi.next()); 326 } 327 params.put(paramKey, paramValue); 328 } 329 return paramValueConstructor.apply(value, params); 330 } 331 } 332 333 /** 334 * Extends {@link ParamValueConverterBase} to a realization 335 * of `Converter<ParameterizedValue<T>>`. 336 * 337 * @param <T> the base value type 338 */ 339 public static class ParameterizedValueConverter<T> 340 extends ParamValueConverterBase<ParameterizedValue<T>, T> { 341 342 public ParameterizedValueConverter(Converter<T> valueConverter) { 343 super(valueConverter, ParameterizedValue<T>::new); 344 } 345 346 } 347}