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