com.conwet.silbops.model.Subscription.java Source code

Java tutorial

Introduction

Here is the source code for com.conwet.silbops.model.Subscription.java

Source

package com.conwet.silbops.model;

/*
 * #%L
 * SilboPS API
 * %%
 * Copyright (C) 2011 - 2014 CoNWeT Lab., Universidad Politcnica de Madrid
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import static com.conwet.silbops.model.basic.Operator.CONTAINS;
import static com.conwet.silbops.model.basic.Operator.ENDS_WITH;
import static com.conwet.silbops.model.basic.Operator.EQ;
import static com.conwet.silbops.model.basic.Operator.GE;
import static com.conwet.silbops.model.basic.Operator.GT;
import static com.conwet.silbops.model.basic.Operator.LE;
import static com.conwet.silbops.model.basic.Operator.LT;
import static com.conwet.silbops.model.basic.Operator.NE;
import static com.conwet.silbops.model.basic.Operator.STARTS_WITH;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import com.conwet.silbops.model.basic.Attribute;
import com.conwet.silbops.model.basic.Type;
import com.conwet.silbops.model.basic.Value;
import com.conwet.silbops.util.JSONizable;
import com.conwet.silbops.util.externalizer.AttributeExternalizer;
import com.conwet.silbops.util.externalizer.ConstraintExternalizer;
import com.conwet.silbops.util.externalizer.Externalizer;
import com.conwet.silbops.util.externalizer.ValueExternalizer;

/**
 * Subscription subscription, represents a {@link Set} of {@link Constraint}
 *
 * @author sergio
 * @apiviz.owns com.conwet.silbops.model.Constraint
 */
public class Subscription implements JSONizable, Externalizable, IterableAttribute<Constraint> {

    private static final long serialVersionUID = 1L;
    private static final Externalizer<Attribute> attributeExt;
    private static final ConstraintExternalizer constraintExt;

    static {
        attributeExt = new AttributeExternalizer();
        constraintExt = new ConstraintExternalizer(new ValueExternalizer());
    }

    /**
     * ID representing a subscriber.
     */
    private String id;

    /**
     * Set of constraints grouped by the attribute they refer to.
     */
    private Map<Attribute, Set<Constraint>> constraints;

    /**
     * Context function to use when matching context.
     */
    private ContextFunction contextFunction;

    /**
     * Constructs a new empty subscription.
     * <pre>
     * {@code
     * Subscription emptySubscription = new Subscription();
     * }</pre> <p>Subscription can be further specified by using chained calls to
     * <code>constrain(String)</code> and
     * <code>subscription()</code>. Methods called between constrain and subscription refer
     * to the attribute specified by the former and can require existence, to be
     * greater than a number, having a startsWith and so on. </p>
     *
     * <p>For instance, to require the existence of "attr1":</p>
     * <pre>
     * {@code
     * Subscription attr1ExistsSubscription = new Subscription()
     *      .constrain("attr1", Type.DOUBLE).exists()
     *      .subscription();
     * }</pre> <p>Moreover, several constrain calls can be chained as in the
     * next example:</p>
     * <pre>
     * {@code
     * Subscription multiCriteriaSubscription = new Subscription()
     *      .constrain("attr1", Type.LONG).gt(10).lt(100)
     *      .constrain("attr2", Type.STRING).startsWith("The ")
     *      .subscription();
     * }</pre>
     */
    public Subscription() {

        this.id = "";
        this.constraints = new HashMap<>();
        this.contextFunction = new ContextFunction();
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }

        if (obj instanceof Subscription) {

            Subscription other = (Subscription) obj;

            return (this.id.equals(other.id) && this.contextFunction.equals(other.contextFunction)
                    && this.constraints.equals(other.constraints));
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + this.id.hashCode();
        hash = 31 * hash + this.constraints.hashCode();
        hash = 31 * hash + this.contextFunction.hashCode();
        return hash;
    }

    /**
     * Starts a chain of calls to constraint the given attribute.
     * 
     * @param name the attribute name to constrain
     * @param type the attribute type to constrain
     * @return a {@linkplain ConstraintBuilder} to build the subscription
     */
    public ConstraintBuilder constrain(String name, Type type) {

        return constrain(new Attribute(name, type));
    }

    public ConstraintBuilder constrain(Attribute attribute) {

        return new ConstraintBuilder(attribute);
    }

    /**
     * A {@linkplain Subscription} matches an advertise if its attributes are a subset
     * of advertise ones.
     * 
     * @param advertise the {@linkplain Advertise} to match
     * @return true if match the given advertise, false otherwise.
     */
    public boolean match(Advertise advertise) {

        Set<Attribute> subscriptionAttributes = new HashSet<>(constraints.keySet());
        subscriptionAttributes.removeAll(advertise.getAttributes());

        return subscriptionAttributes.isEmpty();
    }

