/*
* The contents of this file are subject to the Open Software License
* Version 3.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.opensource.org/licenses/osl-3.0.txt
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*/
package org.mulgara.resolver.spi;
import java.util.ArrayList;
import java.util.List;
import org.mulgara.query.Constraint;
import org.mulgara.query.ConstraintElement;
import org.mulgara.query.ConstraintExpression;
import org.mulgara.query.ConstraintFilter;
import org.mulgara.query.ConstraintImpl;
import org.mulgara.query.Variable;
import org.mulgara.query.filter.And;
import org.mulgara.query.filter.Filter;
import org.mulgara.query.filter.SameTerm;
import org.mulgara.query.filter.value.Var;
/**
* Transforms constraint expressions to remove duplicate variables, and replace the
* existing constraints with filters that select matching variable values.
*
* @created May 19, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public class DuplicateVariableTransformer extends AbstractSymbolicTransformer {
/**
* All the work of this class is performed in this method. It ignores general constraints,
* and converts a ConstraintImpls with repeated variables into a conjunction of terms
* which have non-repeating variables, joined in an equivalent way to the original constraint.
* @param c The constraint to transform.
* @return The original constraint, or else a new equivalent conjunction if expr contains
* a repeated variable.
* @throws SymbolicTransformationException If there is an error in the constraint structure.
*/
@Override
protected ConstraintExpression transformConstraint(SymbolicTransformationContext context, Constraint c)
throws SymbolicTransformationException {
if (!c.isRepeating()) return c;
ConstraintImpl cnstr = (ConstraintImpl)c;
VarFreq vf = new VarFreq(cnstr);
// build the equivalent term
ConstraintElement[] elements = buildElements(cnstr, vf);
c = new ConstraintImpl(elements[0], elements[1], elements[2], elements[3]);
// if there was only a pair then return it as a simple filter
if (vf.frequency == 2) return new ConstraintFilter(c, createSameTermPair(vf.repeatedVar, 1));
// build a conjunction of filters
int matches = vf.frequency - 1;
Filter[] sameTerms = new Filter[matches];
for (int f = 0; f < matches; f++) sameTerms[f] = createSameTermPair(vf.repeatedVar, f + 1);
return new ConstraintFilter(c, new And(sameTerms));
}
/**
* Creates a filter which compares a pair of related variables for equality
* @param var The initial variable to compare.
* @param offset The offset from the initial variable name. This is greater than 0.
* @return A new filter that checks that both variables are equal.
*/
private Filter createSameTermPair(Variable var, int offset) {
assert offset > 0;
return new SameTerm(new Var(var.getName()), new Var(offsetName(var, offset)));
}
/**
* Creates the elements required for a new ConstraintImpl with no repeating variables.
* @param constraint The original constraint with repeating variables.
* @param vfStruct The structure of the variable repetition in the constraint.
* @return A ConstraintElement array with the variables all made unique, and a cycle starting at offset.
*/
private ConstraintElement[] buildElements(ConstraintImpl constraint, VarFreq vfStruct) {
ConstraintElement[] ops = new ConstraintElement[4];
int offset = 0;
for (int e = 0; e < 4; e++) {
ConstraintElement elt = constraint.getElement(e);
if (!vfStruct.repeatedVar.equals(elt)) ops[e] = elt;
else {
ops[e] = createOffsetVar(vfStruct.repeatedVar, offset);
offset = (offset + 1);
}
}
return ops;
}
/**
* Creates an internal variable. The name contains characters that are illegal in
* the query language, so they should not cause any conflicts.
* @param startVar The variable to base the new variable from.
* @param offset The change from the original variable.
* @return A variable with name related to the name of startVar by the value of offset,
* or the original startVar if offset is 0.
*/
private Variable createOffsetVar(Variable startVar, int offset) {
if (offset == 0) return startVar;
return new Variable(offsetName(startVar, offset));
}
/**
* Creates a new name for an internal variable, based on an initial variable
* and an offset from that variable.
* @param v The initial variable.
* @param offset The offset from the initial variable
* @return A new variable name.
*/
private String offsetName(Variable v, int offset) {
return "*" + v.getName() + offset;
}
/**
* Internal analysis of a ConstraintImpl to find the repeated variables,
* and the resulting structure.
*/
private static class VarFreq {
/** The number of times the repeated variable appears */
public final int frequency;
/** The repeating variable */
public final Variable repeatedVar;
public VarFreq(ConstraintImpl constraint) throws SymbolicTransformationException {
int frequency = 0;
Variable repeatedVar = null;
// find the repeating variable name, and frequency
List<Variable> vars = new ArrayList<Variable>(4);
for (int e = 0; e < 4; e++) {
ConstraintElement elt = constraint.getElement(e);
if (elt instanceof Variable) {
if (vars.contains(elt)) {
if (repeatedVar != null) {
if (!repeatedVar.equals(elt)) continue;
} else {
repeatedVar = (Variable)elt;
frequency++; // increment the initial count, since this variable has been seen before
}
frequency++;
} else {
vars.add((Variable)elt);
}
}
}
if (frequency < 2) throw new SymbolicTransformationException("No repeats found in a constraint that reported repetition.");
if (repeatedVar == null) throw new SymbolicTransformationException("No repeating variable found, despite counting a repetition of: " + frequency);
assert (vars.contains(repeatedVar));
this.frequency = frequency;
this.repeatedVar = repeatedVar;
}
}
}