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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
223
224
225
226
227
228
229
230
231
232
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 }