    /**
     * List of constrained getAttributes, note that changes to this set will
     * alter the inner structure
     *
     * @return The list
     */
    @Override
    public Set<Attribute> getAttributes() {

        return constraints.keySet();
    }

    /**
     * Returns the set of attribute-constraint pairs
     *
     * @return the set of attribute-constraint pairs
     */
    @Override
    public Set<Entry<Attribute, Constraint>> entries() {

        return new AbstractSet<Entry<Attribute, Constraint>>() {

            @Override
            public Iterator<Entry<Attribute, Constraint>> iterator() {

                return new Iterator<Entry<Attribute, Constraint>>() {

                    private Iterator<Entry<Attribute, Set<Constraint>>> attrIt = constraints.entrySet().iterator();
                    private Iterator<Constraint> constrIt = null;
                    private Attribute current = null;

                    @Override
                    public boolean hasNext() {

                        return attrIt.hasNext() || (constrIt != null && constrIt.hasNext());
                    }

                    @Override
                    public Entry<Attribute, Constraint> next() {

                        if (current == null) {

                            Entry<Attribute, Set<Constraint>> entry = attrIt.next();
                            current = entry.getKey();
                            constrIt = entry.getValue().iterator();
                        }

                        final Attribute attr = current;
                        final Constraint cons = constrIt.next();

                        if (!constrIt.hasNext()) {
                            // set null to go to next attribute
                            current = null;
                        }

                        return new SimpleImmutableEntry<>(attr, cons);
                    }

                    @Override
                    public void remove() {

                        throw new UnsupportedOperationException("Read-only iterator");
                    }
                };
            }

            @Override
            public int size() {

                int size = 0;

                for (Set<Constraint> constrSet : constraints.values()) {

                    size += constrSet.size();
                }

                return size;
            }
        };
    }

    /**
     * @return the size of the subscription: it is the sum of the dimensions of each
     * constraint.
     *
     */
    public int size() {

        return constraints.size();
    }

    public ContextFunction getContextFunction() {

        return contextFunction;
    }

