• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fslev / jtest-utils / #1041

28 Oct 2026 12:58PM UTC coverage: 93.61% (+0.04%) from 93.566%
#1041

push

fslevoaca-ionos
[maven-release-plugin] prepare for next development iteration

542 of 579 relevant lines covered (93.61%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.14
/src/main/java/io/jtest/utils/matcher/StringMatcher.java
1
package io.jtest.utils.matcher;
2

3
import com.fasterxml.jackson.core.StreamReadConstraints;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import io.jtest.utils.common.RegexUtils;
6
import io.jtest.utils.common.StringParser;
7
import io.jtest.utils.exceptions.InvalidTypeException;
8
import io.jtest.utils.matcher.condition.MatchCondition;
9
import org.junit.jupiter.api.AssertionFailureBuilder;
10

11
import java.util.HashMap;
12
import java.util.List;
13
import java.util.Map;
14
import java.util.Set;
15
import java.util.regex.Pattern;
16
import java.util.regex.PatternSyntaxException;
17

18
/**
19
 * Matches Objects as Strings
20
 */
21
public class StringMatcher extends AbstractObjectMatcher<Object> {
22
    public static final String CAPTURE_PLACEHOLDER_PREFIX = "~[";
23
    public static final String CAPTURE_PLACEHOLDER_SUFFIX = "]";
24
    private static final Pattern captureGroupPattern = Pattern.compile(Pattern.quote(CAPTURE_PLACEHOLDER_PREFIX) + "(.*?)" + Pattern.quote(CAPTURE_PLACEHOLDER_SUFFIX),
1 ✔
25
            Pattern.DOTALL | Pattern.MULTILINE);
26
    private static final ObjectMapper MAPPER = new ObjectMapper();
1 ✔
27

28
    static {
29
        MAPPER.getFactory().setStreamReadConstraints(StreamReadConstraints.builder()
1 ✔
30
                .maxNestingDepth(Integer.MAX_VALUE).maxNumberLength(Integer.MAX_VALUE).maxStringLength(Integer.MAX_VALUE).build());
1 ✔
31
    }
1 ✔
32

33
    public StringMatcher(String message, Object expected, Object actual, Set<MatchCondition> matchConditions) throws InvalidTypeException {
34
        super(message, expected, actual, matchConditions);
1 ✔
35
    }
1 ✔
36

37
    @Override
38
    Object convert(Object value) {
39
        return value;
1 ✔
40
    }
41

42
    @Override
43
    protected String matchTypeSuffix() {
44
        return "Strings do not match" + System.lineSeparator() + System.lineSeparator() + ASSERTION_ERROR_HINT_MESSAGE +
1 ✔
45
                System.lineSeparator() + System.lineSeparator();
1 ✔
46
    }
47

48
    @Override
49
    protected String negativeMatchMessage() {
50
        return System.lineSeparator() + "Strings match!" + System.lineSeparator() + ASSERTION_ERROR_HINT_MESSAGE +
1 ✔
51
                System.lineSeparator() + System.lineSeparator();
1 ✔
52
    }
53

54
    @Override
55
    public Map<String, Object> match() {
56
        if (matchConditions.remove(MatchCondition.DO_NOT_MATCH)) {
1 ✔
57
            try {
58
                positiveMatch();
1 ✔
59
            } catch (AssertionError e) {
1 ✔
60
                return new HashMap<>();
1 ✔
61
            }
1 ✔
62
            AssertionFailureBuilder.assertionFailure().message(negativeMatchMessage).expected(expected).actual(actual)
1 ✔
63
                    .includeValuesInMessage(false).buildAndThrow();
×
64
        }
65
        return positiveMatch();
1 ✔
66
    }
67

68
    private Map<String, Object> positiveMatch() {
69
        if (matchesWithNull()) {
1 ✔
70
            return new HashMap<>();
1 ✔
71
        }
72

73
        String expectedString = convertToString(expected);
1 ✔
74
        String actualString = convertToString(actual);
1 ✔
75
        List<String> placeholderNames = StringParser.captureValues(expectedString, captureGroupPattern);
1 ✔
76

77
        if (isStandalonePlaceholder(expectedString, placeholderNames)) {
1 ✔
78
            return captureActualAsStandalonePlaceholder(placeholderNames.get(0));
1 ✔
79
        }
80
        if (actual == null) {
1 ✔
81
            AssertionFailureBuilder.assertionFailure().message(message).expected(expected).actual(null).buildAndThrow();
×
82
        }
83
        if (!placeholderNames.isEmpty()) {
1 ✔
84
            return matchWithCaptureGroups(expectedString, actualString, placeholderNames);
1 ✔
85
        }
86
        return matchAsRegexOrLiteral(expectedString, actualString);
1 ✔
87
    }
88

89
    /**
90
     * @return the name inside {@code ~[name]} if {@code value} is exactly one placeholder, otherwise null
91
     */
92
    public static String standalonePlaceholderName(String value) {
93
        List<String> placeholderNames = StringParser.captureValues(value, captureGroupPattern);
1 ✔
94
        return isStandalonePlaceholder(value, placeholderNames) ? placeholderNames.get(0) : null;
1 ✔
95
    }
96

97
    private static boolean isStandalonePlaceholder(String expectedString, List<String> placeholderNames) {
98
        return placeholderNames.size() == 1
1 ✔
99
                && expectedString.equals(CAPTURE_PLACEHOLDER_PREFIX + placeholderNames.get(0) + CAPTURE_PLACEHOLDER_SUFFIX);
1 ✔
100
    }
101

102
    private Map<String, Object> captureActualAsStandalonePlaceholder(String placeholder) {
103
        Map<String, Object> properties = new HashMap<>();
1 ✔
104
        properties.put(placeholder, actual);
1 ✔
105
        return properties;
1 ✔
106
    }
107

108
    private Map<String, Object> matchWithCaptureGroups(String expectedString, String actualString, List<String> placeholderNames) {
109
        Pattern pattern = patternWithPlaceholdersAsCaptureGroups(expectedString, placeholderNames,
1 ✔
110
                matchConditions.contains(MatchCondition.REGEX_DISABLED));
1 ✔
111
        List<String> capturedValues = StringParser.captureValues(actualString, pattern, true);
1 ✔
112
        if (capturedValues.isEmpty()) {
1 ✔
113
            AssertionFailureBuilder.assertionFailure().message(message).expected(expected).actual(actual).buildAndThrow();
×
114
        }
115
        Map<String, Object> properties = new HashMap<>();
1 ✔
116
        int limit = Math.min(capturedValues.size(), placeholderNames.size());
1 ✔
117
        for (int i = 0; i < limit; i++) {
1 ✔
118
            properties.put(placeholderNames.get(i), capturedValues.get(i));
1 ✔
119
        }
120
        return properties;
1 ✔
121
    }
122

123
    private Map<String, Object> matchAsRegexOrLiteral(String expectedString, String actualString) {
124
        if (matchConditions.contains(MatchCondition.REGEX_DISABLED)) {
1 ✔
125
            if (!expectedString.equals(actualString)) {
1 ✔
126
                AssertionFailureBuilder.assertionFailure().message(message).expected(expected).actual(actual).buildAndThrow();
×
127
            }
128
            return new HashMap<>();
1 ✔
129
        }
130
        try {
131
            Pattern pattern = Pattern.compile(expectedString, Pattern.DOTALL | Pattern.MULTILINE);
1 ✔
132
            if (!pattern.matcher(actualString).matches()) {
1 ✔
133
                AssertionFailureBuilder.assertionFailure().message(message).expected(expected).actual(actual).buildAndThrow();
×
134
            }
135
        } catch (PatternSyntaxException e) {
1 ✔
136
            if (!expectedString.equals(actual)) {
1 ✔
137
                AssertionFailureBuilder.assertionFailure().message(message).expected(expected).actual(actual).buildAndThrow();
×
138
            }
139
        }
1 ✔
140
        return new HashMap<>();
1 ✔
141
    }
142

143
    private boolean matchesWithNull() {
144
        if (expected == null) {
1 ✔
145
            if (actual != null) {
1 ✔
146
                AssertionFailureBuilder.assertionFailure().message(message).expected(null).actual(actual).buildAndThrow();
×
147
            } else {
148
                return true;
1 ✔
149
            }
150
        }
151
        return false;
1 ✔
152
    }
153

154
    private static String convertToString(Object value) {
155
        if (value instanceof String s) {
1 ✔
156
            return s;
1 ✔
157
        }
158
        if (value instanceof Number || value instanceof Boolean || value instanceof Character) {
1 ✔
159
            return value.toString();
1 ✔
160
        }
161
        try {
162
            return MAPPER.convertValue(value, String.class);
1 ✔
163
        } catch (IllegalArgumentException ignored) {
1 ✔
164
            return value.toString();
1 ✔
165
        }
166
    }
167

168
    private static Pattern patternWithPlaceholdersAsCaptureGroups(String source, List<String> placeholderNames, boolean regexDisabled) {
169
        String s = source;
1 ✔
170
        boolean allowOtherRegexes = RegexUtils.isRegex(source) && !regexDisabled;
1 ✔
171
        for (String key : placeholderNames) {
1 ✔
172
            s = s.replace(CAPTURE_PLACEHOLDER_PREFIX + key + CAPTURE_PLACEHOLDER_SUFFIX, allowOtherRegexes ? "(.*)" : "\\E(.*)\\Q");
1 ✔
173
        }
1 ✔
174
        return Pattern.compile(allowOtherRegexes ? s : "\\Q" + s + "\\E", Pattern.DOTALL | Pattern.MULTILINE);
1 ✔
175
    }
176
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc