/*
* 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.evaluation;
import notaql.datamodel.ObjectValue;
import notaql.datamodel.Step;
import notaql.datamodel.Value;
import notaql.datamodel.fixation.Fixation;
import notaql.model.AttributeSpecification;
import notaql.model.EvaluationException;
import notaql.model.path.IgnoredIdStep;
import notaql.model.path.StepNameEvaluationResult;
import notaql.model.vdata.ObjectVData;
import notaql.evaluation.values.PartialObjectValue;
import notaql.model.vdata.VData;
import notaql.evaluation.values.KeyGroupValue;
import notaql.model.vdata.aggregation.AggregatingObjectVData;
import scala.Tuple2;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* This is the most complex evaluator in the whole project.
*
* The complicated part is evaluating multiple specifications after each other, because each specification may be ambigous
* and may bind new paths.
*
* Every evaluation of ObjectVData starts by simply providing a "PartialObjectValue" which is then later
* resolved into an "ObjectValue".
*/
public class ObjectVDataEvaluator implements Evaluator, Reducer {
private static final long serialVersionUID = 2062262076467699656L;
@Override
public List<ValueEvaluationResult> evaluate(VData vData, Fixation fixation) {
assert vData instanceof ObjectVData;
final List<AttributeSpecification> specifications = ((ObjectVData) vData).getSpecifications();
// start with a single empty result
List<ValueEvaluationResult> partialResults = Arrays.asList(new ValueEvaluationResult(new PartialObjectValue(), fixation));
// for each attribute definition...
for (AttributeSpecification specification : specifications) {
final List<ValueEvaluationResult> newPartialResults = new LinkedList<>();
// iterate over the already created partial results
for (ValueEvaluationResult partialResult : partialResults) {
// evaluate the specification by extending the current partial result
newPartialResults.addAll(evaluateSpecification(specification, partialResult));
}
// update the partial results for the next attribute specification
partialResults = newPartialResults;
}
return partialResults;
}
@Override
public boolean canReduce(VData vData) {
return true;
}
/**
* Evaluates a specification by extending a given partial result and returning the (possibly many) extended results.
* <p>
* The resulting fixations may be more specific than before.
*
* @param specification The specification that shall be evaluated
* @param extendableResult The intermediate result that shall be extended by evaluating this specification.
* @return
*/
private List<ValueEvaluationResult> evaluateSpecification(AttributeSpecification specification, ValueEvaluationResult extendableResult) {
// collect the new partial results
List<ValueEvaluationResult> partialResults = new LinkedList<>();
assert extendableResult.getValue() instanceof PartialObjectValue;
final PartialObjectValue partialObject = (PartialObjectValue) extendableResult.getValue();
final Fixation partialFixation = extendableResult.getFixation();
final PartialObjectValue startNewPartialObject = new PartialObjectValue(partialObject);
partialResults.add(new ValueEvaluationResult(startNewPartialObject, partialFixation));
// first evaluate all the possible target paths. This might specify the fixation further
final List<StepNameEvaluationResult> targetPaths = specification.getOutputPath().evaluate(partialFixation);
// Then iterate over the resulting paths - each path should be appended to the input object gaining multiple objects.
// The objects are copied in case the vData evaluation is ambiguous
for (StepNameEvaluationResult targetPath : targetPaths) {
// in case we have multiple steps: abort
if (targetPath.getSteps().size() > 1)
throw new EvaluationException("Multiple steps of output paths are not allowed in the OBJECT constructor.");
// in case we have no step: abort
if (targetPath.getSteps().size() < 1)
throw new EvaluationException("The evaluation of a target path in the OBJECT constructor lead to an empty path. This is not allowed.");
// get the single step
final Step<?> step = targetPath.getSteps().get(0);
final Step<String> objectStep;
if (step instanceof IgnoredIdStep.IgnoredStep)
objectStep = (IgnoredIdStep.IgnoredStep) step;
else
objectStep = new Step<>(step.toString());
// make sure to have the most specific fixation in hand.
final Fixation moreSpecificFixation = targetPath.getFixation().getMoreSpecific(partialFixation);
// evaluate the vData - multiple possible results can be returned here
final List<ValueEvaluationResult> valueEvaluationResults = EvaluatorService.getInstance()
.evaluate(specification.getVData(), moreSpecificFixation);
final List<ValueEvaluationResult> extendedPartialResults = new LinkedList<>();
// iterate over the vData results and clone and extend the partial objects from the last round
// (of target paths) for each result
for (ValueEvaluationResult valueEvaluationResult : valueEvaluationResults) {
for (ValueEvaluationResult partialResult : partialResults) {
final PartialObjectValue newPartialObject = (PartialObjectValue) partialResult.getValue();
// copy the object
final PartialObjectValue splitPartialObject = new PartialObjectValue(newPartialObject);
// extend it with the new data
splitPartialObject.put(objectStep, valueEvaluationResult.getValue().deepCopy(), specification.getVData());
// store it in the temporary splitting list
extendedPartialResults.add(new ValueEvaluationResult(splitPartialObject, valueEvaluationResult.getFixation().getMoreSpecific(moreSpecificFixation)));
}
}
// in case the expression does not generate any results and: keep the current value (if it already contains data)
if(valueEvaluationResults.isEmpty() && ((PartialObjectValue) extendableResult.getValue()).size() > 0)
extendedPartialResults.add(extendableResult);
partialResults = extendedPartialResults;
}
return partialResults;
}
/**
* @param vData
* @param v1 reduced as much as possible
* @param v2 to add to v1
* @return
*/
@Override
public PartialObjectValue reduce(VData vData, Value v1, Value v2) {
assert v1 instanceof PartialObjectValue && v2 instanceof PartialObjectValue;
final PartialObjectValue merge = merge((PartialObjectValue) v1, (PartialObjectValue) v2);
final PartialObjectValue result = new PartialObjectValue();
merge.toMap().entrySet()
.stream()
.forEach( // add all attributes to the resulting object
e -> {
final VData mergeVData = merge.getVData(e.getKey());
if (EvaluatorService.getInstance().canReduce(mergeVData)) // in case the step is reducable: do so in any case!
{
if (e.getValue() instanceof KeyGroupValue) // unnest for keygroupvalues
result.put(
e.getKey(),
((KeyGroupValue) e.getValue()).stream().reduce(EvaluatorService.getInstance().createIdentity(mergeVData), (a, b) -> EvaluatorService.getInstance()
.reduce(mergeVData, a, b)),
mergeVData
);
else
result.put(
e.getKey(),
EvaluatorService.getInstance().reduce(mergeVData, EvaluatorService.getInstance().createIdentity(mergeVData), e.getValue()),
mergeVData
);
} else { // for any other value: just add it
result.put(e.getKey(), e.getValue(), mergeVData);
}
}
);
return result;
}
@Override
public Value createIdentity(VData vData) {
return new PartialObjectValue();
}
/**
* Simply copy the data back to a non-partial object.
*
* @param vData The instance of vData, that this reducer is built for
* @param value
* @return
*/
@Override
public Value finalize(VData vData, Value value) {
assert vData instanceof ObjectVData;
assert value instanceof PartialObjectValue;
final PartialObjectValue partialObjectValue = (PartialObjectValue) value;
final ObjectValue result = new ObjectValue();
partialObjectValue.toMap().entrySet().stream()
.filter(e -> !(e.getKey() instanceof IgnoredIdStep.IgnoredStep))
.map(e -> new Tuple2<>(
e.getKey(),
!EvaluatorService.getInstance().canReduce(partialObjectValue.getVData(e.getKey())) ?
e.getValue()
: EvaluatorService.getInstance().finalize(partialObjectValue.getVData(e.getKey()), e.getValue())
)
)
.forEach(t -> result.put(t._1, t._2));
return result;
}
public static PartialObjectValue merge(PartialObjectValue o1, PartialObjectValue o2) {
final PartialObjectValue target = new PartialObjectValue();
crossCopy(o1, target);
crossCopy(o2, target);
return target;
}
/**
* for each value put the step into the partial object. In case we have a KeyGroupValue, put all in.
*
* @param source
* @param target
*/
private static void crossCopy(PartialObjectValue source, PartialObjectValue target) {
source.toMap().entrySet().stream().forEach(e ->
{
final VData vData = source.getVData(e.getKey());
if (e.getValue() instanceof KeyGroupValue) {
((KeyGroupValue) e.getValue()).stream()
.forEach(
f -> target.put(
e.getKey(),
f,
vData
)
);
// in case we have an Unresolved value or
// any value which has not been added yet: add it
} else if (EvaluatorService.getInstance().canReduce(vData) || !target.containsKey(e.getKey())) {
target.put(
e.getKey(),
e.getValue(),
vData
);
}
}
);
}
@Override
public List<Class<? extends VData>> getProcessedClasses() {
return Arrays.asList(ObjectVData.class, AggregatingObjectVData.class);
}
}