/*
* Copyright 2017 TNG Technology Consulting GmbH
*
* 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.tngtech.archunit.junit;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.tngtech.archunit.Internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.tngtech.archunit.junit.MessageAssertionChain.Link.Result.difference;
import static java.util.Collections.singletonList;
@Internal
public class MessageAssertionChain {
private final List<Link> links = new ArrayList<>();
public void add(Link link) {
links.add(link);
}
@Override
public String toString() {
List<String> descriptions = new ArrayList<>();
for (Link link : links) {
descriptions.add(link.getDescription());
}
return Joiner.on(System.lineSeparator()).join(descriptions);
}
public static Link matchesLine(final String pattern) {
final Pattern p = Pattern.compile(pattern);
return new Link() {
@Override
public Result filterMatching(List<String> lines) {
for (String line : lines) {
if (p.matcher(line).matches()) {
return Result.success(difference(lines, line));
}
}
return Result.failure(lines);
}
@Override
public String getDescription() {
return String.format("Message has line matching '%s'", pattern);
}
};
}
public static Link containsLine(String text, Object... args) {
final String expectedLine = String.format(text, args);
return new Link() {
@Override
public Result filterMatching(List<String> lines) {
List<String> result = new ArrayList<>(lines);
boolean matches = result.remove(expectedLine);
return new Result(matches, result, "Lines were: " + lines);
}
@Override
public String getDescription() {
return String.format("Message contains line '%s'", expectedLine);
}
};
}
public static Link containsConsecutiveLines(final List<String> lines) {
checkArgument(!lines.isEmpty(), "Asserting zero consecutive lines makes no sense");
final String linesDesription = Joiner.on(System.lineSeparator()).join(lines);
final String description = "Message contains consecutive lines " + System.lineSeparator() + linesDesription;
return new Link() {
@Override
public Result filterMatching(List<String> allLines) {
int indexOfFirstLine = allLines.indexOf(lines.get(0));
if (indexOfFirstLine < 0) {
return new Result(false, allLines);
}
if (!lines.equals(allLines.subList(indexOfFirstLine, indexOfFirstLine + lines.size()))) {
return new Result(false, allLines);
}
List<String> remainingLines = ImmutableList.copyOf(Iterables.concat(
allLines.subList(0, indexOfFirstLine),
allLines.subList(indexOfFirstLine + lines.size(), allLines.size())));
return new Result(true, remainingLines);
}
@Override
public String getDescription() {
return description;
}
};
}
public void evaluate(AssertionError error) {
List<String> remainingLines = Splitter.on(System.lineSeparator()).splitToList(error.getMessage());
for (Link link : links) {
Link.Result result = link.filterMatching(remainingLines);
if (!result.matches) {
throw new AssertionError(createErrorMessage(link, result));
}
remainingLines = result.remainingLines;
}
if (!remainingLines.isEmpty()) {
throw new AssertionError("Unexpected message lines: " + remainingLines);
}
}
private String createErrorMessage(Link link, Link.Result result) {
String message = "Expected: " + link.getDescription();
if (result.mismatchDescription.isPresent()) {
message += System.lineSeparator() + "But: " + result.mismatchDescription.get();
}
return message;
}
@Internal
public interface Link {
Result filterMatching(List<String> lines);
String getDescription();
@Internal
class Result {
private final boolean matches;
private final List<String> remainingLines;
private final Optional<String> mismatchDescription;
public static Result success(List<String> remainingLines) {
return new Result(true, remainingLines);
}
public static Result failure(List<String> lines) {
return failure(lines, "Lines were " + Joiner.on(System.lineSeparator()).join(lines));
}
public static Result failure(List<String> lines, String mismatchDescription, Object... args) {
return new Result(false, lines, String.format(mismatchDescription, args));
}
public Result(boolean matches, List<String> remainingLines) {
this(matches, remainingLines, Optional.<String>absent());
}
public Result(boolean matches, List<String> remainingLines, String mismatchDescription) {
this(matches, remainingLines, Optional.of(mismatchDescription));
}
private Result(boolean matches, List<String> remainingLines, Optional<String> mismatchDescription) {
this.mismatchDescription = mismatchDescription;
this.matches = matches;
this.remainingLines = remainingLines;
}
public static List<String> difference(List<String> list, String toSubtract) {
return difference(list, singletonList(toSubtract));
}
public static List<String> difference(List<String> list, List<String> toSubtract) {
List<String> result = new ArrayList<>(list);
result.removeAll(toSubtract);
return result;
}
@Internal
public static class Builder {
private final List<Link> subLinks = new ArrayList<>();
public Builder containsLine(String line) {
subLinks.add(MessageAssertionChain.containsLine(line));
return this;
}
public Builder containsConsecutiveLines(List<String> lines) {
subLinks.add(MessageAssertionChain.containsConsecutiveLines(lines));
return this;
}
public Result build(List<String> lines) {
boolean matches = true;
List<String> remainingLines = new ArrayList<>(lines);
Optional<String> mismatchDescription = Optional.absent();
for (Link link : subLinks) {
Result result = link.filterMatching(remainingLines);
matches = matches && result.matches;
remainingLines = result.remainingLines;
mismatchDescription = append(mismatchDescription, result.mismatchDescription);
}
return new Result(matches, remainingLines);
}
private Optional<String> append(Optional<String> description, Optional<String> part) {
if (!description.isPresent() || !part.isPresent()) {
return description.or(part);
}
return Optional.of(description.get() + System.lineSeparator() + part.get());
}
}
}
}
}