1    //
2    // SwitchRMI  Framework
3    // Copyright (c) 2000-2002 by Michael J. Henderson & Associates.
4    //
5    // Michael Henderson
6    // http://switchrmi.sf.net
7    // mailto:mikehenderson@dunelm.org.uk
8    //
9    // This library is free software.
10   //
11   // You may redistribute it and/or modify it under the terms of the GNU
12   // Lesser General Public License as published by the Free Software Foundation.
13   //
14   // Version 2.1 of the license should be included with this distribution in
15   // the file LICENSE, as well as License.html. If the license is not
16   // included with this distribution, you may find a copy at the FSF web
17   // site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
18   // Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA.
19   //
20   // This library is distributed in the hope that it will be useful,
21   // but WITHOUT ANY WARRANTY; without even the implied waranty of
22   // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23   // Lesser General Public License for more details.
24   //
25   // $Id: BeanMap.java,v 1.1 2002/11/11 22:19:48 mikehenderson Exp $
26   package com.mjh.util;
27   
28   import java.beans.*;
29   
30   import java.lang.reflect.*;
31   
32   import java.util.*;
33   
34   import org.apache.log4j.Logger;
35   
36   /**
37    * BeanMap provides an adaptor for Java Beans to allow them to be treated as a java.util.Map.
38    * <p>
39    * BeanMap is pretty good at coercing string values into the appropriate primitive type
40    * for a bean setter method argument.
41    * <p>
42    * For example:
43    * <p>
44    * <code>
45    *    MyBean myBean = new MyBean();<br>
46    *    BeanMap myMap = new BeamMap(myBean);<br>
47    *    myMap.put("totalCount", "100");<br>
48    *    Number value = (Number)myMap.get("totalCount");<br>
49    * </code>
50    * <p>
51    * The above code will call the <code>setTotalCount()</code> method on myBean with the string value of "100"
52    * coerced to the required integer argument. Then call the <code>getTotalCount()</code> method on myBean and convert
53    * the int return value to the Java primitive wrapper class.
54    * <p>
55    * The coercion makes BeanMap particularly useful to initialize beans from values stored in text files.
56    *
57    * @author Mike Henderson
58    */
59   public class BeanMap implements java.util.Map
60   {
61       private static final Logger log = Logger.getLogger(BeanMap.class.getName());
62       private Object bean;
63       private BeanInfo info;
64       private Map pdMap = new HashMap();
65   
66       public BeanMap(Object object)
67               throws IntrospectionException
68       {
69           bean = object;
70           info = Introspector.getBeanInfo(object.getClass());
71   
72           PropertyDescriptor[] pd = info.getPropertyDescriptors();
73   
74           for (int i = 0; i < pd.length; i++)
75           {
76               pdMap.put(pd[i].getName(), pd[i]);
77           }
78       }
79   
80       private PropertyDescriptor getPropertyDescriptor(String key)
81       {
82           return (PropertyDescriptor) pdMap.get(key);
83       }
84   
85       public Object put(Object key, Object value)
86       {
87           Object prev = null;
88           PropertyDescriptor pd = (PropertyDescriptor) pdMap.get(key);
89   
90           log.debug("put() pd = " + pd);
91           log.debug("put() key = " + key);
92           log.debug("put() value = " + value);
93   
94           if (pd != null)
95           {
96               Class clazz = pd.getPropertyType();
97   
98               if (!clazz.getName().equals(value.getClass().getName()))
99               {
100                  value = coerceValue(value, clazz);
101              }
102  
103              Method writer = pd.getWriteMethod();
104  
105              log.debug("put() writer = " + writer);
106              prev = get(key);
107  
108              if (writer != null)
109              {
110                  try
111                  {
112                      writer.invoke(bean, new Object[] { value });
113                  }
114                  catch (Exception ex)
115                  {
116                      ex.printStackTrace();
117                  }
118              }
119          }
120  
121          return prev;
122      }
123  
124      public Object get(Object key)
125      {
126          Object result = null;
127          PropertyDescriptor pd = (PropertyDescriptor) pdMap.get(key);
128  
129          if (pd != null)
130          {
131              Method reader = pd.getReadMethod();
132  
133              if (reader != null)
134              {
135                  try
136                  {
137                      result = reader.invoke(bean, new Object[] {  });
138                  }
139                  catch (Exception ex)
140                  {
141                  }
142              }
143          }
144  
145          return result;
146      }
147  
148      public boolean containsKey(Object key)
149      {
150          return (pdMap.get(key) != null);
151      }
152  
153      public boolean containsValue(Object o)
154      {
155          return false;
156      }
157  
158      public void clear()
159      {
160      }
161  
162      public Object remove(Object key)
163      {
164          return null;
165      }
166  
167      public Collection values()
168      {
169          return getAll().values();
170      }
171  
172      public Set keySet()
173      {
174          return pdMap.keySet();
175      }
176  
177      public Set entrySet()
178      {
179          HashSet set = new HashSet();
180          PropertyDescriptor[] pd = info.getPropertyDescriptors();
181  
182          for (int i = 0; i < pd.length; i++)
183          {
184              set.add(new BeanMapEntry(pd[i].getName()));
185          }
186  
187          return set;
188      }
189  
190      public boolean equals(Object o)
191      {
192          return (o instanceof BeanMap && ((BeanMap) o).bean.equals(this.bean));
193      }
194  
195      public int size()
196      {
197          return info.getPropertyDescriptors().length;
198      }
199  
200      public Map getAll()
201      {
202          return new HashMap(this);
203      }
204  
205      public void putAll(Map map)
206      {
207          Iterator keys = map.keySet().iterator();
208  
209          while (keys.hasNext())
210          {
211              Object key = keys.next();
212  
213              log.debug("putAll() key = " + key);
214              put(key, map.get(key));
215          }
216      }
217  
218      public boolean isEmpty()
219      {
220          return false;
221      }
222  
223      private Object coerceValue(Object value, Class targetClazz)
224      {
225          if (value instanceof String)
226          {
227              String string = (String) value;
228              String targetClassName = targetClazz.getName();
229  
230              if (targetClassName.equals("int")
231                  || targetClassName.equals("java.lang.Integer"))
232              {
233                  value = new Integer(string);
234              }
235              else if (targetClassName.equals("long")
236                       || targetClassName.equals("java.lang.Long"))
237              {
238                  value = new Long(string);
239              }
240              else if (targetClassName.equals("float")
241                       || targetClassName.equals("java.lang.Float"))
242              {
243                  value = new Float(string);
244              }
245              else if (targetClassName.equals("double")
246                       || targetClassName.equals("java.lang.Double"))
247              {
248                  value = new Double(string);
249              }
250              else if (targetClassName.equals("short")
251                       || targetClassName.equals("java.lang.Short"))
252              {
253                  value = new Short(string);
254              }
255              else if (targetClassName.equals("char")
256                       || targetClassName.equals("java.lang.Character"))
257              {
258                  value = new Character(string.charAt(0));
259              }
260              else if (targetClassName.equals("byte")
261                       || targetClassName.equals("java.lang.byte"))
262              {
263                  value = new Byte(string);
264              }
265          }
266  
267          return value;
268      }
269  
270      public class BeanMapEntry
271          implements Map.Entry
272      {
273          Object key;
274  
275          public BeanMapEntry(Object key)
276          {
277              this.key = key;
278          }
279  
280          public boolean equals(Object o)
281          {
282              return (o instanceof Map.Entry
283                     && key.equals(((Map.Entry) o).getKey())
284                     && getValue().equals(((Map.Entry) o).getValue()));
285          }
286  
287          public int hashCode()
288          {
289              return key.hashCode() ^ getValue().hashCode();
290          }
291  
292          public Object getKey()
293          {
294              return key;
295          }
296  
297          public Object getValue()
298          {
299              return get(key);
300          }
301  
302          public Object setValue(Object value)
303          {
304              return put(key, value);
305          }
306      }
307  }