/*
* Copyright (c) 2015 Hewlett Packard Enterprise Development Company, L.P. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.nic.graph.impl;
import org.opendaylight.nic.graph.api.Expression;
import org.opendaylight.nic.graph.api.Term;
import org.opendaylight.nic.graph.api.TermLabel;
import org.opendaylight.nic.graph.api.TermType;
import java.util.*;
import java.util.Map.Entry;
public class ExpressionImpl implements Expression {
private final Map<TermType, TermImpl> termMap;
// private boolean isEmpty() {
// return this.termMap.isEmpty();
// }
public boolean isAll() {
return !isNull() && this.termMap.isEmpty();
}
public ExpressionImpl(Map<TermType, TermImpl> termMap) {
super();
this.termMap = termMap;
}
@Override
public Set<? extends TermLabel> getTermTypeLabels() {
Set<TermLabel> labels = new HashSet<>();
for (TermType tt : termMap.keySet()) {
labels.add(tt.label());
}
return labels;
}
@Override
public Term getTerm(TermLabel termTypeLabel) {
// FIXME: Needs more optimal implementation. How do I convert TermLabel to TermType quickly?
for (Entry<TermType, TermImpl> entry : termMap.entrySet()) {
if (entry.getKey().label().equals(termTypeLabel)) {
return entry.getValue();
}
}
return null;
}
private Set<TermType> getTermTypes() {
return termMap.keySet();
}
@Override
public Collection<TermImpl> getTerms() {
return termMap.values();
}
@Override
public Set<Entry<TermLabel, Term>> getEntries() {
Map<TermLabel, Term> duplicate = new HashMap<>();
for (Entry<TermType, TermImpl> entry : termMap.entrySet()) {
duplicate.put(entry.getKey().label(), entry.getValue());
}
return duplicate.entrySet();
}
TermImpl getTerm(TermType tt) {
TermImpl term = termMap.get(tt);
if (term == null) {
return TermImpl.getInstance(tt);
}
return term;
}
/**
* This is needed to distinguish an empty expression from a fully wild
* carded expression.
*
* Both have no terms and so look identical. This instance is defined to be
* empty.
*/
public static final ExpressionImpl EXPRESSION_NULL = getInstance();
public boolean isNull() {
return this == EXPRESSION_NULL;
}
/**
* Adds the given term to the list of terms in this expression. Note that
* this operation is only allows if a term does not already exist with the
* same type.
*
* @param term
* term to add
* @throws IllegalArgumentException
* if the term is of the same type as an existing term already
* in the expression
*/
public void addTerm(TermImpl term) {
if (null != termMap.get(term.getType())) {
throw new IllegalArgumentException(
"Term conflicts with existing type");
}
termMap.put(term.getType(), term);
}
/**
* Terms not present in an expression are wildcarded, which means they
* include the full range. This api returns the Term in the Expression, and
* if one is not present it returns a term which includes the full possible
* range for the {@link TermType}.
* @param tt termtype
* @return term
*/
public TermImpl getFullTerm(TermType tt) {
TermImpl term = getTerm(tt);
if (term.isEmpty()) {
return TermImpl.getInstanceMax(tt);
}
return term;
}
/**
* Public factory for constructing Expressions.
* <p>
* Note that Terms not in Expression include all values (are wildcarded).
* @return Expression
*/
public static ExpressionImpl getInstance() {
ExpressionImpl exp = new ExpressionImpl();
return exp;
}
/**
* Public factory for constructing Expressions.
* <p>
* Note that Terms not in Expression include all values (are wildcarded).
* @param termCollection term
* @return Expression
*/
public static ExpressionImpl getInstance(Collection<TermImpl> termCollection) {
ExpressionImpl exp = new ExpressionImpl();
for (TermImpl ti : termCollection) {
if (ti.equals(TermImpl.getInstanceMax(ti.getType()))) {
continue; // don't add max intervals
}
exp.termMap.put(ti.getType(), ti);
}
return exp;
}
private ExpressionImpl() {
termMap = new HashMap<TermType, TermImpl>();
}
@Override
public String toString() {
return "Expression { " + termMap.values() + " }";
}
/**
* Method performs operator "greater than" logic: this "greater than" other.
* <p>
* Returns true if this is a superset of other for every {@link TermImpl}.
* For every {@link TermImpl} this has, it must be a superset of other. For
* every term other has that this does not, this is a superset already
* because Terms not in an expression are wildcarded, which means includes
* all values.
* @param other expression
* @return boolean
*/
public boolean greaterThan(ExpressionImpl other) {
/**
* Need to handle cases of All, Null, Value: for this and other
*/
/** this=All, other=[Null,Value] */
if (isAll() && !other.isAll()) {
return true;
}
/** this=[All], other=[All] */
if (isAll() && other.isAll()) {
return false;
}
/** this=Null, other=[*] */
if (isNull()) {
return false;
}
/** this=[All,value], other=[Null] */
if (!isNull() && other.isNull()) {
return true;
}
/** this=[value], other=[value] */
Set<TermType> termTypeUnion = new HashSet<TermType>(this.getTermTypes());
termTypeUnion.addAll(other.getTermTypes());
boolean greaterThan = false;
for (TermType tt : termTypeUnion) {
// Calculate intersection between t and other
TermImpl thisTerm = getFullTerm(tt);
TermImpl otherTerm = other.getFullTerm(tt);
if (thisTerm.greaterThan(otherTerm)) {
greaterThan = true;
} else if (thisTerm.lessThan(otherTerm)) {
// cannot be less then for any Term
return false;
}
// else they are equal for this term, which is OK
}
return greaterThan;
}
/**
* Method performs <em>sub</em> operator logic: this - other.
* <p>
* The difference of two expressions may be a disjunction of multiple
* expressions, so this function returns a Set of expressions.
* @param other expression
* @return Set of expression
*/
public Set<ExpressionImpl> sub(ExpressionImpl other) {
// First check for shared terms. Terms are ANDed (&)
//
// If expression A and B each has terms q,r,s,t as follows
// A(q[3,5]) r[3,5] u[5,8]) which means:
// A( q[3,5]&r[3,5]&s[1,10]&t[1,10]&u[5,8]
// B(q4,6]) s[3,5] u[4,9]) which means:
// B( q[4,6]&r[1,10]&s[3,5]&t[1,10]&u[4,9]
//
// For term q: They overlap (no wildcards)
// For term r: b>a (b is wildcarded)
// For term s: a>b (a is wildcarded)
// For term t: a=b (both are wild carded)
// For term u: b>a (no wildcard)
/**
* Need to handle cases of All, Null, Value: for this and other
*/
/** this=All, other=[All] */
if (isAll() && other.isAll()) {
// returning a set with no expressions - empty
return new HashSet<ExpressionImpl>();
}
/** this=Null, other=[All, Null,Value] */
if (isNull()) {
// returning a set with no expressions - empty
return new HashSet<ExpressionImpl>();
}
/** this=[All,value], other=[Null] */
if (other.isNull()) {
Set<ExpressionImpl> listWithThis = new HashSet<ExpressionImpl>();
listWithThis.add(this);
return listWithThis;
}
Set<ExpressionImpl> ss = new HashSet<ExpressionImpl>();
Set<TermType> termTypeUnion = new HashSet<TermType>(this.getTermTypes());
termTypeUnion.addAll(other.getTermTypes());
for (TermType tt : termTypeUnion) {
// calculate difference between this and other Full Terms
TermImpl thisTerm = getFullTerm(tt);
TermImpl otherTerm = other.getFullTerm(tt);
TermImpl termDifference = thisTerm.sub(otherTerm);
// if thisTerm==termDifference, then this is orthogonal to other
// returning here ensures a non-redundant expression is returned
if (thisTerm.equals(termDifference)) {
Set<ExpressionImpl> listWithThis = new HashSet<ExpressionImpl>();
listWithThis.add(this);
return listWithThis;
}
// if no difference, move to next shared term
if (termDifference.isEmpty()) {
continue;
}
// there is a difference, use it plus all other Terms in this to
// create expression
Collection<TermImpl> co = new HashSet<TermImpl>();
co.add(termDifference);
for (TermImpl ti : getTerms()) {
if (ti.getType() == tt) {
continue;
}
co.add(ti);
}
// add new expression to result set
ss.add(getInstance(co));
}
if (ss.isEmpty()) {
// returning a set with no expressions - empty
return new HashSet<ExpressionImpl>();
}
return ss;
}
/**
* Method performs <em>and</em> operator logic: this AND other.
* <p>
* The and of two expressions is the intersection of all {@link TermImpl}s.
* @param other expression
* @return expression
*/
public ExpressionImpl and(ExpressionImpl other) {
/**
* Need to handle cases of All, Null, Value: for this and other
*/
/** handle NULL */
if (this.isNull() || other.isNull()) {
return EXPRESSION_NULL;
}
/** handle both All */
if (this.isAll() && other.isAll()) {
return getInstance();
}
/** handle this=All */
if (this.isAll()) {
return getInstance(other.getTerms());
}
/** handle All */
if (other.isAll()) {
return getInstance(this.getTerms());
}
Collection<TermImpl> co = new HashSet<TermImpl>();
Set<TermType> termTypeUnion = new HashSet<TermType>(this.getTermTypes());
termTypeUnion.addAll(other.getTermTypes());
for (TermType tt : termTypeUnion) {
// Calculate intersection between t and other
TermImpl thisTerm = getFullTerm(tt);
TermImpl otherTerm = other.getFullTerm(tt);
TermImpl termIntersection = thisTerm.and(otherTerm);
// if no intersection, return
if (termIntersection.isEmpty()) {
return EXPRESSION_NULL;
}
// there is an intersection, add it to the expression
co.add(termIntersection);
}
if (co.isEmpty()) {
return EXPRESSION_NULL;
}
return getInstance(co);
}
/**
* Method performs <em>add</em> operator logic: this + other.
* <p>
* This results in the union all {@link TermImpl}s.
* @param other expression
* @return expression
*/
public ExpressionImpl add(ExpressionImpl other) {
/**
* Need to handle cases of All, Null, Value: for this and other
*/
/** handle NULL */
if (this.isNull() && other.isNull()) {
return EXPRESSION_NULL;
}
/** handle this=NULL */
if (this.isNull()) {
return getInstance(other.getTerms());
}
/** handle other=NULL */
if (other.isNull()) {
return getInstance(this.getTerms());
}
/** handle both All */
if (this.isAll() || other.isAll()) {
return getInstance();
}
Collection<TermImpl> co = new HashSet<TermImpl>();
Set<TermType> termTypeUnion = new HashSet<TermType>(this.getTermTypes());
termTypeUnion.addAll(other.getTermTypes());
for (TermType tt : termTypeUnion) {
// Calculate union between t and other
TermImpl thisTerm = getFullTerm(tt);
TermImpl otherTerm = other.getFullTerm(tt);
TermImpl termIntersection = thisTerm.add(otherTerm);
// if union results in full range, then do not add to expression
if (termIntersection.equals(TermImpl.getInstanceMax(tt))) {
continue;
}
// there is an intersection, add it to the expression
co.add(termIntersection);
}
// empty here means expression is fully wildcarded
if (co.isEmpty()) {
return getInstance();
}
return getInstance(co);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((termMap == null) ? 0 : termMap.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExpressionImpl other = (ExpressionImpl) obj;
if (termMap == null) {
if (other.termMap != null) {
return false;
}
} else if (!termMap.equals(other.termMap)) {
return false;
}
return true;
}
}