Coverage Report - com.lexicalscope.fluentreflection.bean.BeanMap
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanMap
93%
15/16
N/A
1.511
BeanMap$1
100%
3/3
N/A
1.511
BeanMap$2
100%
3/3
N/A
1.511
BeanMap$3
100%
3/3
N/A
1.511
BeanMap$4
100%
3/3
N/A
1.511
BeanMap$5
100%
3/3
N/A
1.511
BeanMap$6
100%
3/3
N/A
1.511
BeanMap$7
100%
3/3
N/A
1.511
BeanMap$BeanMapImpl
100%
42/42
100%
18/18
1.511
BeanMap$BeanMapImpl$1
100%
3/3
N/A
1.511
BeanMap$BeanMapImpl$BeanMapEntrySet
66%
2/3
N/A
1.511
BeanMap$BeanMapImpl$BeanMapEntrySet$1
100%
7/7
N/A
1.511
BeanMap$BeanMapImpl$BeanMapEntrySet$1$1
80%
12/15
37%
6/16
1.511
BeanMap$KeySetCalculation
N/A
N/A
1.511
BeanMap$PropertyNameConvertor
N/A
N/A
1.511
 
 1  88
 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  0
 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  48
         private final Set<String> keySet;
 52  
 
 53  368
         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  368
             this.getters = indexMethods(object, getterMatcher, propertyNameConvertor);
 60  368
             this.setters = indexMethods(object, setterMatcher, propertyNameConvertor);
 61  368
             this.keySet = unmodifiableSet(keySetCalculation.supportedKeys(getters, setters));
 62  368
         }
 63  
 
 64  
         private Map<String, FluentMethod> indexMethods(
 65  
                 final FluentObject<Object> object,
 66  
                 final Matcher<FluentMember> matcher,
 67  
                 final PropertyNameConvertor propertyNameConvertor) {
 68  736
             return Lambda.map(
 69  736
                     object.methods(matcher),
 70  736
                     new Converter<FluentMethod, String>() {
 71  
                         @Override public String convert(final FluentMethod from) {
 72  1408
                             return propertyNameConvertor.propertyName(from);
 73  
                         }
 74  
                     });
 75  
         }
 76  
 
 77  
         @Override public void clear() {
 78  8
             throw new UnsupportedOperationException("clear is not supported on " + BeanMap.class.getSimpleName());
 79  
         }
 80  
 
 81  
         @Override public boolean containsKey(final Object key) {
 82  24
             return keySet.contains(key);
 83  
         }
 84  
 
 85  
         @Override public boolean containsValue(final Object value) {
 86  136
             for (final String string : keySet) {
 87  80
                 final Object containedValue = get(string);
 88  80
                 if (containedValue == value) {
 89  8
                     return true;
 90  72
                 } else if (value != null && value.equals(containedValue)) {
 91  16
                     return true;
 92  
                 }
 93  
             }
 94  16
             return false;
 95  
         }
 96  
 
 97  
         @Override public Set<Entry<String, Object>> entrySet() {
 98  48
             return new BeanMapEntrySet();
 99  
         }
 100  
 
 101  
         @Override public Object get(final Object key) {
 102  336
             final FluentMethod getter = getters.get(key);
 103  336
             if (getter == null) {
 104  88
                 return null;
 105  
             }
 106  248
             return getter.callRaw();
 107  
         }
 108  
 
 109  
         @Override public boolean isEmpty() {
 110  16
             return keySet.isEmpty();
 111  
         }
 112  
 
 113  
         @Override public Set<String> keySet() {
 114  80
             return keySet;
 115  
         }
 116  
 
 117  
         @Override public Object put(final String key, final Object value) {
 118  128
             if (!keySet.contains(key)) {
 119  16
                 throw new IllegalArgumentException("map does not allow key: " + key);
 120  
             }
 121  
 
 122  112
             final Object oldValue = get(key);
 123  112
             final FluentMethod setter = setters.get(key);
 124  112
             if (setter != null) {
 125  88
                 setter.callRaw(value);
 126  
             }
 127  112
             return oldValue;
 128  
         }
 129  
 
 130  
         @Override public void putAll(final Map<? extends String, ? extends Object> m) {
 131  72
             for (final Entry<? extends String, ? extends Object> entry : m.entrySet()) {
 132  32
                 put(entry.getKey(), entry.getValue());
 133  
             }
 134  16
         }
 135  
 
 136  
         @Override public Object remove(final Object key) {
 137  8
             throw new UnsupportedOperationException("remove is not supported on " + BeanMap.class.getSimpleName());
 138  
         }
 139  
 
 140  
         @Override public int size() {
 141  16
             return keySet.size();
 142  
         }
 143  
 
 144  
         @Override public Collection<Object> values() {
 145  8
             final Collection<Object> result = new ArrayList<Object>(keySet.size());
 146  40
             for (final String key : keySet) {
 147  24
                 result.add(get(key));
 148  
             }
 149  8
             return result;
 150  
         }
 151  
 
 152  200
         private final class BeanMapEntrySet extends AbstractSet<Entry<String, Object>> {
 153  
             @Override public Iterator<Entry<String, Object>> iterator() {
 154  104
                 return new Iterator<Entry<String, Object>>() {
 155  96
                     private final Iterator<String> keyIterator = keySet.iterator();
 156  
 
 157  
                     @Override public boolean hasNext() {
 158  40
                         return keyIterator.hasNext();
 159  
                     }
 160  
 
 161  
                     @Override public Entry<String, Object> next() {
 162  48
                         return new Entry<String, Object>() {
 163  48
                             private final String key = keyIterator.next();
 164  48
                             private final Object value = get(key);
 165  
 
 166  
                             @Override public Object setValue(final Object value) {
 167  8
                                 return put(key, value);
 168  
                             }
 169  
 
 170  
                             @Override public Object getValue() {
 171  32
                                 return value;
 172  
                             }
 173  
 
 174  
                             @Override public String getKey() {
 175  40
                                 return key;
 176  
                             }
 177  
 
 178  
                             @Override public boolean equals(final Object that) {
 179  8
                                 if (that != null && that.getClass().equals(this.getClass())) {
 180  8
                                     final Entry<?, ?> thatEntry = (Entry<?, ?>) that;
 181  
 
 182  8
                                     return (key == null ? thatEntry.getKey() == null : key.equals(thatEntry.getKey()))
 183  
                                             &&
 184  16
                                             (value == null ? thatEntry.getValue() == null : value.equals(thatEntry
 185  8
                                                     .getValue()));
 186  
                                 }
 187  0
                                 return false;
 188  
                             }
 189  
 
 190  
                             @Override public int hashCode() {
 191  0
                                 return key.hashCode() ^ (value == null ? 0 : value.hashCode());
 192  
                             }
 193  
 
 194  
                             @Override public String toString() {
 195  0
                                 return key + "=" + value;
 196  
                             }
 197  
                         };
 198  
                     }
 199  
 
 200  
                     @Override public void remove() {
 201  32
                         throw new UnsupportedOperationException("remove is not supported on "
 202  16
                                 + BeanMap.class.getSimpleName());
 203  
                     }
 204  
                 };
 205  
             }
 206  
 
 207  
             @Override public int size() {
 208  0
                 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  312
         return beanMap().build(bean);
 236  
     }
 237  
 
 238  
     public static BeanMapBuilder beanMap() {
 239  368
         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  736
         return new BeanMapImpl(
 249  368
                 object(bean),
 250  368
                 propertyNameConvertor,
 251  368
                 getterMatcher,
 252  368
                 setterMatcher,
 253  368
                 keySetCalculation);
 254  
     }
 255  
 
 256  
     public static KeySetCalculation onlyReadWriteProperties() {
 257  8
         return new KeySetCalculation() {
 258  
             @Override public Set<String> supportedKeys(
 259  
                         final Map<String, FluentMethod> getters,
 260  
                         final Map<String, FluentMethod> setters) {
 261  8
                 return intersection(getters.keySet(), setters.keySet());
 262  
             }
 263  
         };
 264  
     }
 265  
 
 266  
     public static KeySetCalculation allReadableProperties() {
 267  8
         return new KeySetCalculation() {
 268  
             @Override public Set<String> supportedKeys(
 269  
                         final Map<String, FluentMethod> getters,
 270  
                         final Map<String, FluentMethod> setters) {
 271  8
                 return getters.keySet();
 272  
             }
 273  
         };
 274  
     }
 275  
 
 276  
     public static KeySetCalculation allWriteableProperties() {
 277  8
         return new KeySetCalculation() {
 278  
             @Override public Set<String> supportedKeys(
 279  
                         final Map<String, FluentMethod> getters,
 280  
                         final Map<String, FluentMethod> setters) {
 281  8
                 return setters.keySet();
 282  
             }
 283  
         };
 284  
     }
 285  
 
 286  
     public static KeySetCalculation writeablePropertiesOnly() {
 287  8
         return new KeySetCalculation() {
 288  
             @Override public Set<String> supportedKeys(
 289  
                         final Map<String, FluentMethod> getters,
 290  
                         final Map<String, FluentMethod> setters) {
 291  8
                 return difference(setters.keySet(), getters.keySet());
 292  
             }
 293  
         };
 294  
     }
 295  
 
 296  
     public static KeySetCalculation readablePropertiesOnly() {
 297  8
         return new KeySetCalculation() {
 298  
             @Override public Set<String> supportedKeys(
 299  
                         final Map<String, FluentMethod> getters,
 300  
                         final Map<String, FluentMethod> setters) {
 301  8
                 return difference(getters.keySet(), setters.keySet());
 302  
             }
 303  
         };
 304  
     }
 305  
 
 306  
     public static KeySetCalculation allProperties() {
 307  368
         return new KeySetCalculation() {
 308  
             @Override public Set<String> supportedKeys(
 309  
                         final Map<String, FluentMethod> getters,
 310  
                         final Map<String, FluentMethod> setters) {
 311  328
                 return union(getters.keySet(), setters.keySet());
 312  
             }
 313  
         };
 314  
     }
 315  
 
 316  
     public static PropertyNameConvertor lowercasePropertyName() {
 317  8
         return new PropertyNameConvertor() {
 318  
             @Override public String propertyName(final FluentMethod method) {
 319  32
                 return method.property().toLowerCase();
 320  
             }
 321  
         };
 322  
     }
 323  
 }