1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.util;
20
21import java.io.Serializable;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27import java.util.Set;
28import java.util.concurrent.ConcurrentHashMap;
29import java.util.concurrent.ConcurrentMap;
30
31/* ------------------------------------------------------------ */
32/** A multi valued Map.
33 * This Map specializes HashMap and provides methods
34 * that operate on multi valued items.
35 * <P>
36 * Implemented as a map of LazyList values
37 * @param <K> The key type of the map.
38 *
39 * @see LazyList
40 *
41 */
42public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
43{
44    private static final long serialVersionUID = -6878723138353851005L;
45    Map<K,Object> _map;
46    ConcurrentMap<K, Object> _cmap;
47
48    public MultiMap()
49    {
50        _map=new HashMap<K, Object>();
51    }
52
53    public MultiMap(Map<K,Object> map)
54    {
55        if (map instanceof ConcurrentMap)
56            _map=_cmap=new ConcurrentHashMap<K, Object>(map);
57        else
58            _map=new HashMap<K, Object>(map);
59    }
60
61    public MultiMap(MultiMap<K> map)
62    {
63        if (map._cmap!=null)
64            _map=_cmap=new ConcurrentHashMap<K, Object>(map._cmap);
65        else
66            _map=new HashMap<K,Object>(map._map);
67    }
68
69    public MultiMap(int capacity)
70    {
71        _map=new HashMap<K, Object>(capacity);
72    }
73
74    public MultiMap(boolean concurrent)
75    {
76        if (concurrent)
77            _map=_cmap=new ConcurrentHashMap<K, Object>();
78        else
79            _map=new HashMap<K, Object>();
80    }
81
82
83    /* ------------------------------------------------------------ */
84    /** Get multiple values.
85     * Single valued entries are converted to singleton lists.
86     * @param name The entry key.
87     * @return Unmodifieable List of values.
88     */
89    public List getValues(Object name)
90    {
91        return LazyList.getList(_map.get(name),true);
92    }
93
94    /* ------------------------------------------------------------ */
95    /** Get a value from a multiple value.
96     * If the value is not a multivalue, then index 0 retrieves the
97     * value or null.
98     * @param name The entry key.
99     * @param i Index of element to get.
100     * @return Unmodifieable List of values.
101     */
102    public Object getValue(Object name,int i)
103    {
104        Object l=_map.get(name);
105        if (i==0 && LazyList.size(l)==0)
106            return null;
107        return LazyList.get(l,i);
108    }
109
110
111    /* ------------------------------------------------------------ */
112    /** Get value as String.
113     * Single valued items are converted to a String with the toString()
114     * Object method. Multi valued entries are converted to a comma separated
115     * List.  No quoting of commas within values is performed.
116     * @param name The entry key.
117     * @return String value.
118     */
119    public String getString(Object name)
120    {
121        Object l=_map.get(name);
122        switch(LazyList.size(l))
123        {
124          case 0:
125              return null;
126          case 1:
127              Object o=LazyList.get(l,0);
128              return o==null?null:o.toString();
129          default:
130          {
131              StringBuilder values=new StringBuilder(128);
132              for (int i=0; i<LazyList.size(l); i++)
133              {
134                  Object e=LazyList.get(l,i);
135                  if (e!=null)
136                  {
137                      if (values.length()>0)
138                          values.append(',');
139                      values.append(e.toString());
140                  }
141              }
142              return values.toString();
143          }
144        }
145    }
146
147    /* ------------------------------------------------------------ */
148    public Object get(Object name)
149    {
150        Object l=_map.get(name);
151        switch(LazyList.size(l))
152        {
153          case 0:
154              return null;
155          case 1:
156              Object o=LazyList.get(l,0);
157              return o;
158          default:
159              return LazyList.getList(l,true);
160        }
161    }
162
163    /* ------------------------------------------------------------ */
164    /** Put and entry into the map.
165     * @param name The entry key.
166     * @param value The entry value.
167     * @return The previous value or null.
168     */
169    public Object put(K name, Object value)
170    {
171        return _map.put(name,LazyList.add(null,value));
172    }
173
174    /* ------------------------------------------------------------ */
175    /** Put multi valued entry.
176     * @param name The entry key.
177     * @param values The List of multiple values.
178     * @return The previous value or null.
179     */
180    public Object putValues(K name, List<? extends Object> values)
181    {
182        return _map.put(name,values);
183    }
184
185    /* ------------------------------------------------------------ */
186    /** Put multi valued entry.
187     * @param name The entry key.
188     * @param values The String array of multiple values.
189     * @return The previous value or null.
190     */
191    public Object putValues(K name, String... values)
192    {
193        Object list=null;
194        for (int i=0;i<values.length;i++)
195            list=LazyList.add(list,values[i]);
196        return _map.put(name,list);
197    }
198
199
200    /* ------------------------------------------------------------ */
201    /** Add value to multi valued entry.
202     * If the entry is single valued, it is converted to the first
203     * value of a multi valued entry.
204     * @param name The entry key.
205     * @param value The entry value.
206     */
207    public void add(K name, Object value)
208    {
209        Object lo = _map.get(name);
210        Object ln = LazyList.add(lo,value);
211        if (lo!=ln)
212            _map.put(name,ln);
213    }
214
215    /* ------------------------------------------------------------ */
216    /** Add values to multi valued entry.
217     * If the entry is single valued, it is converted to the first
218     * value of a multi valued entry.
219     * @param name The entry key.
220     * @param values The List of multiple values.
221     */
222    public void addValues(K name, List<? extends Object> values)
223    {
224        Object lo = _map.get(name);
225        Object ln = LazyList.addCollection(lo,values);
226        if (lo!=ln)
227            _map.put(name,ln);
228    }
229
230    /* ------------------------------------------------------------ */
231    /** Add values to multi valued entry.
232     * If the entry is single valued, it is converted to the first
233     * value of a multi valued entry.
234     * @param name The entry key.
235     * @param values The String array of multiple values.
236     */
237    public void addValues(K name, String[] values)
238    {
239        Object lo = _map.get(name);
240        Object ln = LazyList.addCollection(lo,Arrays.asList(values));
241        if (lo!=ln)
242            _map.put(name,ln);
243    }
244
245    /* ------------------------------------------------------------ */
246    /** Remove value.
247     * @param name The entry key.
248     * @param value The entry value.
249     * @return true if it was removed.
250     */
251    public boolean removeValue(K name,Object value)
252    {
253        Object lo = _map.get(name);
254        Object ln=lo;
255        int s=LazyList.size(lo);
256        if (s>0)
257        {
258            ln=LazyList.remove(lo,value);
259            if (ln==null)
260                _map.remove(name);
261            else
262                _map.put(name, ln);
263        }
264        return LazyList.size(ln)!=s;
265    }
266
267
268    /* ------------------------------------------------------------ */
269    /** Put all contents of map.
270     * @param m Map
271     */
272    public void putAll(Map<? extends K, ? extends Object> m)
273    {
274        boolean multi = (m instanceof MultiMap);
275
276        if (multi)
277        {
278            for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
279            {
280                _map.put(entry.getKey(),LazyList.clone(entry.getValue()));
281            }
282        }
283        else
284        {
285            _map.putAll(m);
286        }
287    }
288
289    /* ------------------------------------------------------------ */
290    /**
291     * @return Map of String arrays
292     */
293    public Map<K,String[]> toStringArrayMap()
294    {
295        HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2)
296        {
297            public String toString()
298            {
299                StringBuilder b=new StringBuilder();
300                b.append('{');
301                for (K k:keySet())
302                {
303                    if(b.length()>1)
304                        b.append(',');
305                    b.append(k);
306                    b.append('=');
307                    b.append(Arrays.asList(get(k)));
308                }
309
310                b.append('}');
311                return b.toString();
312            }
313        };
314
315        for(Map.Entry<K,Object> entry: _map.entrySet())
316        {
317            String[] a = LazyList.toStringArray(entry.getValue());
318            map.put(entry.getKey(),a);
319        }
320        return map;
321    }
322
323    @Override
324    public String toString()
325    {
326        return _cmap==null?_map.toString():_cmap.toString();
327    }
328
329    public void clear()
330    {
331        _map.clear();
332    }
333
334    public boolean containsKey(Object key)
335    {
336        return _map.containsKey(key);
337    }
338
339    public boolean containsValue(Object value)
340    {
341        return _map.containsValue(value);
342    }
343
344    public Set<Entry<K, Object>> entrySet()
345    {
346        return _map.entrySet();
347    }
348
349    @Override
350    public boolean equals(Object o)
351    {
352        return _map.equals(o);
353    }
354
355    @Override
356    public int hashCode()
357    {
358        return _map.hashCode();
359    }
360
361    public boolean isEmpty()
362    {
363        return _map.isEmpty();
364    }
365
366    public Set<K> keySet()
367    {
368        return _map.keySet();
369    }
370
371    public Object remove(Object key)
372    {
373        return _map.remove(key);
374    }
375
376    public int size()
377    {
378        return _map.size();
379    }
380
381    public Collection<Object> values()
382    {
383        return _map.values();
384    }
385
386
387
388    public Object putIfAbsent(K key, Object value)
389    {
390        if (_cmap==null)
391            throw new UnsupportedOperationException();
392        return _cmap.putIfAbsent(key,value);
393    }
394
395    public boolean remove(Object key, Object value)
396    {
397        if (_cmap==null)
398            throw new UnsupportedOperationException();
399        return _cmap.remove(key,value);
400    }
401
402    public boolean replace(K key, Object oldValue, Object newValue)
403    {
404        if (_cmap==null)
405            throw new UnsupportedOperationException();
406        return _cmap.replace(key,oldValue,newValue);
407    }
408
409    public Object replace(K key, Object value)
410    {
411        if (_cmap==null)
412            throw new UnsupportedOperationException();
413        return _cmap.replace(key,value);
414    }
415}
416