View Javadoc

1   package com.lexicalscope.fluentreflection.bean;
2   
3   import static com.google.common.collect.Sets.*;
4   import static com.lexicalscope.fluentreflection.FluentReflection.object;
5   import static java.util.Collections.unmodifiableSet;
6   
7   import java.util.AbstractSet;
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.Iterator;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import org.hamcrest.Matcher;
15  
16  import ch.lambdaj.Lambda;
17  import ch.lambdaj.function.convert.Converter;
18  
19  import com.lexicalscope.fluentreflection.FluentMember;
20  import com.lexicalscope.fluentreflection.FluentMethod;
21  import com.lexicalscope.fluentreflection.FluentObject;
22  
23  /*
24   * Copyright 2011 Tim Wood
25   *
26   * Licensed under the Apache License, Version 2.0 (the "License");
27   * you may not use this file except in compliance with the License.
28   * You may obtain a copy of the License at
29   *
30   * http://www.apache.org/licenses/LICENSE-2.0
31   *
32   * Unless required by applicable law or agreed to in writing, software
33   * distributed under the License is distributed on an "AS IS" BASIS,
34   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35   * See the License for the specific language governing permissions and
36   * limitations under the License. 
37   */
38  
39  /**
40   * Create a map by reflecting on bean properties
41   * 
42   * If you do not need the configurability of this class, consider using
43   * {@link net.sf.cglib.beans.BeanMap} instead for performance reasons.
44   * 
45   * @author Tim Wood
46   */
47  public class BeanMap {
48      private static final class BeanMapImpl implements Map<String, Object> {
49          private final Map<String, FluentMethod> getters;
50          private final Map<String, FluentMethod> setters;
51          private final Set<String> keySet;
52  
53          BeanMapImpl(
54                  final FluentObject<Object> object,
55                  final PropertyNameConvertor propertyNameConvertor,
56                  final Matcher<FluentMember> getterMatcher,
57                  final Matcher<FluentMember> setterMatcher,
58                  final KeySetCalculation keySetCalculation) {
59              this.getters = indexMethods(object, getterMatcher, propertyNameConvertor);
60              this.setters = indexMethods(object, setterMatcher, propertyNameConvertor);
61              this.keySet = unmodifiableSet(keySetCalculation.supportedKeys(getters, setters));
62          }
63  
64          private Map<String, FluentMethod> indexMethods(
65                  final FluentObject<Object> object,
66                  final Matcher<FluentMember> matcher,
67                  final PropertyNameConvertor propertyNameConvertor) {
68              return Lambda.map(
69                      object.methods(matcher),
70                      new Converter<FluentMethod, String>() {
71                          @Override public String convert(final FluentMethod from) {
72                              return propertyNameConvertor.propertyName(from);
73                          }
74                      });
75          }
76  
77          @Override public void clear() {
78              throw new UnsupportedOperationException("clear is not supported on " + BeanMap.class.getSimpleName());
79          }
80  
81          @Override public boolean containsKey(final Object key) {
82              return keySet.contains(key);
83          }
84  
85          @Override public boolean containsValue(final Object value) {
86              for (final String string : keySet) {
87                  final Object containedValue = get(string);
88                  if (containedValue == value) {
89                      return true;
90                  } else if (value != null && value.equals(containedValue)) {
91                      return true;
92                  }
93              }
94              return false;
95          }
96  
97          @Override public Set<Entry<String, Object>> entrySet() {
98              return new BeanMapEntrySet();
99          }
100 
101         @Override public Object get(final Object key) {
102             final FluentMethod getter = getters.get(key);
103             if (getter == null) {
104                 return null;
105             }
106             return getter.callRaw();
107         }
108 
109         @Override public boolean isEmpty() {
110             return keySet.isEmpty();
111         }
112 
113         @Override public Set<String> keySet() {
114             return keySet;
115         }
116 
117         @Override public Object put(final String key, final Object value) {
118             if (!keySet.contains(key)) {
119                 throw new IllegalArgumentException("map does not allow key: " + key);
120             }
121 
122             final Object oldValue = get(key);
123             final FluentMethod setter = setters.get(key);
124             if (setter != null) {
125                 setter.callRaw(value);
126             }
127             return oldValue;
128         }
129 
130         @Override public void putAll(final Map<? extends String, ? extends Object> m) {
131             for (final Entry<? extends String, ? extends Object> entry : m.entrySet()) {
132                 put(entry.getKey(), entry.getValue());
133             }
134         }
135 
136         @Override public Object remove(final Object key) {
137             throw new UnsupportedOperationException("remove is not supported on " + BeanMap.class.getSimpleName());
138         }
139 
140         @Override public int size() {
141             return keySet.size();
142         }
143 
144         @Override public Collection<Object> values() {
145             final Collection<Object> result = new ArrayList<Object>(keySet.size());
146             for (final String key : keySet) {
147                 result.add(get(key));
148             }
149             return result;
150         }
151 
152         private final class BeanMapEntrySet extends AbstractSet<Entry<String, Object>> {
153             @Override public Iterator<Entry<String, Object>> iterator() {
154                 return new Iterator<Entry<String, Object>>() {
155                     private final Iterator<String> keyIterator = keySet.iterator();
156 
157                     @Override public boolean hasNext() {
158                         return keyIterator.hasNext();
159                     }
160 
161                     @Override public Entry<String, Object> next() {
162                         return new Entry<String, Object>() {
163                             private final String key = keyIterator.next();
164                             private final Object value = get(key);
165 
166                             @Override public Object setValue(final Object value) {
167                                 return put(key, value);
168                             }
169 
170                             @Override public Object getValue() {
171                                 return value;
172                             }
173 
174                             @Override public String getKey() {
175                                 return key;
176                             }
177 
178                             @Override public boolean equals(final Object that) {
179                                 if (that != null && that.getClass().equals(this.getClass())) {
180                                     final Entry<?, ?> thatEntry = (Entry<?, ?>) that;
181 
182                                     return (key == null ? thatEntry.getKey() == null : key.equals(thatEntry.getKey()))
183                                             &&
184                                             (value == null ? thatEntry.getValue() == null : value.equals(thatEntry
185                                                     .getValue()));
186                                 }
187                                 return false;
188                             }
189 
190                             @Override public int hashCode() {
191                                 return key.hashCode() ^ (value == null ? 0 : value.hashCode());
192                             }
193 
194                             @Override public String toString() {
195                                 return key + "=" + value;
196                             }
197                         };
198                     }
199 
200                     @Override public void remove() {
201                         throw new UnsupportedOperationException("remove is not supported on "
202                                 + BeanMap.class.getSimpleName());
203                     }
204                 };
205             }
206 
207             @Override public int size() {
208                 return keySet.size();
209             }
210         }
211     }
212 
213     public static interface KeySetCalculation {
214         Set<String> supportedKeys(Map<String, FluentMethod> getters, Map<String, FluentMethod> setters);
215     }
216 
217     public static interface PropertyNameConvertor {
218         String propertyName(FluentMethod method);
219     }
220 
221     /**
222      * A map of the properties in the bean. Putting values into the map will
223      * update the underlying bean. Getting write only properties will return
224      * null. Setting read only properties is ignored.
225      * 
226      * Removing keys from the map (or any operation that implies removing one or
227      * more keys) is not supported.
228      * 
229      * @param bean
230      *            the bean to expose as a map
231      * 
232      * @return the bean wrapped in a map
233      */
234     public static Map<String, Object> map(final Object bean) {
235         return beanMap().build(bean);
236     }
237 
238     public static BeanMapBuilder beanMap() {
239         return new BeanMapBuilderImpl();
240     }
241 
242     static Map<String, Object> map(
243             final Object bean,
244             final PropertyNameConvertor propertyNameConvertor,
245             final Matcher<FluentMember> getterMatcher,
246             final Matcher<FluentMember> setterMatcher,
247             final KeySetCalculation keySetCalculation) {
248         return new BeanMapImpl(
249                 object(bean),
250                 propertyNameConvertor,
251                 getterMatcher,
252                 setterMatcher,
253                 keySetCalculation);
254     }
255 
256     public static KeySetCalculation onlyReadWriteProperties() {
257         return new KeySetCalculation() {
258             @Override public Set<String> supportedKeys(
259                         final Map<String, FluentMethod> getters,
260                         final Map<String, FluentMethod> setters) {
261                 return intersection(getters.keySet(), setters.keySet());
262             }
263         };
264     }
265 
266     public static KeySetCalculation allReadableProperties() {
267         return new KeySetCalculation() {
268             @Override public Set<String> supportedKeys(
269                         final Map<String, FluentMethod> getters,
270                         final Map<String, FluentMethod> setters) {
271                 return getters.keySet();
272             }
273         };
274     }
275 
276     public static KeySetCalculation allWriteableProperties() {
277         return new KeySetCalculation() {
278             @Override public Set<String> supportedKeys(
279                         final Map<String, FluentMethod> getters,
280                         final Map<String, FluentMethod> setters) {
281                 return setters.keySet();
282             }
283         };
284     }
285 
286     public static KeySetCalculation writeablePropertiesOnly() {
287         return new KeySetCalculation() {
288             @Override public Set<String> supportedKeys(
289                         final Map<String, FluentMethod> getters,
290                         final Map<String, FluentMethod> setters) {
291                 return difference(setters.keySet(), getters.keySet());
292             }
293         };
294     }
295 
296     public static KeySetCalculation readablePropertiesOnly() {
297         return new KeySetCalculation() {
298             @Override public Set<String> supportedKeys(
299                         final Map<String, FluentMethod> getters,
300                         final Map<String, FluentMethod> setters) {
301                 return difference(getters.keySet(), setters.keySet());
302             }
303         };
304     }
305 
306     public static KeySetCalculation allProperties() {
307         return new KeySetCalculation() {
308             @Override public Set<String> supportedKeys(
309                         final Map<String, FluentMethod> getters,
310                         final Map<String, FluentMethod> setters) {
311                 return union(getters.keySet(), setters.keySet());
312             }
313         };
314     }
315 
316     public static PropertyNameConvertor lowercasePropertyName() {
317         return new PropertyNameConvertor() {
318             @Override public String propertyName(final FluentMethod method) {
319                 return method.property().toLowerCase();
320             }
321         };
322     }
323 }