/*
* Copyright 2015 by Thomas Lottermann
*
* 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 notaql.datamodel.fixation;
import notaql.datamodel.ComplexValue;
import notaql.model.EvaluationException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Provides a way to see which path has been selected during evaluation.
* Such a path can consist of multiple steps which might be bound (by *, ?() or similar expressions) or not.
*/
public class Fixation implements Serializable {
private static final long serialVersionUID = 1569791160406292056L;
private List<FixationStep<?>> fixedPath;
private ComplexValue rootValue;
/**
* Create an empty fixation
* @param rootValue
*/
public Fixation(ComplexValue rootValue) {
this(rootValue, new LinkedList<>());
}
/**
* Create a fixation consisting of the provided steps
* @param rootValue
* @param fixedPath
*/
public Fixation(ComplexValue rootValue, FixationStep<?>... fixedPath) {
this(rootValue, Arrays.asList(fixedPath));
}
/**
* Create a fixation consisting of the provided steps
* @param rootValue
* @param fixedPath
*/
public Fixation(ComplexValue rootValue, List<FixationStep<?>> fixedPath) {
this.fixedPath = new LinkedList<>(fixedPath);
this.rootValue = rootValue;
}
/**
* Creates a new fixation from the old one appending the provided step.
*
* @param fixation
* @param step
*/
public Fixation(Fixation fixation, FixationStep step) {
this.fixedPath = new LinkedList<>(fixation.fixedPath);
this.fixedPath.add(step);
this.rootValue = fixation.rootValue;
}
/**
* Returns the steps (FIFO)
* @return
*/
public List<FixationStep<?>> getFixedPath() {
return fixedPath;
}
/**
* Provides the root value, at which this fixation starts
* @return
*/
public ComplexValue getRootValue() {
return rootValue;
}
/**
* Provides the step which comes after traversing the input fixation to the end.
*
* e.g. this is 0.bla.1 and fixation is 0.bla then the output is 1
*
* @param fixation
* @return
*/
public FixationStep<?> getNextStep(Fixation fixation) {
if (rootValue != fixation.rootValue)
return null;
final Iterator<FixationStep<?>> myIterator = fixedPath.iterator();
final Iterator<FixationStep<?>> otherIterator = fixation.fixedPath.iterator();
while (myIterator.hasNext() && otherIterator.hasNext()) {
final FixationStep mine = myIterator.next();
final FixationStep others = otherIterator.next();
if(!mine.equals(others))
return null;
}
if (myIterator.hasNext())
return myIterator.next();
return null;
}
/**
* Provides the fixation which is common to both this and the other fixation.
*
* e.g. a.b.0.c and a.b.1.c => a.b
*
* @param other
* @return
*/
public Fixation getCommonPrefix(Fixation other) {
if (rootValue != other.rootValue)
return null;
final Fixation result = new Fixation(rootValue, new LinkedList<>());
final Iterator<FixationStep<?>> myIterator = fixedPath.iterator();
final Iterator<FixationStep<?>> otherIterator = other.fixedPath.iterator();
while (myIterator.hasNext() && otherIterator.hasNext()) {
final FixationStep myStep = myIterator.next();
final FixationStep otherStep = otherIterator.next();
if (!myStep.equals(otherStep))
break;
result.fixedPath.add(myStep);
}
return result;
}
/**
* Returns the more specific fixation (in terms of bound steps).
*
* e.g. 0(bound).a(bound) is more specific than 0(bound).b
*
* @param other
* @return
*/
public Fixation getMoreSpecific(Fixation other) {
Fixation commonPrefix = getCommonPrefix(other);
final FixationStep<?> ownNextStep = this.getNextStep(commonPrefix);
final FixationStep<?> otherNextStep = other.getNextStep(commonPrefix);
if (otherNextStep == null)
return this;
if (ownNextStep == null)
return other;
assert !(ownNextStep.isBound() && otherNextStep.isBound());
if (ownNextStep.isBound())
return this;
if (otherNextStep.isBound())
return other;
// Fix the issues that causes fixations to be forgotten.
// e.g. The query
// OUT._id <- IN.children[*].name,
// OUT.parents <- LIST(
// OBJECT(
// name <- IN._id,
// allowance <- IN.children[@].allowance
// )
// );
// FIXME: Issue #22 is caused this way. Actually all fixations may be returned as they are all relevant.
final long ownBindCount = countBound(this);
final long otherBindCount = countBound(other);
return ownBindCount >= otherBindCount ? this : other;
}
/**
* Provides the number of bound steps
*
* @param fixation
* @return
*/
private static long countBound(Fixation fixation) {
return fixation.getFixedPath().stream().filter(FixationStep::isBound).count();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fixation fixation = (Fixation) o;
if (fixedPath != null ? !fixedPath.equals(fixation.fixedPath) : fixation.fixedPath != null) return false;
if (rootValue != null ? !rootValue.equals(fixation.rootValue) : fixation.rootValue != null) return false;
return true;
}
@Override
public int hashCode() {
int result = fixedPath != null ? fixedPath.hashCode() : 0;
result = 31 * result + (rootValue != null ? rootValue.hashCode() : 0);
return result;
}
@Override
public String toString() {
return fixedPath.stream().map(FixationStep::toString).collect(Collectors.joining("."));
}
}