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 * Creates a new object with the given value and no parameters. 062 * 063 * @param value the value 064 */ 065 public ParameterizedValue(U value) { 066 this.value = value; 067 this.params = Collections.emptyMap(); 068 } 069 070 /** 071 * For builder only. 072 */ 073 private ParameterizedValue() { 074 params = new HashMap<>(); 075 } 076 077 /** 078 * Returns the value. 079 * 080 * @return the value 081 */ 082 public U value() { 083 return value; 084 } 085 086 /** 087 * Returns the parameters. 088 * 089 * @return the parameters as unmodifiable map 090 */ 091 public Map<String, String> parameters() { 092 return Collections.unmodifiableMap(params); 093 } 094 095 /** 096 * Return the value of the parameter with the given name. 097 * 098 * @param name the name 099 * @return the value or `null` if there is no parameter with this name 100 */ 101 public String parameter(String name) { 102 return params.get(name); 103 } 104 105 /** 106 * Creates a new builder for a parameterized value. 107 * 108 * @return the builder 109 */ 110 public static <T> Builder<ParameterizedValue<T>, T> builder() { 111 return new Builder<ParameterizedValue<T>, T>( 112 new ParameterizedValue<>()); 113 } 114 115 /* 116 * (non-Javadoc) 117 * 118 * @see java.lang.Object#hashCode() 119 */ 120 @Override 121 public int hashCode() { 122 final int prime = 31; 123 int result = 1; 124 result = prime * result + ((params == null) ? 0 : params.hashCode()); 125 result = prime * result + ((value == null) ? 0 : value.hashCode()); 126 return result; 127 } 128 129 /* 130 * (non-Javadoc) 131 * 132 * @see java.lang.Object#equals(java.lang.Object) 133 */ 134 @Override 135 public boolean equals(Object obj) { 136 if (this == obj) { 137 return true; 138 } 139 if (obj == null) { 140 return false; 141 } 142 if (getClass() != obj.getClass()) { 143 return false; 144 } 145 @SuppressWarnings("rawtypes") 146 ParameterizedValue other = (ParameterizedValue) obj; 147 if (params == null) { 148 if (other.params != null) { 149 return false; 150 } 151 } else if (!params.equals(other.params)) { 152 return false; 153 } 154 if (value == null) { 155 if (other.value != null) { 156 return false; 157 } 158 } else if (!value.equals(other.value)) { 159 return false; 160 } 161 return true; 162 } 163 164 /* 165 * (non-Javadoc) 166 * 167 * @see java.lang.Object#toString() 168 */ 169 @SuppressWarnings("unchecked") 170 @Override 171 public String toString() { 172 return new ParameterizedValueConverter<>(new Converter<Object>() { 173 174 @Override 175 public String asFieldValue(Object value) { 176 return value.toString(); 177 } 178 179 @Override 180 public Object fromFieldValue(String text) throws ParseException { 181 throw new UnsupportedOperationException(); 182 } 183 184 }).asFieldValue((ParameterizedValue<Object>) this); 185 } 186 187 /** 188 * A builder for the (immutable) parameterized type. 189 * 190 * @param <R> the type of the parameterized value 191 * @param <T> the type of the unparameterized value 192 */ 193 public static class Builder<R extends ParameterizedValue<T>, T> { 194 195 private R value; 196 197 protected Builder(R value) { 198 this.value = value; 199 } 200 201 /** 202 * Initialize the builder from an existing value. 203 * 204 * @param existing the existing value, assumed to be immutable 205 * @return the builder for easy chaining 206 */ 207 public Builder<R, T> from(ParameterizedValue<T> existing) { 208 ((ParameterizedValue<T>) value).value = existing.value(); 209 ((ParameterizedValue<T>) value).params.clear(); 210 ((ParameterizedValue<T>) value).params 211 .putAll(existing.parameters()); 212 return this; 213 } 214 215 /** 216 * Returns the object built. 217 * 218 * @return the object 219 */ 220 public R build() { 221 R result = value; 222 value = null; 223 return result; 224 } 225 226 /** 227 * Set a new value. 228 * 229 * @param value the value 230 * @return the builder for easy chaining 231 */ 232 public Builder<R, T> setValue(T value) { 233 ((ParameterizedValue<T>) this.value).value = value; 234 return this; 235 } 236 237 /** 238 * Set a parameter. 239 * 240 * @param name the parameter name 241 * @param value the value 242 * @return the builder for easy chaining 243 */ 244 public Builder<R, T> setParameter(String name, String value) { 245 ((ParameterizedValue<T>) this.value).params.put(name, value); 246 return this; 247 } 248 249 /** 250 * Remove a parameter 251 * 252 * @param name the parameter name 253 * @return the builder for easy chaining 254 */ 255 public Builder<R, T> remove(String name) { 256 ((ParameterizedValue<T>) this.value).params.remove(name); 257 return this; 258 } 259 } 260 261 /** 262 * A base class for converters for parameterized values. 263 * Converts field values such as `value; param1=value1; param2=value2`. 264 * 265 * @param <P> the parameterized type 266 * @param <U> the unparameterized type 267 */ 268 public static class ParamValueConverterBase<P extends ParameterizedValue<U>, 269 U> 270 implements Converter<P> { 271 272 private Converter<U> valueConverter; 273 private Converter<String> paramValueConverter; 274 private BiFunction<U, Map<String, String>, P> paramValueConstructor; 275 276 /** 277 * Creates a new converter by extending the given value converter 278 * with functionality for handling the parameters. Parameter 279 * values are used literally (no quoting). 280 * 281 * @param valueConverter the converter for a value (without parameters) 282 * @param paramValueConstructor a method that creates the result 283 * from an instance of the type and a map of parameters 284 * (used by {@link #fromFieldValue(String)}). 285 */ 286 public ParamValueConverterBase(Converter<U> valueConverter, 287 BiFunction<U, Map<String, String>, P> paramValueConstructor) { 288 this(valueConverter, Converters.UNQUOTED_STRING, 289 paramValueConstructor); 290 } 291 292 /** 293 * Creates a new converter by extending the given value converter 294 * with functionality for handling the parameters. 295 * 296 * @param valueConverter the converter for a value (without parameters) 297 * @param paramValueConverter the converter for parameterValues 298 * @param paramValueConstructor a method that creates the result 299 * from an instance of the type and a map of parameters 300 * (used by {@link #fromFieldValue(String)}). 301 */ 302 public ParamValueConverterBase(Converter<U> valueConverter, 303 Converter<String> paramValueConverter, 304 BiFunction<U, Map<String, String>, P> paramValueConstructor) { 305 if (valueConverter == null) { 306 throw new IllegalArgumentException( 307 "Value converter may not be null."); 308 } 309 this.valueConverter = valueConverter; 310 this.paramValueConverter = paramValueConverter; 311 this.paramValueConstructor = paramValueConstructor; 312 } 313 314 public String asFieldValue(P value) { 315 StringBuilder result = new StringBuilder(); 316 result.append(valueConverter.asFieldValue(value.value())); 317 for (Entry<String, String> e : value.parameters().entrySet()) { 318 result.append("; "); 319 result.append(e.getKey()); 320 result.append('='); 321 result.append(paramValueConverter.asFieldValue(e.getValue())); 322 } 323 return result.toString(); 324 } 325 326 public P fromFieldValue(String text) throws ParseException { 327 ListItemizer li = new ListItemizer(text, ";"); 328 String valueRepr = li.next(); 329 if (valueRepr == null) { 330 throw new ParseException("Value may not be empty", 0); 331 } 332 U value = valueConverter.fromFieldValue(valueRepr); 333 Map<String, String> params = new HashMap<>(); 334 while (li.hasNext()) { 335 ListItemizer pi = new ListItemizer(li.next(), "="); 336 if (!pi.hasNext()) { 337 throw new ParseException("parameter may not be empty", 0); 338 } 339 String paramKey = pi.next().trim().toLowerCase(); 340 String paramValue = null; 341 if (pi.hasNext()) { 342 paramValue = paramValueConverter.fromFieldValue(pi.next()); 343 } 344 params.put(paramKey, paramValue); 345 } 346 return paramValueConstructor.apply(value, params); 347 } 348 } 349 350 /** 351 * Extends {@link ParamValueConverterBase} to a realization 352 * of `Converter<ParameterizedValue<T>>`. 353 * 354 * @param <T> the base value type 355 */ 356 public static class ParameterizedValueConverter<T> 357 extends ParamValueConverterBase<ParameterizedValue<T>, T> { 358 359 public ParameterizedValueConverter(Converter<T> valueConverter) { 360 super(valueConverter, ParameterizedValue<T>::new); 361 } 362 363 } 364}