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}