All Downloads are FREE. Search and download functionalities are using the official Maven repository.

liquibase.AbstractExtensibleObject Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase;

import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.util.ObjectUtil;
import liquibase.util.SmartMap;
import liquibase.util.StringUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Convenience class implementing ExtensibleObject. It is usually easiest to extend this class rather than implement all of ExtensibleObject yourself.
 */
public class AbstractExtensibleObject implements ExtensibleObject {

    /**
     * Additional non-standard attributes.
     */
    private final SmartMap attributes = new SmartMap();

    /**
     * Cache of fields on this object. Lazy loaded in {@link #getAttributeFields()}
     */
    private static final Map> attributeFieldCache = new HashMap<>();

    public AbstractExtensibleObject() {
    }

    /**
     * Creates a new object with the given attributes.
     */
    public AbstractExtensibleObject(Map values) {
        for (Map.Entry entry : values.entrySet()) {
            this.set(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public SortedSet getAttributes() {
        SortedSet returnSet = new TreeSet<>(attributes.keySet());
        for (String field : getAttributeFields().keySet()) {
            if (has(field)) {
                returnSet.add(field);
            }
        }
        return Collections.unmodifiableSortedSet(returnSet);
    }

    @Override
    public ObjectMetaData getObjectMetaData() {
        ObjectMetaData metaData = new ObjectMetaData();

        for (Field field : getAttributeFields().values()) {
            ObjectMetaData.Attribute attribute = new ObjectMetaData.Attribute(field.getName());
            attribute.type = field.getGenericType();

            ExtensibleObjectAttribute annotation = field.getAnnotation(ExtensibleObjectAttribute.class);
            if (annotation != null) {
                attribute.description = annotation.description();
                attribute.required = annotation.required();
            }

            metaData.attributes.add(attribute);
        }
        return metaData;
    }


    /**
     * Return true if the given key is defined.
     */
    @Override
    public boolean has(String key) {
        return get(key, Object.class) != null;
    }

    @Override
    public  T get(String attribute, Class type) {
        return get(attribute, null, type);
    }

    @Override
    public  T get(String attribute, T defaultValue) {
        Class type = (Class) Object.class;
        if (defaultValue != null) {
            type = (Class) defaultValue.getClass();
        }
        return get(attribute, defaultValue, type);
    }

    private Map getAttributeFields() {
        Map fields = attributeFieldCache.get(this.getClass());
        if (fields == null) {
            fields = new HashMap<>();
            for (Field field : this.getClass().getFields()) {
                int modifiers = field.getModifiers();
                if (Modifier.isPublic(modifiers) && !field.isSynthetic() && !Modifier.isFinal(modifiers) && !Modifier.isStatic(modifiers)) {
                    fields.put(field.getName(), field);
                }
            }
            attributeFieldCache.put(this.getClass(), fields);
        }
        return fields;
    }

    protected  T get(String attribute, T defaultValue, Class type) {
        Object value;
        if (attribute.contains(".")) {
            List path = getValuePath(attribute, type);
            value = path.get(path.size() - 1);
        } else {
            value = getFieldValue(attribute, type);
        }

        if (value == null) {
            return defaultValue;
        } else {
            return (T) value;
        }
    }

    @Override
    public List getValuePath(String attributes, Class lastType) {
        List path = new ArrayList();

        String baseField;
        String remainingAttribute = null;
        int separatorIndex = attributes.indexOf('.');
        if (separatorIndex < 0) {
            baseField = attributes;
        } else {
            baseField = attributes.substring(0, separatorIndex);
            remainingAttribute = attributes.substring(separatorIndex + 1);
        }

        Object lastValue = this;

        while (baseField != null) {
            boolean isLastField = remainingAttribute == null;

            Object newValue;
            Class typeToGet = isLastField ? lastType : Object.class;

            if (lastValue == null) {
                newValue = null;
            } else if (lastValue instanceof ExtensibleObject) {
                newValue = ((ExtensibleObject) lastValue).get(baseField, typeToGet);
            } else if (lastValue instanceof Collection) {
                newValue = new ArrayList<>();
                boolean foundNonNullValue = false;
                for (Object object : (Collection) lastValue) {
                    if (object == null) {
                        ((Collection) newValue).add(null);
                    } else if (object instanceof ExtensibleObject) {
                        ((Collection) newValue).add(((ExtensibleObject) object).get(baseField, typeToGet));
                        foundNonNullValue = true;
                    } else {
                        throw new UnexpectedLiquibaseException("Cannot traverse field(s) " + baseField + " on a " + object.getClass().getName());
                    }
                }
                if (!foundNonNullValue) {
                    newValue = null;
                }
            } else {
                throw new UnexpectedLiquibaseException("Cannot traverse field(s) " + baseField + " on a " + lastValue.getClass().getName());
            }

            if (newValue instanceof Collection) {
                List flattenedCollection = new ArrayList();
                for (Object obj : (Collection) newValue) {
                    if (obj instanceof Collection) {
                        flattenedCollection.addAll((Collection) obj);
                    } else {
                        flattenedCollection.add(obj);
                    }
                }
                newValue = flattenedCollection;
            }

            path.add(newValue);
            lastValue = newValue;


            if (remainingAttribute == null) {
                baseField = null;
            } else {
                separatorIndex = remainingAttribute.indexOf('.');
                if (separatorIndex < 0) {
                    baseField = remainingAttribute;
                    remainingAttribute = null;
                } else {
                    baseField = remainingAttribute.substring(0, separatorIndex);
                    remainingAttribute = remainingAttribute.substring(separatorIndex + 1);
                }
            }
        }


        return path;
    }

    protected Object getFieldValue(String attribute, Class type) {
        Object value;

        Field field = getAttributeFields().get(attribute);
        if (field == null) {
            value = attributes.get(attribute, type);
        } else {
            try {
                value = ObjectUtil.convert(field.get(this), type);
            } catch (IllegalAccessException e) {
                throw new UnexpectedLiquibaseException(e);
            }
        }
        return value;
    }

    @Override
    public ExtensibleObject set(String attribute, Object value) {
        Field field = getAttributeFields().get(attribute);
        if (field == null) {
            attributes.set(attribute, value);
        } else {
            try {
                field.set(this, ObjectUtil.convert(value, field.getType()));
            } catch (Exception e) {
                throw new UnexpectedLiquibaseException("Error setting " + getClass().getName() + "." + attribute, e);
            }
        }

        return this;
    }

    @Override
    public String describe() {
        String name = getClass().getSimpleName();
        return name + "{" + StringUtil.join(this, ", ", new StringUtil.DefaultFormatter()) + "}";
    }

    @Override
    public String toString() {
        return describe();
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    /**
     * Default implementation counts objects equal if their describe() methods return equal strings.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        return obj instanceof AbstractExtensibleObject && this.describe().equals(((AbstractExtensibleObject) obj).describe());
    }

    @Override
    public Object clone() {
        try {
            AbstractExtensibleObject clone = (AbstractExtensibleObject) super.clone();
            for (String attr : getAttributes()) {
                Object value = this.get(attr, Object.class);
                if (value instanceof Collection) {
                    try {
                        Collection valueClone = (Collection) value.getClass().getConstructor().newInstance();
                        valueClone.addAll(((Collection) value));
                        value = valueClone;
                    } catch (Exception e) {
                        //keep original object
                    }
                } else if (value instanceof Map) {
                    try {
                        Map valueClone = (Map) value.getClass().getConstructor().newInstance();
                        valueClone.putAll((Map) value);
                        value = valueClone;
                    } catch (Exception e) {
                        //keep original object
                    }
                } else if (value instanceof AbstractExtensibleObject) {
                    value = ((AbstractExtensibleObject) value).clone();
                }
                clone.set(attr, value);
            }

            return clone;
        } catch (CloneNotSupportedException e) {
            throw new UnexpectedLiquibaseException(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy