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 NullPointerExceptions.
No comments:
Post a Comment