package org.xpect.expectation.impl; import java.util.Collections; import java.util.List; import org.eclipse.xtext.util.internal.FormattingMigrator; import org.junit.ComparisonFailure; import org.xpect.XpectArgument; import org.xpect.XpectImport; import org.xpect.expectation.ISingleLineExpectationRegion; import org.xpect.expectation.IStringDiffExpectation; import org.xpect.expectation.StringDiffExpectation; import org.xpect.setup.XpectSetupFactory; import org.xpect.state.Creates; import org.xpect.text.CharSequences; import org.xpect.text.ITextDifferencer; import org.xpect.text.ITextDifferencer.ISegment; import org.xpect.text.ITextDifferencer.ITextDiff; import org.xpect.text.ITextDifferencer.ITextDiffConfig; import org.xpect.text.StringEndsSimilarityFunction; import org.xpect.text.TextDiffToString; import org.xpect.text.TextDifferencer; import org.xpect.util.IDifferencer.ISimilarityFunction; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @XpectSetupFactory @SuppressWarnings("restriction") @XpectImport(ExpectationRegionProvider.class) public class StringDiffExpectationImpl extends AbstractExpectation implements IStringDiffExpectation { public static class GenericTextDiffConfig<T> implements ITextDiffConfig<T> { private final ITokenAdapter<T> delegate; private final List<T> left; private final List<T> right; public GenericTextDiffConfig(List<T> left, List<T> right, ITokenAdapter<T> delegate) { super(); this.delegate = delegate; this.left = left; this.right = right; } public boolean isHidden(T token, String segment) { return delegate.isHidden(token, segment); } public float similarity(ISegment object1, ISegment object2) { T token1 = left.get(object1.getTokenIndex()); T token2 = right.get(object2.getTokenIndex()); return delegate.similarity(token1, object1.toString(), token2, object2.toString()); } public Iterable<String> toSegments(T token) { return delegate.splitIntoSegments(token); } } public static class StringTextDiffConfig implements ITextDiffConfig<String> { private final ISimilarityFunction<String> similarityFunction; private final boolean whitespaceSensitive; public StringTextDiffConfig(ISimilarityFunction<String> similarityFunction, boolean whitespaceSensitive) { super(); this.similarityFunction = similarityFunction; this.whitespaceSensitive = whitespaceSensitive; } public boolean isHidden(String token, String segment) { if (whitespaceSensitive) return false; return CharSequences.isWhitespace(segment); } public float similarity(ISegment object1, ISegment object2) { return similarityFunction.similarity(object1.toString(), object2.toString()); } public Iterable<String> toSegments(String token) { return Collections.singleton(token); } } public static class TokenTextDiffConfig<T extends IToken<? super T>> implements ITextDiffConfig<T> { private final List<T> left; private final List<T> right; public TokenTextDiffConfig(List<T> left, List<T> right) { super(); this.left = left; this.right = right; } public boolean isHidden(T token, String segment) { return token.isHidden(segment); } public float similarity(ISegment object1, ISegment object2) { T l = left.get(object1.getTokenIndex()); T r = right.get(object2.getTokenIndex()); return l.similarity(object1.toString(), r, object2.toString()); } public Iterable<String> toSegments(T token) { return token.splitIntoSegments(); } } private final StringDiffExpectation annotation; public StringDiffExpectationImpl(XpectArgument argument, TargetSyntaxSupport targetSyntax) { super(argument, targetSyntax); this.annotation = argument.getAnnotationOrDefault(StringDiffExpectation.class); } public <T extends IToken<? super T>> void assertDiffEquals(Iterable<T> leftTokens, Iterable<T> rightTokens) { List<T> left = ImmutableList.copyOf(leftTokens); List<T> right = ImmutableList.copyOf(rightTokens); ITextDiffConfig<T> config = createDiffConfig(left, right); assertDiffEquals(left, right, config); } public <T> void assertDiffEquals(Iterable<T> leftTokens, Iterable<T> rightTokens, ITokenAdapter<T> adapter) { Preconditions.checkNotNull(adapter); List<T> left = ImmutableList.copyOf(leftTokens); List<T> right = ImmutableList.copyOf(rightTokens); ITextDiffConfig<T> config = createDiffConfig(left, right, adapter); assertDiffEquals(left, right, config); } protected <T> void assertDiffEquals(List<T> left, List<T> right, ITextDiffConfig<T> config) throws ComparisonFailure { ITextDifferencer differencer = createDifferencer(); ITextDiff diff = differencer.diff(left, right, config); String actual = createDiffToString().apply(diff); String escapedActual = getTargetSyntaxLiteral().escape(actual); String originalExpectation = getExpectation(); String migratedExpectation; if (!annotation.whitespaceSensitive()) { FormattingMigrator migrator = new FormattingMigrator(); migratedExpectation = migrator.migrate(escapedActual, originalExpectation); } else { migratedExpectation = originalExpectation; } if (!migratedExpectation.equals(escapedActual)) { String expDoc = replaceInDocument(migratedExpectation); String actDoc = replaceInDocument(escapedActual); throw new ComparisonFailure("", expDoc, actDoc); } } public void assertDiffEquals(String left, String right) { Preconditions.checkNotNull(left); Preconditions.checkNotNull(right); Function<String, ? extends Iterable<String>> tokenizer = createTokenizer(); List<String> leftTokens = ImmutableList.copyOf(tokenizer.apply(left)); List<String> rightTokens = ImmutableList.copyOf(tokenizer.apply(right)); ITextDiffConfig<String> config = createDiffConfig(); assertDiffEquals(leftTokens, rightTokens, config); } protected ITextDifferencer.ITextDiffConfig<String> createDiffConfig() { return new StringTextDiffConfig(new StringEndsSimilarityFunction(), annotation.whitespaceSensitive()); } protected <T extends IToken<? super T>> ITextDifferencer.ITextDiffConfig<T> createDiffConfig(List<T> left, List<T> right) { return new TokenTextDiffConfig<T>(left, right); } protected <T> ITextDifferencer.ITextDiffConfig<T> createDiffConfig(List<T> left, List<T> right, ITokenAdapter<T> adapter) { return new GenericTextDiffConfig<T>(left, right, adapter); } protected ITextDifferencer createDifferencer() { return new TextDifferencer(); } protected Function<? super ITextDiff, String> createDiffToString() { TextDiffToString toString = new TextDiffToString(); toString.setAllowSingleLineDiff(annotation.allowSingleLineDiff()); toString.setAllowSingleSegmentDiff(annotation.allowSingleSegmentDiff() && getRegion() instanceof ISingleLineExpectationRegion); toString.setLinesBeforeDiff(annotation.linesBeforeDiff()); toString.setLinesAfterDiff(annotation.linesAfterDiff()); return toString; } protected Function<String, ? extends Iterable<String>> createTokenizer() { try { return annotation.tokenizer().newInstance(); } catch (Throwable e) { throw new RuntimeException("Error instantiating " + annotation.tokenizer(), e); } } public StringDiffExpectation getAnnotation() { return annotation; } @Creates public IStringDiffExpectation create() { return this; } }