/*******************************************************************************
* Copyright 2014 Felipe Takiyama
*
* 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 br.usp.poli.takiyama.prv;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import br.usp.poli.takiyama.common.Constraint;
import br.usp.poli.takiyama.utils.Lists;
public final class Substitution {
private final Map<LogicalVariable, Term> bindings;
/* ************************************************************************
* Constructors
* ************************************************************************/
/**
* Creates an empty substitution.
*/
private Substitution() {
bindings = new LinkedHashMap<LogicalVariable, Term>();
}
/**
* Creates a substitution based on a single binding
* @param binding A {@link Binding}
*/
private Substitution(Binding binding) {
this();
add(binding);
}
/**
* Creates a substitution based on a list of bindings.
* @param bindings A {@link List} of {@link Binding}s.
*/
private Substitution(List<Binding> bindings) {
this();
for (Binding binding : bindings) {
add(binding);
}
}
/* ************************************************************************
* Static factories
* ************************************************************************/
/**
* Returns a substitution based on a list of bindings.
* @param bindings A {@link List} of {@link Binding}s.
* @return A new substitution built from the list given.
*/
public static Substitution getInstance(List<Binding> bindings) {
return new Substitution(bindings);
}
/**
* Returns a substitution based on a single binding.
* @param binding A {@link Binding}.
* @return A new substitution built from the binding given.
*/
public static Substitution getInstance(Binding binding) {
return new Substitution(binding);
}
/**
* Returns a substitution based on two bindings.
*
* @param b1 A {@link Binding}.
* @param b2 A {@link Binding}.
* @return A new substitution built from the binding given.
*/
public static Substitution getInstance(Binding b1, Binding b2) {
List<Binding> args = Lists.listOf(b1, b2);
return new Substitution(args);
}
/**
* Returns a substitution based on three bindings.
*
* @param b1 A {@link Binding}.
* @param b2 A {@link Binding}.
* @param b3 A {@link Binding}.
* @return A new substitution built from the binding given.
*/
public static Substitution getInstance(Binding b1, Binding b2, Binding b3) {
List<Binding> args = Lists.listOf(b1, b2, b3);
return new Substitution(args);
}
/**
* Returns an empty substitution.
*
* @return an empty substitution.
*/
public static Substitution getInstance() {
return new Substitution();
}
/**
* Adds a new binding to this set of substitutions.
* @param b A {@link Binding}
* @throws IllegalArgumentException If the first term of the specified
* binding is already being replaced in this substitution.
*/
private void add(Binding b) throws IllegalArgumentException {
if (bindings.containsKey(b.firstTerm())) {
throw new IllegalArgumentException(b.firstTerm()
+ " is already being replaced.");
}
bindings.put(b.firstTerm(), b.secondTerm());
}
/* ************************************************************************
* Getters
* ************************************************************************/
/**
* @deprecated
* Returns a set containing all the logical variables that are in the first
* element of each binding in this substitution. I.e., it returns all the
* logical variables that are being substituted.
* @return A set containing the logical variables that are being substituted
*/
public Set<LogicalVariable> getLogicalVariables() {
return this.bindings.keySet();
}
/**
* Returns an iterator over the set containing all the logical
* variables that are in the first
* element of each binding in this substitution. I.e., it returns all the
* logical variables that are being replaced.
* @return A set containing the logical variables that are being substituted
*/
public Iterator<LogicalVariable> getSubstitutedIterator() {
return bindings.keySet().iterator();
}
/**
* Returns the replacement of a given logical variable in this substitution.
* <p>
* For instance, if the substitution is given by the set
* {X/Y, Z/r}, then calling this method with the parameter 'X' will
* return the logical variable 'Y'.
* </p>
* @param substituted The variable being substituted
* @return The replacement of a given logical variable in this substitution
*/
public Term getReplacement(LogicalVariable substituted) {
return bindings.get(substituted);
}
/**
* Returns true if this substitution contains the binding specified, false
* otherwise.
* @param binding The binding to search for.
* @return True if this substitution contains the binding specified, false
* otherwise.
*/
public boolean contains(Binding binding) {
LogicalVariable key = binding.firstTerm();
boolean replacedExists = bindings.containsKey(key);
boolean replacementExists = bindings.get(key).equals(binding.secondTerm());
return replacedExists && replacementExists;
}
/**
* Returns <code>true</code> if there is any binding in this substitution
* that replaces the specified {@link LogicalVariable}.
*
* @param replaced The logical variable being replaced to search for
* @return <code>true</code> if there is any binding in this substitution
* that replaces the specified {@link LogicalVariable}, <code>false</code>
* otherwise.
*/
public boolean contains(LogicalVariable replaced) {
return bindings.containsKey(replaced);
}
/**
* Returns <code>true</code> if there is any binding in this substitution
* containing the specified {@link Term}.
* <p>
* Note that this method differs from {@link #contains(LogicalVariable)}
* in that all terms are searched for, not only the logical variables
* being replaced.
* </p>
*
* @param t The term to search for
* @return <code>true</code> if there is any binding in this substitution
* containing the specified {@link Term}, <code>false</code>
* otherwise.
*/
public boolean has(Term t) {
return (bindings.containsKey(t) || bindings.containsValue(t));
}
/**
* Returns true if the specified logical variables have the same replacement
* in this substitution. That is, if the specified variables are X and Y,
* this method returns true if there is a term t such that X/t and Y/t are
* both in this substitution.
* <br>
* If such term is not found, or if one or both logical variables have no
* replacements in this substitution, this method returns false.
* @param firstVariable The first variable to compare
* @param secondVariable The second variable to compare
* @return True if the specified logical variables have a common replacement,
* false otherwise.
*/
public boolean hasCommonReplacement(LogicalVariable firstVariable, LogicalVariable secondVariable) {
if (this.bindings.containsKey(firstVariable) && this.bindings.containsKey(secondVariable)) {
return this.bindings.get(firstVariable).equals(this.bindings.get(secondVariable));
} else {
return false;
}
}
/**
* Returns true if there are no elements in this substitution.
* @return True if this set is empty (it has no bindings), false otherwise.
*/
public boolean isEmpty() {
return this.bindings.isEmpty();
}
/**
* Returns <code>true</code> if this substitution unifies the specified
* {@link Prv}
* <p>
* A substitution θ is a unifier of two parameterized random variables
* f (ti1,...,tik ) and f(tj1,...,tjk) if
* f(ti1,...,tik)[θ] = f(tj1,...,tjk)[θ].
* </p>
* <p>
* We then say that the two parameterized random variables unify [Kisynski, 2010].
* </p>
* @param prv1 The first {@link Prv}.
* @param prv2 The second {@link Prv}.
* @return <code>true</code> if this substitution the specified PRVs,
* <code>false</code> otherwise.
*/
public boolean isUnifier(Prv prv1, Prv prv2) {
return prv1.apply(this).equals(prv2.apply(this));
}
/**
* Returns the number of {@link Binding}s in this substitution.
*
* @return the number of {@link Binding}s in this substitution.
*/
public int size() {
return bindings.size();
}
/**
* Converts this Substitution to a {@link List} of {@link Binding}s.
*
* @return this Substitution as a {@link List} of {@link Binding}s.
*/
public List<Binding> asList() {
List<Binding> binds = new ArrayList<Binding>(bindings.size());
for (LogicalVariable t1 : bindings.keySet()) {
Binding b = Binding.getInstance(t1, bindings.get(t1));
binds.add(b);
}
return binds;
}
/**
* Retrieves, but does not remove, the first {@link Binding} inserted in
* this substitution, throwing an exception if this is empty.
*
* @return The first Binding inserted in this substitution.
* @throws NoSuchElementException if this substitution is empty.
*/
public Binding first() throws NoSuchElementException {
if (bindings.isEmpty()) {
throw new NoSuchElementException();
} else {
return asList().get(0);
}
}
/**
* Returns <code>true</code> if this substitution is consistent with the
* specified set of constraints.
* <p>
* A substitution is consistent with a set of constraints when applying
* the former to the latter generates only valid expressions.
* </p>
*
* @param constraints The set of constraints to test this substitution
* against
* @return <code>true</code> if this substitution is consistent with the
* specified set of constraints, <code>false</code> otherwise.
*/
public boolean isConsistentWith(Set<Constraint> constraints) {
int size = 0;
for (Constraint constraint : constraints) {
try {
constraint.apply(this);
size++;
} catch (IllegalStateException e) {
// takes into account valid Constant-only constraints
size++;
} catch (IllegalArgumentException e) {
// ignore invalid constraints
}
}
return (constraints.size() == size);
}
/* ************************************************************************
* hashCode, equals and toString
* ************************************************************************/
@Override
public String toString() {
StringBuilder result = new StringBuilder("{ ");
Iterator<LogicalVariable> bindings = this.bindings.keySet().iterator();
while (bindings.hasNext()) {
LogicalVariable lv = bindings.next();
result.append("( " + lv.toString() + "/" + this.bindings.get(lv) + " ) ");
}
result.append("}");
return result.toString();
}
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof Substitution))
return false;
Substitution otherSubstitution = (Substitution) other;
return this.bindings.equals(otherSubstitution.bindings);
}
@Override
public int hashCode() {
int result = 17;
for (Entry<LogicalVariable, Term> entry : this.bindings.entrySet()) {
result = 31 * result + entry.getKey().hashCode();
result = 31 * result + entry.getValue().hashCode();
}
return result;
}
}