/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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 com.asakusafw.testdriver.core;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Verifies model objects using {@link VerifyRule} and expected data set.
* @since 0.2.0
*/
public class VerifyEngine {
private final VerifyRule rule;
private final Map<Object, DataModelReflection> expectedRest;
private final Map<Object, DataModelReflection> sawActual;
/**
* Creates a new instance.
* @param rule the verification strategy
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public VerifyEngine(VerifyRule rule) {
if (rule == null) {
throw new IllegalArgumentException("rule must not be null"); //$NON-NLS-1$
}
this.rule = rule;
this.expectedRest = new LinkedHashMap<>();
this.sawActual = new HashMap<>();
}
/**
* Appends the expected data model objects.
* <p>
* If this already saw data model objects with same key in the expected input,
* the old one will be replaced with in input.
* Note that the expected input will be closed.
* </p>
* @param expected the expected input
* @return this object (for method chain)
* @throws IOException if failed to obtain model objects from the input,
* or the input key is already registered
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public VerifyEngine addExpected(DataModelSource expected) throws IOException {
if (expected == null) {
throw new IllegalArgumentException("expected must not be null"); //$NON-NLS-1$
}
try {
while (true) {
DataModelReflection next = expected.next();
if (next == null) {
break;
}
Object key = rule.getKey(next);
DataModelReflection old = expectedRest.put(key, next);
if (old != null) {
throw new IOException(MessageFormat.format(
Messages.getString("VerifyEngine.errorConflictExpectedDataKey"), //$NON-NLS-1$
key,
old,
next));
}
}
} finally {
expected.close();
}
return this;
}
/**
* Verifies the input sequence and returns diagnostics.
* <p>
* First, this engine search an expected model object corresponded to each input object using their
* {@link VerifyRule#getKey(DataModelReflection) keys}.
* If there are the such pairs, then this engine invokes
* {@link VerifyRule#verify(DataModelReflection, DataModelReflection)
* VerifyRule.verifyModel(eachExpected, eachInput)}
* for each pair, and then removes the expected data.
* Otherwise, this engine invokes
* {@link VerifyRule#verify(DataModelReflection, DataModelReflection)
* VerifyRule.verifyModel(null, eachInput)}.
* for each input without corresponding expected object.
* </p>
* <p>
* If there are any differences between expected objects and input objects,
* the resulting list includes them.
* </p>
* @param input the input to verify
* @return differences between the input and the expected, or an empty list if successfully verified
* @throws IOException if failed to obtain model objects from the input,
* or the input key is already presented
* @throws IllegalArgumentException if some parameters were {@code null}
* @see #inspectRest()
*/
public List<Difference> inspectInput(DataModelSource input) throws IOException {
if (input == null) {
throw new IllegalArgumentException("input must not be null"); //$NON-NLS-1$
}
List<Difference> results = new ArrayList<>();
try {
while (true) {
DataModelReflection actual = input.next();
if (actual == null) {
break;
}
Object key = rule.getKey(actual);
DataModelReflection saw = sawActual.get(key);
if (saw != null) {
results.add(new Difference(actual, null, MessageFormat.format(
Messages.getString("VerifyEngine.errorConflictActualDataKey"), //$NON-NLS-1$
key,
saw,
actual)));
} else {
sawActual.put(key, actual);
DataModelReflection expected = expectedRest.remove(key);
Difference diff = verify(key, expected, actual);
if (diff != null) {
results.add(diff);
}
}
}
} finally {
input.close();
}
return results;
}
/**
* Verifies the rest of expected data objects.
* <p>
* This engine invokes
* {@link VerifyRule#verify(DataModelReflection, DataModelReflection)
* VerifyRule.verifyModel(eachExpected, null)}
* for each expected data objects, and clears them from this engine.
* </p>
* <p>
* Note that the rest of expected data mean "expected but appeared in input."
* You should invoke {@link #inspectInput(DataModelSource)} for each input before invoke this method.
* </p>
* @return diagnostics for expected but not appeared in input objects
* @see #inspectInput(DataModelSource)
*/
public List<Difference> inspectRest() {
List<Difference> results = new ArrayList<>();
for (Map.Entry<Object, DataModelReflection> entry : expectedRest.entrySet()) {
Difference diff = verify(entry.getKey(), entry.getValue(), null);
if (diff != null) {
results.add(diff);
}
}
expectedRest.clear();
sawActual.clear();
return results;
}
private Difference verify(Object key, DataModelReflection expected, DataModelReflection actual) {
assert key != null;
assert expected != null || actual != null;
Object result = rule.verify(expected, actual);
if (result == null) {
return null;
}
return new Difference(expected, actual, result);
}
}