Java tutorial
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); } }