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.Collection;
023import java.util.Iterator;
024import java.util.function.BiConsumer;
025import java.util.function.Supplier;
026
027import org.jdrupes.httpcodec.util.ListItemizer;
028
029/**
030 * Used by by converters that convert header fields with a list of values.
031 * 
032 * Minimal restrictions are imposed on the type used as container for the 
033 * values. It must be {@link Iterable} to provide read access. A supplier
034 * and a function for appending values provide the required write access.
035 * 
036 * @param <T> the container for the values
037 * @param <V> the type of the values
038 */
039public class DefaultMultiValueConverter<T extends Iterable<V>, V>
040        implements MultiValueConverter<T, V> {
041
042    private Supplier<T> containerSupplier;
043    private BiConsumer<T, V> valueAdder;
044    private Converter<V> valueConverter;
045    // Used by default in RFC 7230, see section 7.
046    private String delimiters = ",";
047    private boolean separateValues = false;
048
049    /**
050     * Create a new converter.
051     * 
052     * @param containerSupplier a function that creates a new empty container
053     * @param valueAdder a function that adds a value to the sequence
054     * @param valueConverter the converter for the individual values
055     * @param delimiters the delimiters
056     * @param separateValues indicates that each value should be represented
057     * by a header field of its own in a message header
058     */
059    public DefaultMultiValueConverter(Supplier<T> containerSupplier,
060            BiConsumer<T, V> valueAdder, Converter<V> valueConverter,
061            String delimiters, boolean separateValues) {
062        this.containerSupplier = containerSupplier;
063        this.valueAdder = valueAdder;
064        this.valueConverter = valueConverter;
065        this.delimiters = delimiters;
066        this.separateValues = separateValues;
067    }
068
069    /**
070     * Create a new converter for a container that implements {@link Collection}
071     * and does not generate separate header fields.
072     * 
073     * @param containerSupplier a function that creates a new empty list
074     * @param valueConverter the converter for the items
075     * @param delimiters the delimiters
076     */
077    @SuppressWarnings("unchecked")
078    public DefaultMultiValueConverter(Supplier<T> containerSupplier,
079            Converter<V> valueConverter, String delimiters) {
080        this(containerSupplier,
081            (left, right) -> {
082                ((Collection<V>) left).add(right);
083            },
084            valueConverter, delimiters, false);
085    }
086
087    /**
088     * Create a new converter for a container that implements {@link Collection},
089     * does not generate separate header fields and uses a comma as separator.
090     * 
091     * @param containerSupplier a function that creates a new empty list
092     * @param itemConverter the converter for the items
093     * @see "[ABNF List Extension](https://tools.ietf.org/html/rfc7230#section-7)"
094     */
095    @SuppressWarnings("unchecked")
096    public DefaultMultiValueConverter(
097            Supplier<T> containerSupplier, Converter<V> itemConverter) {
098        this(containerSupplier, (left, right) -> {
099            ((Collection<V>) left).add(right);
100        },
101            itemConverter, ",", false);
102    }
103
104    @Override
105    public boolean separateValues() {
106        return separateValues;
107    }
108
109    /*
110     * (non-Javadoc)
111     * 
112     * @see org.jdrupes.httpcodec.types.ListConverter#containerSupplier()
113     */
114    @Override
115    public Supplier<T> containerSupplier() {
116        return containerSupplier;
117    }
118
119    /*
120     * (non-Javadoc)
121     * 
122     * @see org.jdrupes.httpcodec.types.ListConverter#valueAdder()
123     */
124    @Override
125    public BiConsumer<T, V> valueAdder() {
126        return valueAdder;
127    }
128
129    /*
130     * (non-Javadoc)
131     * 
132     * @see org.jdrupes.httpcodec.types.ListConverter#itemConverter()
133     */
134    @Override
135    @Deprecated
136    public Converter<V> valueConverter() {
137        return valueConverter;
138    }
139
140    @Override
141    public Converter<V> valueConverter(T value) {
142        return valueConverter;
143    }
144
145    /*
146     * (non-Javadoc)
147     * 
148     * @see
149     * org.jdrupes.httpcodec.protocols.http.fields.Converter#asFieldValue(java.
150     * lang.Object)
151     */
152    @Override
153    public String asFieldValue(T value) {
154        Iterator<V> iterator = value.iterator();
155        if (!iterator.hasNext()) {
156            throw new IllegalStateException(
157                "Field with list value may not be empty.");
158        }
159        boolean first = true;
160        StringBuilder result = new StringBuilder();
161        while (iterator.hasNext()) {
162            V element = iterator.next();
163            if (first) {
164                first = false;
165            } else {
166                result.append(delimiters.charAt(0));
167                result.append(' ');
168            }
169            result.append(valueConverter.asFieldValue(element));
170        }
171        return result.toString();
172    }
173
174    /*
175     * (non-Javadoc)
176     * 
177     * @see Converter#fromFieldValue(java.lang.String)
178     */
179    @Override
180    public T fromFieldValue(String text) throws ParseException {
181        T result = containerSupplier.get();
182        ListItemizer itemizer = new ListItemizer(text, delimiters);
183        while (itemizer.hasNext()) {
184            valueAdder.accept(
185                result, valueConverter.fromFieldValue(itemizer.next()));
186        }
187        return result;
188    }
189
190}