001/*
002 * MIT License
003 * 
004 * Copyright (c) 2016 Michael Angstadt
005 * 
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 * 
013 * The above copyright notice and this permission notice shall be included in
014 * all copies or substantial portions of the Software.
015 * 
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024
025package com.github.mangstadt.vinnie;
026
027import java.nio.charset.Charset;
028import java.nio.charset.IllegalCharsetNameException;
029import java.nio.charset.UnsupportedCharsetException;
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.Iterator;
033import java.util.LinkedHashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Map.Entry;
037
038/**
039 * A simple multimap implementation for holding the parameters of a
040 * {@link VObjectProperty}. Enforces case-insensitivity of parameter names by
041 * converting them to uppercase.
042 * @author Michael Angstadt
043 */
044public class VObjectParameters implements Iterable<Map.Entry<String, List<String>>> {
045        private final Map<String, List<String>> multimap;
046
047        /**
048         * Creates an empty list of parameters.
049         */
050        public VObjectParameters() {
051                multimap = new LinkedHashMap<String, List<String>>(); //preserve insertion order of keys
052        }
053
054        /**
055         * <p>
056         * Creates a list of parameters backed by the given map. Any changes made to
057         * the given map will effect the parameter list and vice versa.
058         * </p>
059         * <p>
060         * If the given map is not empty, care should be taken to ensure that all of
061         * its keys are in uppercase before passing it into this constructor.
062         * </p>
063         * @param map the map
064         */
065        public VObjectParameters(Map<String, List<String>> map) {
066                multimap = map;
067        }
068
069        /**
070         * Copies an existing list of parameters.
071         * @param original the existing list
072         */
073        public VObjectParameters(VObjectParameters original) {
074                this();
075                for (Map.Entry<String, List<String>> entry : original) {
076                        String name = entry.getKey();
077                        List<String> values = entry.getValue();
078                        multimap.put(name, new ArrayList<String>(values));
079                }
080        }
081
082        /**
083         * Gets the values that are assigned to a key.
084         * @param key the key
085         * @return the values or null if the key does not exist
086         */
087        public List<String> get(String key) {
088                key = sanitizeKey(key);
089                return _get(key);
090        }
091
092        /**
093         * @param key assumed to already be in uppercase
094         */
095        private List<String> _get(String key) {
096                return multimap.get(key);
097        }
098
099        /**
100         * Inserts a value.
101         * @param key the key
102         * @param value the value to add
103         */
104        public void put(String key, String value) {
105                key = sanitizeKey(key);
106                _put(key, value);
107        }
108
109        /**
110         * @param key assumed to already be in uppercase
111         * @param value the value to add
112         */
113        private void _put(String key, String value) {
114                List<String> list = _get(key);
115                if (list == null) {
116                        list = new ArrayList<String>();
117                        multimap.put(key, list);
118                }
119                list.add(value);
120        }
121
122        /**
123         * Inserts multiple values.
124         * @param key the key
125         * @param values the values to add
126         */
127        public void putAll(String key, String... values) {
128                if (values.length == 0) {
129                        return;
130                }
131                key = sanitizeKey(key);
132                _putAll(key, values);
133        }
134
135        /**
136         * @param key assumed to already be in uppercase
137         * @param values the values to add
138         */
139        private void _putAll(String key, String... values) {
140                List<String> list = _get(key);
141                if (list == null) {
142                        list = new ArrayList<String>();
143                        multimap.put(key, list);
144                }
145                list.addAll(Arrays.asList(values));
146        }
147
148        /**
149         * Replaces all the values of the given key with the given value.
150         * @param key the key
151         * @param value the value
152         * @return the replaced values or null if the key didn't exist
153         */
154        public List<String> replace(String key, String value) {
155                key = sanitizeKey(key);
156                List<String> replaced = _removeAll(key);
157                _put(key, value);
158                return replaced;
159        }
160
161        /**
162         * Replaces all the values of the given key with the given values.
163         * @param key the key
164         * @param values the values
165         * @return the replaced values or null if the key didn't exist
166         */
167        public List<String> replaceAll(String key, String... values) {
168                key = sanitizeKey(key);
169                List<String> replaced = _removeAll(key);
170                if (values.length > 0) {
171                        _putAll(key, values);
172                }
173                return replaced;
174        }
175
176        /**
177         * Removes a value.
178         * @param key the key
179         * @param value the value to remove
180         * @return true if the value was found, false if not
181         */
182        public boolean remove(String key, String value) {
183                List<String> values = get(key);
184                return (values == null) ? false : values.remove(value);
185        }
186
187        /**
188         * Removes all values associated with a key, along with the key itself.
189         * @param key the key
190         * @return the removed values or null if the key didn't exist
191         */
192        public List<String> removeAll(String key) {
193                key = sanitizeKey(key);
194                return _removeAll(key);
195        }
196
197        /**
198         * @param key assumed to already be in uppercase
199         */
200        private List<String> _removeAll(String key) {
201                return multimap.remove(key);
202        }
203
204        /**
205         * Clears the multimap.
206         */
207        public void clear() {
208                multimap.clear();
209        }
210
211        /**
212         * Gets the first value assigned to the given key.
213         * @param key the key
214         * @return the value or null if the given key does not have any values
215         */
216        public String first(String key) {
217                List<String> values = get(key);
218                return (values == null || values.isEmpty()) ? null : values.get(0);
219        }
220
221        /**
222         * Determines if a "quoted-printable encoding" parameter exists.
223         * @return true if the parameter exists, false if not
224         */
225        public boolean isQuotedPrintable() {
226                for (String key : new String[] { "ENCODING", null }) {
227                        List<String> values = _get(key);
228                        if (values == null) {
229                                continue;
230                        }
231
232                        for (String value : values) {
233                                if ("QUOTED-PRINTABLE".equalsIgnoreCase(value)) {
234                                        return true;
235                                }
236                        }
237                }
238
239                return false;
240        }
241
242        /**
243         * Gets the CHARSET parameter.
244         * @return the character set or null if a character set is not defined
245         * @throws IllegalCharsetNameException if the character set name contains
246         * illegal characters
247         * @throws UnsupportedCharsetException if the local JVM does not recognized
248         * the character set
249         */
250        public Charset getCharset() throws IllegalCharsetNameException, UnsupportedCharsetException {
251                String charsetStr = first("CHARSET");
252                return (charsetStr == null) ? null : Charset.forName(charsetStr);
253        }
254
255        /**
256         * Gets the map that backs this parameters list.
257         * @return the map
258         */
259        public Map<String, List<String>> getMap() {
260                return multimap;
261        }
262
263        /**
264         * Creates an iterator over all the parameters (for use in foreach loops).
265         * @return the iterator
266         */
267        public Iterator<Entry<String, List<String>>> iterator() {
268                return multimap.entrySet().iterator();
269        }
270
271        /**
272         * Converts the given key to uppercase. Call this method before passing a
273         * key to the multimap.
274         * @param key the key
275         * @return the sanitized key
276         */
277        private String sanitizeKey(String key) {
278                return (key == null) ? null : key.toUpperCase();
279        }
280
281        @Override
282        public int hashCode() {
283                return multimap.hashCode();
284        }
285
286        @Override
287        public boolean equals(Object obj) {
288                if (this == obj) return true;
289                if (obj == null) return false;
290                if (getClass() != obj.getClass()) return false;
291                VObjectParameters other = (VObjectParameters) obj;
292                return multimap.equals(other.multimap);
293        }
294
295        @Override
296        public String toString() {
297                return multimap.toString();
298        }
299}