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}