org.deephacks.confit.model.Bean.java Source code

Java tutorial

Introduction

Here is the source code for org.deephacks.confit.model.Bean.java

Source

/**
 * 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.deephacks.confit.model;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.deephacks.confit.model.Schema.SchemaProperty;
import org.deephacks.confit.model.Schema.SchemaPropertyList;
import org.deephacks.confit.model.Schema.SchemaPropertyRef;
import org.deephacks.confit.model.Schema.SchemaPropertyRefList;
import org.deephacks.confit.model.Schema.SchemaPropertyRefMap;
import org.deephacks.confit.serialization.Conversion;
import org.deephacks.confit.serialization.UniqueIds;
import org.deephacks.confit.serialization.ValueSerialization.ValueReader;
import org.deephacks.confit.serialization.ValueSerialization.ValueWriter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import static com.google.common.base.Objects.equal;
import static org.deephacks.confit.model.Events.CFG310_CIRCULAR_REF;

/**
 * <p>
 * Bean represent an instance of a configurable bean. Every bean
 * that are to be provisioned must first have its corresponding schema
 * registered in the system.
 * </p>
 * <p>
 * Bean only serve as a simple transfer object and should not be
 * concerned about schema specific thing.  It should not try to
 * understand or validate if specific values are correct.
 * </p>
 * <p>
 * Bean only know about two kinds of values, properties and references. All
 * values are treated as a list of plain strings and do not care if certain
 * properties in reality are single valued.
 * </p>
 * @author Kristoffer Sjogren
 */
public final class Bean {
    private static final Conversion conversion = Conversion.get();
    /** lazy, only needed for binary serialization */
    private static UniqueIds ids;
    private final BeanId id;
    private final HashMap<String, List<String>> properties = new HashMap<>();
    private final HashMap<String, List<BeanId>> references = new HashMap<>();

    private Bean(BeanId id) {
        Preconditions.checkNotNull(id);
        // make a defensive copy
        if (id.isSingleton()) {
            this.id = BeanId.createSingleton(id.getSchemaName());
        } else {
            this.id = BeanId.create(id.getInstanceId(), id.getSchemaName());
        }
    }

    /**
     * Create a admin bean instance.
     *
     * @param id unique identification of the bean instance.
     * @return AdminBean
     */
    public static Bean create(final BeanId id) {
        Preconditions.checkNotNull(id);
        Bean bean = new Bean(id);
        bean.set(id.getSchema());
        return bean;
    }

    /**
     * @return unique identification of the bean instance.
     */
    public BeanId getId() {
        return id;
    }

    /**
     * Schema will only be present if the Bean was fetched or given from
     * a managed administrative context.
     *
     * @return the schema that belong to the bean instance.
     */
    public Schema getSchema() {
        return id.getSchema();
    }

    /**
     * Set the schema that define content structure and constraints of this
     * the bean instance.
     *
     * @param schema schema
     */
    public void set(final Schema schema) {
        this.id.set(schema);
    }

    /**
     * Return the list of property names which have values. Properties
     * with default values are not returned.
     *
     * @return list of property names.
     */
    public List<String> getPropertyNames() {
        ArrayList<String> names = new ArrayList<>(properties.keySet());
        Collections.sort(names);
        return names;
    }

    /**
     * Return the list of property names which are references.
     *
     * @return list of property names.
     */
    public List<String> getReferenceNames() {
        ArrayList<String> names = new ArrayList<>(references.keySet());
        Collections.sort(names);
        return names;
    }

    /**
     * Add a list of value to a property on this bean.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param values final String representations of the parameterized type of the collection
     * that conforms to its type as defined by the bean's schema.
     */
    public void addProperty(final String propertyName, final Collection<String> values) {
        Preconditions.checkNotNull(values);
        Preconditions.checkNotNull(propertyName);
        List<String> list = properties.get(propertyName);
        if (list == null) {
            properties.put(propertyName, new ArrayList<>(values));
        } else {
            list.addAll(values);
        }
    }

