cat.albirar.framework.dynabean.impl.DynaBeanImpl.java Source code

Java tutorial

Introduction

Here is the source code for cat.albirar.framework.dynabean.impl.DynaBeanImpl.java

Source

/*
 * This file is part of "albirar framework" project.
 * 
 * "albirar framework" is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * "albirar framework" is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with calendar. If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2013 Octavi Forns octavi@fornes.cat
 */
package cat.albirar.framework.dynabean.impl;

import static cat.albirar.framework.dynabean.impl.DynaBeanImplementationUtils.isGetter;

import java.beans.PropertyEditor;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import cat.albirar.framework.dynabean.DynaBeanUtils;
import cat.albirar.framework.dynabean.IDynaBeanFactory;
import cat.albirar.framework.dynabean.visitor.IDynaBeanVisitor;
import cat.albirar.framework.patterns.ITransformerVisitor;

/**
 * A proxy for create dynamic beans from interfaces.
 * 
 * For use with interfaces that represents a Java Bean. <br>
 * <b>Use</b>
 * 
 * <pre>
 * InterfaceJavaBean a;
 * 
 * a = {@link DynaBeanUtils#instanceDefaultFactory()}.{@link IDynaBeanFactory#newDynaBean(Class) newDynaBean(InterfaceJavaBean.class)};
 * ...
 * a.setXXX("xxx");
 * </pre>
 * 
 * The proxy is a JDK Proxy framework instance.
 * 
 * @param <T> The type to implement
 * 
 * @author <a href="mailto:ofornes@albirar.cat">Octavi Forns ofornes@albirar.cat</a>
 * @since 1.0.0
 */
