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}