This is a straight-forward code post.
public class BeanMap<T> extends AbstractMap<String, Object> { private final T bean; private final Set<PropertyDescriptor> descriptors; public BeanMap(final T bean) throws IntrospectionException { this.bean = asNotNull(bean, "Missing bean"); final Set<PropertyDescriptor> descriptors = new HashSet<PropertyDescriptor>(); for (final PropertyDescriptor descriptor : getBeanInfo(bean.getClass()).getPropertyDescriptors()) // Only support simple setter/getters. if (!(descriptor instanceof IndexedPropertyDescriptor)) descriptors.add(descriptor); this.descriptors = unmodifiableSet(descriptors); } public Set<Entry<String, Object>> entrySet() { return new BeanSet(); } @Override public Object get(final Object key) { return super.get(checkKey(key)); } @Override public Object put(final String key, final Object value) { checkKey(key); for (final Entry<String, Object> entry : entrySet()) if (entry.getKey().equals(key)) return entry.setValue(value); return null; } @Override public Object remove(final Object key) { return super.remove(checkKey(key)); } private String checkKey(final Object key) { // NB - the cast forces CCE if key is the wrong type. final String name = (String) key; if (!containsKey(asNotNull(name, "Missing key"))) throw new IllegalArgumentException("Bad key: " + key); return name; } private class BeanSet extends AbstractSet<Entry<String, Object>> { public Iterator<Entry<String, Object>> iterator() { return new BeanIterator(descriptors.iterator()); } public int size() { return descriptors.size(); } } private class BeanIterator implements Iterator<Entry<String, Object>> { private final Iterator<PropertyDescriptor> it; public BeanIterator(final Iterator<PropertyDescriptor> it) { this.it = it; } public boolean hasNext() { return it.hasNext(); } public Entry<String, Object> next() { return new BeanEntry(it.next()); } public void remove() { it.remove(); } } private class BeanEntry implements Entry<String, Object> { private final PropertyDescriptor descriptor; public BeanEntry(final PropertyDescriptor descriptor) { this.descriptor = descriptor; } public String getKey() { return descriptor.getName(); } public Object getValue() { return unwrap(new Wrapped() { public Object run() throws IllegalAccessException, InvocationTargetException { final Method method = descriptor.getReadMethod(); // A write-only bean. if (null == method) throw new UnsupportedOperationException( "No getter: " + descriptor.getName()); return method.invoke(bean); } }); } public Object setValue(final Object value) { return unwrap(new Wrapped() { public Object run() throws IllegalAccessException, InvocationTargetException { final Method method = descriptor.getWriteMethod(); // A read-only bean. if (null == method) throw new UnsupportedOperationException( "No setter: " + descriptor.getName()); final Object old = getValue(); method.invoke(bean, value); return old; } }); } } private static interface Wrapped { Object run() throws IllegalAccessException, InvocationTargetException; } private static Object unwrap(final Wrapped wrapped) { try { return wrapped.run(); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (final InvocationTargetException e) { // Javadocs for setValue indicate cast is ok. throw(RuntimeException) e.getCause(); } } }
The idea is simple: give the Java getter/setter idiom a Map
interface. Commons Beanutils already does this but with a significant difference.
My example class is very brittle. It doesn't like null
, wrong classes or missing keys. And in a brittle language like Java, this is a good thing. This kind of brittleness finds bugs quickly following the venerable fail-fast principle.
Avoid code which returns null
or silently converts wrong-class arguments. You pay now in return for a clear conscience in the long term. I have better things to do with my time than debug NullPointerException
s.
No comments:
Post a Comment