    /**
     * Add a value to a property on this bean.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param value final String representations of the property that conforms to
     * its type as defined by the bean's schema.
     */
    public void addProperty(final String propertyName, final String value) {
        Preconditions.checkNotNull(propertyName);
        Preconditions.checkNotNull(value);
        List<String> values = properties.get(propertyName);
        if (values == null) {
            values = new ArrayList<>();
            values.add(value);
            properties.put(propertyName, values);
        } else {
            values.add(value);
        }
    }

    /**
     * Overwrite the current values with the provided value.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param value final String representations of the property that conforms to
     * its type as defined by the bean's schema.
     */
    public void setProperty(final String propertyName, final String value) {
        Preconditions.checkNotNull(propertyName);
        if (value == null) {
            properties.put(propertyName, null);
            return;
        }
        List<String> values = new ArrayList<>();
        values.add(value);
        properties.put(propertyName, values);
    }

    /**
     * Overwrite/replace the current values with the provided values.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param values final String representations of the property that conforms to
     * its type as defined by the bean's schema.
     */
    public void setProperty(final String propertyName, final List<String> values) {
        Preconditions.checkNotNull(propertyName);
        if (values == null) {
            properties.put(propertyName, null);
            return;
        }
        properties.put(propertyName, values);
    }

    /**
     * Clear the values of a property or reference, setting it to null.
     *
     * This operation can be useful when removing properties using "merge"
     * operation.
     *
     * @param propertyName of the property as defined by the bean's schema.
     */
    public void clear(final String propertyName) {
        Preconditions.checkNotNull(propertyName);
        if (properties.containsKey(propertyName)) {
            properties.put(propertyName, null);
        } else if (references.containsKey(propertyName)) {
            references.put(propertyName, null);
        }
    }

    /**
     * Remove a property or reference as a property from this bean.
     *
     * This operation can be useful when removing properties using "set"
     * operation.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     */
    public void remove(final String propertyName) {
        Preconditions.checkNotNull(propertyName);
        if (properties.containsKey(propertyName)) {
            properties.remove(propertyName);
        } else if (references.containsKey(propertyName)) {
            references.remove(propertyName);
        }
    }

    /**
     * Get the values of a property on a bean.
     *
     * @param propertyName of the property as defined by the bean's schema.
     * @return final String representations of the property that conforms to
     * its type as defined by the bean's schema.
     */
    public List<String> getValues(final String propertyName) {
        Preconditions.checkNotNull(propertyName);
        List<String> values = properties.get(propertyName);
        if (values == null) {
            return null;
        }
        // creates a shallow defensive copy
        return new ArrayList<>(values);
    }

    /**
     * A helper method for getting the value of single valued properties. Returns
     * null if the property does not exist.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @return final String representations of the property that conforms to
     * its type as defined by the bean's schema.
     */
    public String getSingleValue(final String propertyName) {
        Preconditions.checkNotNull(propertyName);
        List<String> values = getValues(propertyName);
        if (values == null || values.size() < 1) {
            return null;
        }
        return values.get(0);
    }

    /**
     * Add a list of references to a property on this bean.
     *
     * A reference identify other beans based on schema and instance id.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param refs the reference as defined by the bean's schema.
     */
    public void addReference(final String propertyName, final Collection<BeanId> refs) {
        Preconditions.checkNotNull(refs);
        Preconditions.checkNotNull(propertyName);
        checkCircularReference(refs.toArray(new BeanId[refs.size()]));
        List<BeanId> list = references.get(propertyName);
        if (list == null) {
            list = new ArrayList<>();
            list.addAll(refs);
            references.put(propertyName, list);
        } else {
            list.addAll(refs);
        }
    }

    /**
     * Check that references does not point to self.
     * @param references from
     */
    private void checkCircularReference(final BeanId... references) {
        for (BeanId beanId : references) {
            if (getId().equals(beanId)) {
                throw CFG310_CIRCULAR_REF(getId(), getId());
            }
        }
    }

