org.fhcrc.cpl.toolbox.datastructure.BoundMap.java Source code

Java tutorial

Introduction

Here is the source code for org.fhcrc.cpl.toolbox.datastructure.BoundMap.java

Source

/*
 * Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fhcrc.cpl.toolbox.datastructure;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.log4j.Logger;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.io.Serializable;
import java.io.IOException;

public class BoundMap extends AbstractMap<String, Object> implements Serializable {
    static final HashMap<Class, HashMap<String, BoundProperty>> _savedPropertyMaps = new HashMap<Class, HashMap<String, BoundProperty>>();

    protected Object _bean;
    transient protected HashMap<String, Object> _map = new HashMap<String, Object>();
    transient protected HashMap<String, BoundProperty> _properties;
    transient private Object _keyDebug = null;

    static class BoundProperty {
        BoundProperty(Method get, Method set, Class type) {
            _getter = get;
            _setter = set;
            _type = type;
        }

        Method _getter;
        Method _setter;
        Class _type;
    }

    public BoundMap(Object bean) {
        _bean = bean;
        initialize(_bean.getClass());
    }

    public BoundMap() {
    }

    public void setBean(Object bean) {
        _bean = bean;
        initialize(_bean.getClass());
    }

    public Object getBean() {
        return _bean;
    }

    /** Magic method for deserialization */
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        initialize(_bean.getClass());
    }

    @SuppressWarnings({ "CloneDoesntCallSuperClone" })
    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    @Override
    public Object get(Object key) {
        String sKey = (String) key;
        try {
            BoundProperty bound = getBoundProperty(sKey);
            if (null != bound) {
                if (null == bound._getter)
                    throw new IllegalArgumentException("Can not get property " + sKey);
                return bound._getter.invoke(_bean);
            }
        } catch (IllegalAccessException x) {
            throw new RuntimeException(x);
        } catch (InvocationTargetException x) {
            throw new RuntimeException(x);
        }
        return _map.get(sKey);
    }

    @Override
    public Object put(String key, Object value) {
        try {
            BoundProperty bound = getBoundProperty(key);
            if (null != bound) {
                Object previous = null;
                if (null == bound._setter)
                    throw new IllegalArgumentException("Can not set property " + key);
                if (null != value && !bound._type.isAssignableFrom(value.getClass()))
                    value = ConvertUtils.convert(value.toString(), bound._type);
                if (null != bound._getter)
                    previous = bound._getter.invoke(_bean);
                if (value != previous) {
                    assert key != _keyDebug : "infinite recursion???";
                    //noinspection ConstantConditions
                    assert null != (_keyDebug = key);
                    bound._setter.invoke(_bean, value);
                }
                return previous;
            }
        } catch (IllegalAccessException x) {
            throw new RuntimeException(x);
        } catch (InvocationTargetException x) {
            throw new RuntimeException(x);
        } finally {
            //noinspection ConstantConditions
            assert null == (_keyDebug = null);
        }
        return _map.put(key, value);
    }

    @Override
    public Set<Map.Entry<String, Object>> entrySet() {
        Set<String> keys = keySet();
        Set<Map.Entry<String, Object>> entries = new HashSet<Map.Entry<String, Object>>();
        for (String key : keys) {
            entries.add(new Entry(key));
        }
        return Collections.unmodifiableSet(entries);
    }

    private class Entry implements Map.Entry<String, Object> {
        String key;

        Entry(String key) {
            this.key = key;
        }

        public String getKey() {
            return key;
        }

        public Object getValue() {
            return get(key);
        }

        public Object setValue(Object v) {
            return put(key, v);
        }
    }

    private String convertToPropertyName(String name) {
        if (1 == name.length())
            return name.toLowerCase();

        if (Character.isUpperCase(name.charAt(0)) && !Character.isUpperCase(name.charAt(1)))
            return Character.toLowerCase(name.charAt(0)) + name.substring(1);
        else
            return name;
    }

    private BoundProperty getBoundProperty(String key) {
        BoundProperty bound = _properties.get(key);
        if (null == bound && Character.isUpperCase(key.charAt(0)))
            bound = _properties.get(convertToPropertyName(key));

        return bound;
    }

    @Override
    public Set<String> keySet() {
        Set<String> keys = new HashSet<String>();
        Set<String> mapKeys = _map.keySet();
        keys.addAll(mapKeys);

        Set<String> propKeys = _properties.keySet();
        keys.addAll(propKeys);

        return Collections.unmodifiableSet(keys);
    }

    @Override
    public int size() {
        return _map.size() + _properties.size();
    }

    @Override
    public boolean containsKey(Object key) {
        String sKey = (String) key;
        if (null != _properties.get(sKey))
            return true;
        return _map.containsKey(sKey);
    }

    @Override
    public Object remove(Object key) {
        String sKey = (String) key;
        if (null != _properties.get(sKey))
            throw new UnsupportedOperationException("can't remove property " + key);
        return _map.remove(sKey);
    }

    public Map getExtendedProperties() {
        return _map;
    }

    private void initialize(Class beanClass) {
        synchronized (_savedPropertyMaps) {
            HashMap<String, BoundProperty> props = _savedPropertyMaps.get(beanClass);
            if (props == null) {
                try {
                    props = new HashMap<String, BoundProperty>();
                    BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
                    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                    if (propertyDescriptors != null) {
                        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                            if (propertyDescriptor != null) {
                                String name = propertyDescriptor.getName();
                                if ("class".equals(name))
                                    continue;
                                Method readMethod = propertyDescriptor.getReadMethod();
                                Method writeMethod = propertyDescriptor.getWriteMethod();
                                Class aType = propertyDescriptor.getPropertyType();
                                props.put(name, new BoundProperty(readMethod, writeMethod, aType));
                            }
                        }
                    }
                } catch (IntrospectionException e) {
                    Logger.getLogger(this.getClass()).error("error creating BoundMap", e);
                    throw new RuntimeException(e);
                }
                _savedPropertyMaps.put(beanClass, props);
            }
            _properties = props;
        }
    }

    public static class TestBean {
        private int i;
        private Integer j;
        private String s;

        public int getI() {
            return i;
        }

        public void setI(int i) {
            this.i = i;
        }

        public Integer getJ() {
            return j;
        }

        public void setJ(Integer j) {
            this.j = j;
        }

        public String getS() {
            return s;
        }

        public void setS(String s) {
            this.s = s;
        }
    }

    public static void main(String[] args) {
        TestBean bean = new TestBean();
        Map<String, Object> m = new BoundMap(bean);

        System.out.println("i=" + m.get("i"));
        System.out.println("j=" + m.get("j"));
        System.out.println("s=" + m.get("s"));

        bean.i = 1;
        bean.j = 2;
        bean.s = "fred";

        System.out.println("i=" + m.get("i"));
        System.out.println("j=" + m.get("j"));
        System.out.println("s=" + m.get("s"));

        m.put("i", "3");
        m.put("j", 4);
        m.put("s", "velma");
        m.put("t", "shaggy");

        System.out.println("i=" + bean.i);
        System.out.println("j=" + bean.j);
        System.out.println("s=" + bean.s);
        System.out.println("t=" + m.get("t"));
    }
}