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