    /**
     * Add a reference to a property on this bean.
     *
     * A reference identify other beans based on schema and instance id.
     *
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param ref the reference as defined by the bean's schema.
     */
    public void addReference(final String propertyName, final BeanId ref) {
        Preconditions.checkNotNull(ref);
        Preconditions.checkNotNull(propertyName);
        checkCircularReference(ref);
        List<BeanId> list = references.get(propertyName);
        if (list == null) {
            list = new ArrayList<>();
            list.add(ref);
            references.put(propertyName, list);
        } else {
            list.add(ref);
        }
    }

    /**
     * Get the references of a property on a bean.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @return References that identify other beans.
     */
    public List<BeanId> getReference(final String propertyName) {
        List<BeanId> values = references.get(propertyName);
        if (values == null) {
            return null;
        }
        return values;
    }

    /**
     * Get all references for all properties of this bean.
     *
     * @return References that identify other beans.
     */
    public List<BeanId> getReferences() {
        if (references == null) {
            return new ArrayList<>();
        }
        ArrayList<BeanId> result = new ArrayList<>();
        for (List<BeanId> b : references.values()) {
            if (b != null) {
                result.addAll(b);
            }

        }
        return result;
    }

    /**
     * Overwrite/replace the current references with the provided reference.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param values override
     */
    public void setReferences(final String propertyName, final List<BeanId> values) {
        Preconditions.checkNotNull(propertyName);
        if (values == null || values.size() == 0) {
            references.put(propertyName, null);
            return;
        }
        checkCircularReference(values.toArray(new BeanId[values.size()]));
        references.put(propertyName, values);
    }

    /**
     * Overwrite/replace the current references with a provided reference.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @param value override
     */
    public void setReference(final String propertyName, final BeanId value) {
        Preconditions.checkNotNull(propertyName);
        if (value == null) {
            references.put(propertyName, null);
            return;
        }
        checkCircularReference(value);
        List<BeanId> values = new ArrayList<>();
        values.add(value);
        references.put(propertyName, values);
    }

    /**
     * A helper method for getting the value of single referenced property. Returns
     * null if the refrences does not exist.
     *
     * @param propertyName name of the property as defined by the bean's schema.
     * @return the value.
     */
    public BeanId getFirstReference(final String propertyName) {
        List<BeanId> refrences = getReference(propertyName);
        if (refrences == null || refrences.size() < 1) {
            return null;
        }
        return refrences.get(0);
    }