    public void setContextFunction(ContextFunction contextFunction) {

        this.contextFunction = Objects.requireNonNull(contextFunction, "Context Function is null");
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {

        this.id = Objects.requireNonNull(id, "id is null");
    }

    @Override
    public String toString() {

        return "[id=" + id + ", contextFunction=" + contextFunction + ", constraints=" + constraints + "]";
    }

    @Override
    public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {

        id = (String) input.readObject();
        contextFunction = (ContextFunction) input.readObject();

        // read the map content
        int keySize = input.readInt();
        Map<Attribute, Set<Constraint>> map = new HashMap<>();

        for (int i = 0; i < keySize; i++) {

            Attribute attribute = attributeExt.readExternal(input);
            int constSize = input.readInt();
            Set<Constraint> constrSet = new HashSet<>();

            for (int j = 0; j < constSize; j++) {

                constrSet.add(constraintExt.readExternal(input, attribute.getType()));
            }

            map.put(attribute, constrSet);
        }

        constraints = map;
    }

    @Override
    public void writeExternal(ObjectOutput output) throws IOException {

        output.writeObject(id);
        output.writeObject(contextFunction);

        // write the attributes size
        output.writeInt(constraints.size());

        for (Entry<Attribute, Set<Constraint>> entry : constraints.entrySet()) {

            // write the attribute
            Attribute attribute = entry.getKey();
            attributeExt.writeExternal(output, attribute);

            // write constraints size and their values
            Set<Constraint> constrSet = entry.getValue();
            output.writeInt(constrSet.size());

            for (Constraint constraint : constrSet) {

                constraintExt.writeExternal(output, constraint);
            }
        }
    }

    /**
     * Deserializes a Subscription from its JSON representation.
     *
     * @param json JSON representation of a subscription
     * @return A new subscription
     */
    @SuppressWarnings("unchecked")
    public static Subscription fromJSON(JSONObject json) {

        Subscription subscription = new Subscription();
        subscription.setId((String) json.get("id"));
        subscription.setContextFunction(ContextFunction.fromJSON((JSONObject) json.get("contextFunction")));

        JSONObject constr = (JSONObject) json.get("constraints");

        for (Entry<String, JSONArray> entry : (Set<Entry<String, JSONArray>>) constr.entrySet()) {

            for (JSONObject jsonConstr : (List<JSONObject>) entry.getValue()) {

                subscription.addConstraint(Attribute.fromJSON(entry.getKey()), Constraint.fromJSON(jsonConstr));
            }
        }

        return subscription;
    }

    @Override
    @SuppressWarnings("unchecked")
    public JSONObject toJSON() {

        JSONObject json = new JSONObject();
        json.put("id", id);
        json.put("contextFunction", contextFunction.toJSON());
        JSONObject jsonConstr = new JSONObject();

        for (Entry<Attribute, Set<Constraint>> entry : constraints.entrySet()) {

            JSONArray jsonAttrConstraints = new JSONArray();

            for (Constraint constr : entry.getValue()) {

                jsonAttrConstraints.add(constr.toJSON());
            }

            jsonConstr.put(entry.getKey().toJSON(), jsonAttrConstraints);
        }

        json.put("constraints", jsonConstr);

        return json;
    }

    @Override
    public String toJSONString() {

        return this.toJSON().toJSONString();
    }

    /**
     * Interface for building constraints as a chain of calls.
     */
    public class ConstraintBuilder {

        /**
         * Current attribute to constrain
         */
        private Attribute attribute;

        /**
         * Starts a chain of calls to constrain an attribute.
         *
         * @param attribute Attribute to constrain
         */
        private ConstraintBuilder(Attribute attribute) {

            this.attribute = attribute;
        }

        /**
         * Finish the chain of calls
         *
         * @return the subscription including the new constraints
         */
        public Subscription subscription() {

            return Subscription.this;
        }

        /**
         * Change the attribute to constrain
         * 
         * @param name the attribute name
         * @param type the attribute type
         * @return This
         */
        public ConstraintBuilder constrain(String name, Type type) {

            return constrain(new Attribute(name, type));
        }

        /**
         * Change the attribute to constrain
         *
         * @param attribute Attribute to constrain
         * @return This
         */
        public ConstraintBuilder constrain(Attribute attribute) {

            this.attribute = Objects.requireNonNull(attribute, "Attribute is null");
            return this;
        }

        /**
         * Require the attribute to exists
         *
         * @return This
         */
        public ConstraintBuilder exists() {

            addConstraint(attribute, Constraint.EXIST);
            return this;
        }

        /**
         * Require the attribute to have a concrete value
         *
         * @param value the value to compare with
         * @return This
         */
        public ConstraintBuilder eq(Object value) {

            addConstraint(attribute, new Constraint(EQ, Value.valueOf(value)));
            return this;
        }

        /**
         * Require the attribute not to have a concrete value
         *
         * @param value the value to compare with
         * @return This
         */
        public ConstraintBuilder ne(Object value) {

            addConstraint(attribute, new Constraint(NE, Value.valueOf(value)));
            return this;
        }

        /**
         * Require the attribute to be greater than a given value
         *
         * @param value  the value to compare with
         * @return This
         */
        public ConstraintBuilder gt(Object value) {

            addConstraint(attribute, new Constraint(GT, Value.valueOf(value)));
            return this;
        }

        /**
         * Require the attribute to be greater or equal than a given value
         *
         * @param value  the value to compare with
         * @return This
         */
        public ConstraintBuilder ge(Object value) {

            addConstraint(attribute, new Constraint(GE, Value.valueOf(value)));
            return this;
        }

        /**
         * Require the attribute to be less than a given value
         *
         * @param value the value to compare with
         * @return This
         */
        public ConstraintBuilder lt(Object value) {

            addConstraint(attribute, new Constraint(LT, Value.valueOf(value)));
            return this;
        }

        /**
         * Require the attribute to be less or equal than a given value
         *
         * @param value the value to compare with
         * @return This
         */
        public ConstraintBuilder le(Object value) {

            addConstraint(attribute, new Constraint(LE, Value.valueOf(value)));
            return this;
        }

        /**
         * @param prefix constraint the attribute to start with the given prefix
         * @return This
         */
        public ConstraintBuilder startsWith(String prefix) {

            addConstraint(attribute, new Constraint(STARTS_WITH, Value.valueOf(prefix)));
            return this;
        }

        /**
         * @param suffix constraint the attribute to end with the given suffix.
         * @return This
         */
        public ConstraintBuilder endsWith(String suffix) {

            addConstraint(attribute, new Constraint(ENDS_WITH, Value.valueOf(suffix)));
            return this;
        }

        /**
         * @param string constraint the attribute to contain the given string.
         * @return This
         */
        public ConstraintBuilder contains(String string) {

            addConstraint(attribute, new Constraint(CONTAINS, Value.valueOf(string)));
            return this;
        }
    }

    /**
     * Append a new constraint to the outer class
     *
     * @param attribute the attribute to select
     * @param constraint the constraint to add
     */
    private void addConstraint(Attribute attribute, Constraint constraint) {

        if (!Constraint.EXIST.equals(constraint) && attribute.getType() != constraint.getValue().getType()) {

            throw new IllegalArgumentException(
                    "Constraint and attribute doesn't" + " have the same type: attribute type="
                            + attribute.getType() + ", constraint type=" + constraint.getValue().getType());
        }

        // TODO: use the cover relation?
        Set<Constraint> constrSet = this.constraints.get(attribute);

        if (constrSet == null) {

            constrSet = new HashSet<>();
            this.constraints.put(attribute, constrSet);
        }

        constrSet.add(constraint);
    }
}