public class DynaBeanImpl<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 0L;

    private static final Logger logger = LoggerFactory.getLogger(DynaBeanImpl.class);

    private DynaBeanDescriptor<T> descriptor;

    private Map<String, Object> values;

    private transient IDynaBeanVisitor visitor;

    private DynaBeanImpl() {
        values = Collections.synchronizedMap(new TreeMap<String, Object>());
        visitor = null;
    }

    /**
     * Constructor with a prepared descriptor. Used to reduce memory consumption and CPU cycles.
     * 
     * @param descriptor The descriptor
     */
    DynaBeanImpl(DynaBeanDescriptor<T> descriptor) {
        this();
        this.descriptor = descriptor;
        doInstantiate();
    }

    /**
     * Clone constructor.
     * 
     * @param origin The origin for data
     */
    DynaBeanImpl(DynaBeanImpl<T> origin) {
        this();
        Assert.notNull(origin, "The 'origin' object is required!");
        this.descriptor = origin.descriptor;
        doClone(origin);
    }

    /**
     * Constructor with type to implement.
     * 
     * @param typeToImplement The interface type to implement
     * @throws IllegalArgumentException If the type is not an interface
     */
    DynaBeanImpl(IDynaBeanImplementationFactory factory, Class<T> typeToImplement) {
        this();

        descriptor = factory.getDescriptorFor(typeToImplement);
        doInstantiate();
    }

    /**
     * Check and assign the values for the properties.
     */
    private void doInstantiate() {
        // Default instantiation
        for (DynaBeanPropertyDescriptor prop : descriptor.getProperties()) {
            // Test: dynaBean; implementation; defaultValue; null (or 0 or false)
            if (prop.isDynaBean()) {
                // Instantiate by factory
                values.put(prop.propertyName, descriptor.getFactory().newDynaBean(prop.getPropertyType()));
            } else {
                values.put(prop.propertyName, nullSafeValue(generateDefaultValue(prop), prop));
            }
        }
    }

    /**
     * Do a clone instantiation.
     * @param origin The origin to get values from. If null, a call to {@link #doInstantiate()} is made.
     */
    private void doClone(DynaBeanImpl<T> origin) {
        for (DynaBeanPropertyDescriptor propDesc : descriptor.getProperties()) {
            values.put(propDesc.getPropertyName(),
                    nullSafeValue(cloneValue(propDesc, origin.values.get(propDesc.getPropertyName())), propDesc));
        }
    }

    /**
     * The implemented interface for this dynaBean.
     * 
     * @return implementedType The implemented type
     */
    public Class<T> getImplementedType() {
        return descriptor.getImplementedType();
    }

    /**
     * Visitor to call in get/set events.
     * 
     * @param visitor The visitor.
     */
    public void setVisitor(IDynaBeanVisitor visitor) {
        this.visitor = visitor;
    }

    /**
     * Visitor to call in get/set events.
     * 
     * @return The visitor.
     */
    public IDynaBeanVisitor getVisitor() {
        return visitor;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return doInvoke(method, args);
    }

    /**
     * Invocation operation.
     * 
     * @param method The invoked method
     * @param args The arguments, if any
     * @return The result. Can be the value for a property or the result of {@link #equals(Object)}, {@link #hashCode()}
     *         or {@link #toString()}
     * @throws Throwable If errors are produced on invoking
     */
    protected Object doInvoke(Method method, Object... args) throws Throwable {
        DynaBeanPropertyDescriptor propDesc;
        String name;

        name = method.getName();
        // Check if is a get/set method
        if ((propDesc = descriptor.getPropertyByMethodName(name)) != null) {
            if (isGetter(name)) {
                return doGetter(propDesc);
            }
            // is a setter
            doSetter(propDesc, args);
            return null;
        }
        // Can be hashCode, toString or equals
        if ("hashCode".equals(name)) {
            return hashCode();
        }
        if ("toString".equals(name)) {
            return toString();
        }
        if ("equals".equals(name)) {
            return equals(args[0]);
        }
        if ("clone".equals(name)) {
            return clone();
        }
        // Not support any other method call
        throw new UnsupportedOperationException("Call to '" + method.getName() + "'");
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public T clone() throws CloneNotSupportedException {
        return (T) descriptor.getFactory().cloneDynaBean(this);
    }

    /**
     * Do the getter call.
     * 
     * @param propDesc The {@link DynaBeanPropertyDescriptor} descriptor
     * @return the property value
     */
    protected Object doGetter(DynaBeanPropertyDescriptor propDesc) {
        Object v = values.get(propDesc.propertyName);
        if (visitor != null) {
            v = visitor.eventGet(propDesc.propertyName, v, propDesc.getPropertyType());
        }
        return v;
    }

    /**
     * Do the setter call.
     * 
     * @param propDesc The property descriptor
     * @param arguments The arguments
     */
    protected void doSetter(DynaBeanPropertyDescriptor propDesc, Object... arguments) {
        Object v;

        v = arguments[0];
        if (visitor != null) {
            v = visitor.eventSet(propDesc.propertyName, v, propDesc.getPropertyType());
        }
        values.put(propDesc.propertyName, nullSafeValue(v, propDesc));
    }

    /**
     * Dynamically implemented {@link Object#toString()} method. Returns a String with the pattern:
     * 
     * <pre>
     * SimpleNameOfImplementedType [ propertyName=value, ...]
     * </pre>
     * 
     * For the {@link #getImplementedType() implemented type}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return String.format(descriptor.getPatternForToString(), values.values().toArray());
    }

    /**
     * Dynamically implemented {@link Object#equals(Object)} method.
     * 
     * @param oOrigin The 'other' object
     * @return as equals specification
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public boolean equals(Object oOrigin) {
        T theOther;
        DynaBeanImpl theOtherDynaBean = null;
        Object o;

        // Check if 'other' is a null
        if (oOrigin == null) {
            return false;
        }
        if (Proxy.isProxyClass(oOrigin.getClass())) {
            o = Proxy.getInvocationHandler(oOrigin);
        } else {
            o = oOrigin;
        }
        // Check for self-equals...
        if (o == this) {
            return true;
        }
        // Check if 'the other' is a 'implementedType' type...
        if (descriptor.getImplementedType().isAssignableFrom(o.getClass()) == false) {
            // ...if not, can be a dynaBean?...
            if (DynaBeanImpl.class.isAssignableFrom(o.getClass())) {
                theOtherDynaBean = (DynaBeanImpl) o;

                // Yes, check if the implementedType is the same or derived
                if (getImplementedType().isAssignableFrom(theOtherDynaBean.getImplementedType()) == false) {
                    return false;
                }
            } else {
                // Is not implemented type nor ProxyBeanImpl...
                return false;
            }
        }
        theOther = (T) o;
        for (DynaBeanPropertyDescriptor property : descriptor.getProperties()) {
            try {
                Object value;
                if (theOtherDynaBean != null) {
                    value = theOtherDynaBean.values.get(property.propertyName);
                } else {
                    value = property.getterMethod.invoke(theOther);
                }

                if (ObjectUtils.nullSafeEquals(values.get(property.propertyName), value) == false) {
                    return false;
                }
            } catch (SecurityException | IllegalArgumentException | IllegalAccessException
                    | InvocationTargetException e) {
                throw new RuntimeException("On equals call!", e);
            }
        }
        return true;
    }

    /**
     * Generate the value for the property taking care of {@link DynaBeanPropertyDescriptor#defaultImplementation} and {@link DynaBeanPropertyDescriptor#defaultValue}.
     * @param propDesc The property descriptor
     * @return The value for that property
     */
    private Object generateDefaultValue(DynaBeanPropertyDescriptor propDesc) {
        PropertyEditor pEditor;
        IPropertyWriter writer;
        ITransformerVisitor<Object> reader;

        if (propDesc.getDefaultValue() != null) {
            if (propDesc.getPropertyItemEditor() != null) {
                pEditor = propDesc.getPropertyItemEditor();
                reader = new ObjectCopyReaderVisitor(pEditor);
            } else {
                reader = new ObjectCopyReaderVisitor();
            }
            writer = prepareWriter(propDesc, reader);
            for (String iv : propDesc.getDefaultValue()) {
                writer.visit(iv);
            }
            return writer.getReturnValue();
        }
        // No default value, return default implementation (or null if none are defined)
        return instantiateDefaultImplementation(propDesc);
    }

    /**
     * Clone or copy a property value.
     * Only applicable on clone call.
     * <ul>
     * <li>If {@code originalValue} is {@link Cloneable}, makes a clone.</li>
     * <li>If {@code originalValue} IS NOT {@link Cloneable}, search for an {@link PropertyEditor editor} and copy.</li>
     * <li>If cannot found a properly {@link PropertyEditor editor}, simply return the {@code originalValue}</li>
     * </ul>
     * @param propDesc The property descriptor
     * @param originalValue The value to clone
     * @return The cloned value
     */
    private Object cloneValue(DynaBeanPropertyDescriptor propDesc, Object originalValue) {
        Iterator<?> iterator;
        int n;
        IPropertyWriter writer;
        ITransformerVisitor<Object> reader;

        // only null?
        if (originalValue == null) {
            return null;
        }

        if (propDesc.isItemDynaBean()) {
            reader = new ObjectCopyReaderVisitor(descriptor.getFactory());
        } else {
            if (propDesc.getPropertyItemCloneMethod() != null) {
                reader = new ObjectCopyReaderVisitor(propDesc.getPropertyItemCloneMethod());
            } else {
                if (propDesc.getPropertyItemEditor() != null) {
                    reader = new ObjectCopyReaderVisitor(propDesc.getPropertyItemEditor());
                } else {
                    reader = new ObjectCopyReaderVisitor();
                }
            }
        }
        writer = prepareWriter(propDesc, reader);
        if (propDesc.isArray()) {
            for (n = 0; n < Array.getLength(originalValue); n++) {
                writer.visit(Array.get(originalValue, n));
            }
        } else {
            if (propDesc.isCollection()) {
                iterator = ((Collection<?>) originalValue).iterator();
                while (iterator.hasNext()) {
                    writer.visit(iterator.next());
                }
            } else {
                writer.visit(originalValue);
            }
        }
        return writer.getReturnValue();
    }

    /**
     * Prepare a writer for the indicated property.
     * @param propDesc The property descriptor
     * @param reader The reader
     * @return The writer
     */
    @SuppressWarnings("unchecked")
    private IPropertyWriter prepareWriter(DynaBeanPropertyDescriptor propDesc, ITransformerVisitor<Object> reader) {
        IPropertyWriter writer;
        Object di;

        // Specific writer
        if (propDesc.isArray() || propDesc.isCollection()) {
            // 1t and 2d cases
            // We use a visitor pattern to assign values
            if (propDesc.isArray()) {
                writer = new ArrayWriterVisitor(reader, propDesc.getPropertyType().getComponentType());
            } else {
                // Check if default collection is assigned
                if ((di = instantiateDefaultImplementation(propDesc)) != null) {
                    writer = new CollectionWriterVisitor(reader, (List<Object>) di);
                } else {
                    writer = new CollectionWriterVisitor(reader, new Vector<Object>());
                }
            }
        } else {
            writer = new IndividualWriterVisitor(reader);
        }
        return writer;
    }

    /**
     * Instantiate the default implementation for the property.
     * @param propDesc The property descriptor
     * @return The default instance or null if no default implementation are indicated
     * @throws IllegalArgumentException If the implementation class is cannot be instantiated
     */
    private Object instantiateDefaultImplementation(DynaBeanPropertyDescriptor propDesc) {
        String s;

        if (propDesc.getDefaultImplementation() != null) {
            try {
                return propDesc.getDefaultImplementation().newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                s = "On instantiating default implementation of type '"
                        .concat(propDesc.getDefaultImplementation().getName()).concat("' for property '")
                        .concat(propDesc.getPropertyName()).concat("' of type '")
                        .concat(propDesc.getPropertyType().getName()).concat("' at dynaBean '")
                        .concat(getImplementedType().getName()).concat("'");
                logger.error(s, e);
                throw new IllegalArgumentException(s, e);
            }
        }
        return null;
    }

    /**
     * Test if the value is for a primitive type and return an object representation with default (0) value. If value is
     * null and the type is primitive, return a representation of default value for the primitive corresponding type.
     * 
     * @param value the value, can be null
     * @param pb The property bean descriptor
     * @return The value or the default value representation for the primitive type (0)
     */
    private Object nullSafeValue(Object value, DynaBeanPropertyDescriptor pb) {
        if (!pb.isPrimitive()) {
            return value;
        }
        if (pb.getPropertyType().getName().equals("byte")) {
            return (value == null ? Byte.valueOf((byte) 0) : (Byte) value);
        }
        if (pb.getPropertyType().getName().equals("short")) {
            return (value == null ? Short.valueOf((short) 0) : (Short) value);
        }
        if (pb.getPropertyType().getName().equals("int")) {
            return (value == null ? Integer.valueOf(0) : (Integer) value);
        }
        if (pb.getPropertyType().getName().equals("long")) {
            return (value == null ? Long.valueOf(0L) : (Long) value);
        }
        if (pb.getPropertyType().getName().equals("float")) {
            return (value == null ? Float.valueOf(0F) : (Float) value);
        }
        if (pb.getPropertyType().getName().equals("double")) {
            return (value == null ? Double.valueOf(0D) : (Double) value);
        }
        if (pb.getPropertyType().getName().equals("char")) {
            return (value == null ? Character.valueOf('\u0000') : (Character) value);
        }
        return (value == null ? Boolean.FALSE : (Boolean) value);
    }

    /**
     * Dynamically implemented {@link Object#hashCode()} method.
     * 
     * @return The calculated hashCode
     * @see ObjectUtils#nullSafeHashCode(Object[])
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        List<Object> vals;

        vals = new Vector<Object>();
        for (Entry<String, Object> e : values.entrySet()) {
            vals.add(e.getValue());
        }
        return ObjectUtils.nullSafeHashCode(vals.toArray());
    }
}