    /**
     * Clears this bean from all properties and references.
     */
    public void clear() {
        properties.clear();
        references.clear();
    }

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

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Bean)) {
            return false;
        }
        Bean other = (Bean) obj;
        return equal(getId(), other.getId());
    }

    @Override
    public final String toString() {
        return Objects.toStringHelper(Bean.class).add("id", id).add("schema", getSchema())
                .add("properties", properties).add("references", references).toString();
    }

    public static Collection<Bean> copy(Collection<Bean> beans) {
        Collection<Bean> copies = new ArrayList<>();
        for (Bean bean : beans) {
            copies.add(copy(bean));
        }
        return copies;
    }

    public static Bean copy(Bean bean) {
        if (bean == null) {
            return null;
        }
        Bean copy = Bean.create(bean.getId());
        for (String property : bean.getPropertyNames()) {
            Collection<String> values = bean.getValues(property);
            if (values == null) {
                continue;
            }
            copy.setProperty(property, bean.getValues(property));
        }
        for (String property : bean.getReferenceNames()) {
            List<BeanId> ids = bean.getReference(property);
            if (ids == null) {
                continue;
            }
            for (BeanId id : ids) {
                copy.addReference(property, BeanId.create(id.getInstanceId(), id.getSchemaName()));
            }
        }
        return copy;
    }

    public byte[] write() {
        if (ids == null) {
            ids = UniqueIds.lookup();
        }
        Schema schema = getSchema();
        Preconditions.checkNotNull(schema);
        ValueWriter writer = new ValueWriter();

        for (SchemaProperty property : schema.get(SchemaProperty.class)) {
            int propId = ids.getSchemaId(property.getFieldName());
            Object value;
            if (writer.isBasicType(property.getClassType())) {
                value = conversion.convert(getSingleValue(property.getFieldName()), property.getClassType());
            } else {
                value = getSingleValue(property.getFieldName());
            }
            writer.putValue(propId, value);
        }
        for (SchemaPropertyList property : schema.get(SchemaPropertyList.class)) {
            int propId = ids.getSchemaId(property.getFieldName());
            Collection<?> values;
            if (writer.isBasicType(property.getClassType())) {
                values = conversion.convert(getValues(property.getFieldName()), property.getClassType());
                writer.putValues(propId, values, property.getClassType());
            } else {
                values = getValues(property.getFieldName());
                writer.putValues(propId, values, String.class);
            }
        }
        for (SchemaPropertyRef property : schema.get(SchemaPropertyRef.class)) {
            int propId = ids.getSchemaId(property.getFieldName());
            BeanId beanId = getFirstReference(property.getFieldName());
            if (beanId != null) {
                writer.putValue(propId, beanId.getInstanceId());
            }
        }
        for (SchemaPropertyRefList property : schema.get(SchemaPropertyRefList.class)) {
            int propId = ids.getSchemaId(property.getFieldName());
            ArrayList<String> list = new ArrayList<>();
            List<BeanId> beanIds = getReference(property.getFieldName());
            if (beanIds != null) {
                for (BeanId id : beanIds) {
                    list.add(id.getInstanceId());
                }
                writer.putValues(propId, list, String.class);
            }
        }
        for (SchemaPropertyRefMap property : schema.get(SchemaPropertyRefMap.class)) {
            int propId = ids.getSchemaId(property.getFieldName());
            ArrayList<String> list = new ArrayList<>();
            List<BeanId> beanIds = getReference(property.getFieldName());
            if (beanIds != null) {
                for (BeanId id : beanIds) {
                    list.add(id.getInstanceId());
                }
                writer.putValues(propId, list, String.class);
            }
        }
        return writer.write();
    }

    public static Bean read(BeanId beanId, byte[] data) {
        if (ids == null) {
            ids = UniqueIds.lookup();
        }
        Preconditions.checkNotNull(beanId);
        Preconditions.checkNotNull(data);
        Schema schema = beanId.getSchema();
        Preconditions.checkNotNull(schema);

        Bean bean = Bean.create(beanId);
        ValueReader reader = new ValueReader(data);

        Multimap<String, String> properties = ArrayListMultimap.create();
        Multimap<String, BeanId> references = ArrayListMultimap.create();

        int[] propertyIds = reader.getIds();
        for (int id : propertyIds) {
            String propertyName = ids.getSchemaName(id);
            Object value = reader.getValue(id);
            if (schema.isProperty(propertyName)) {
                if (Collection.class.isAssignableFrom(value.getClass())) {
                    properties.putAll(propertyName, conversion.convert((Collection) value, String.class));
                } else {
                    properties.put(propertyName, conversion.convert(value, String.class));
                }
            } else if (schema.isReference(propertyName)) {
                String schemaName = schema.getReferenceSchemaName(propertyName);
                if (Collection.class.isAssignableFrom(value.getClass())) {
                    for (String instanceId : (Collection<String>) value) {
                        references.put(propertyName, BeanId.create(instanceId, schemaName));
                    }
                } else {
                    String instanceId = (String) value;
                    references.put(propertyName, BeanId.create(instanceId, schemaName));
                }
            } else {
                throw new IllegalArgumentException("Unrecognized property " + propertyName);
            }
        }
        for (String propertyName : properties.keySet()) {
            bean.addProperty(propertyName, properties.get(propertyName));
        }
        for (String propertyName : references.keySet()) {
            bean.addReference(propertyName, references.get(propertyName));
        }
        bean.set(schema);
        return bean;
    }

}