View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.util;
8   
9   import java.io.Serializable;
10  import java.util.Collection;
11  import java.util.HashMap;
12  import java.util.Iterator;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Set;
16  
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  
20  /**
21   * This allows a collection (list) of maps to be defined in Spring, via the "list" property, and
22   * then presents all the maps as a single combine map at run time.  For efficiency the combination
23   * of maps is done once and then cached.
24   */
25  public class MapCombiner implements Map<Object, Object>, Serializable
26  {
27      private static final long serialVersionUID = -6291404712112000383L;
28   
29      public static final String LIST = "list"; // the setter/getter
30      public static final int UNLIMITED_DEPTH = -1;
31  
32      private transient Log logger = LogFactory.getLog(getClass());
33      private int maxDepth = UNLIMITED_DEPTH;
34      private List list;
35      private Map cachedMerge = new HashMap();
36      private boolean isMerged = false;
37  
38      private synchronized Map getCachedMerge()
39      {
40          if (!isMerged)
41          {
42              for (Iterator maps = list.iterator(); maps.hasNext();)
43              {
44                  mergeMaps(maxDepth, cachedMerge, (Map) maps.next());
45              }
46              isMerged = true;
47          }
48          return cachedMerge;
49      }
50  
51      public void setMaxDepth(int maxDepth)
52      {
53          this.maxDepth = maxDepth;
54      }
55  
56      private void mergeMaps(int headroom, Map accumulator, Map extra)
57      {
58          for (Iterator keys = extra.keySet().iterator(); keys.hasNext();)
59          {
60              Object key = keys.next();
61              Object valueExtra = extra.get(key);
62              if (accumulator.containsKey(key))
63              {
64                  Object valueOriginal = accumulator.get(key);
65                  if (valueExtra instanceof Map && valueOriginal instanceof Map && headroom != 0)
66                  {
67                      mergeMaps(headroom - 1, (Map) valueOriginal, (Map) valueExtra);
68                  }
69                  else if (valueExtra instanceof Collection && valueOriginal instanceof Collection && headroom != 0)
70                  {
71                      ((Collection) valueOriginal).addAll((Collection) valueExtra);
72                  }
73                  else
74                  {
75                      if (logger.isDebugEnabled())
76                      {
77                          logger.debug("Overwriting " + valueOriginal + " for " + key + " during map merge");
78                      }
79                      accumulator.put(key, valueExtra);
80                  }
81              }
82              else
83              {
84                  accumulator.put(key, valueExtra);
85              }
86          }
87      }
88  
89      public void setList(List list)
90      {
91          assertNotMerged();
92          this.list = list;
93      }
94  
95      public List getList()
96      {
97          assertNotMerged();
98          return list;
99      }
100 
101     private synchronized void assertNotMerged()
102     {
103         if (isMerged)
104         {
105             throw new IllegalStateException("Maps have already been merged");
106         }
107     }
108 
109     @Override
110     public int hashCode()
111     {
112         // MULE-6607
113         // This was changed from cachedMerge.hashCode() to getCachedMerge().hashCode() since the mutation of MapCombiner (when the list
114         // of maps was merged into cachedMerge) altered the hash code. Now hashCode() method and, consequently, equals() method, trigger
115         // the merge in order not to alter equality of MapCombiner instances.
116         // This had impact on instances of classes such as AbstractEndpoint (which are stored on hash based collections) whose hashCode()
117         // method is defined based on its properties, and this, in turn, defined based on MapCombiner instances.
118         return getCachedMerge().hashCode();
119     }
120 
121     @Override
122     public boolean equals(Object o)
123     {
124         // MULE-6607
125         // See comment on hashCode() method.
126         return getCachedMerge().equals(o);
127     }
128 
129     // toString() doesn't trigger merge.
130 
131     @Override
132     public String toString()
133     {
134         if (isMerged)
135         {
136             return "merged: " + cachedMerge.toString();
137         }
138         else
139         {
140             return "unmerged: " + (null == list ? null : list.toString());
141         }
142     }
143 
144     public int size()
145     {
146         return getCachedMerge().size();
147     }
148 
149     public void clear()
150     {
151         getCachedMerge().clear();
152     }
153 
154     public boolean isEmpty()
155     {
156         return getCachedMerge().isEmpty();
157     }
158 
159     public boolean containsKey(Object key)
160     {
161         return getCachedMerge().containsKey(key);
162     }
163 
164     public boolean containsValue(Object value)
165     {
166         return getCachedMerge().containsValue(value);
167     }
168 
169     public Collection values()
170     {
171         return getCachedMerge().values();
172     }
173 
174     public void putAll(Map t)
175     {
176         getCachedMerge().putAll(t);
177     }
178 
179     public Set entrySet()
180     {
181         return getCachedMerge().entrySet();
182     }
183 
184     public Set keySet()
185     {
186         return getCachedMerge().keySet();
187     }
188 
189     public Object get(Object key)
190     {
191         return getCachedMerge().get(key);
192     }
193 
194     public Object remove(Object key)
195     {
196         return getCachedMerge().remove(key);
197     }
198 
199     public Object put(Object key, Object value)
200     {
201         return getCachedMerge().put(key, value);
202     }
203 
204 }