> {
10 | /**
11 | * Appends the regular expression. The value is not changed or sanitized in any way.
12 | *
13 | * This should only be used as a last resort when other methods cannot satisfy the expression you are looking for.
14 | * To avoid issues with other methods, make sure to encapsulate your regex with an unnamed group.
15 | * @param regex The regular expression.
16 | * @return This builder.
17 | */
18 | T regexFromString(String regex);
19 |
20 | /**
21 | * See {@link #add(ReadableRegexPattern)}.
22 | * @param regexBuilder The regular expression.
23 | * @return This builder.
24 | */
25 | default T add(ReadableRegex> regexBuilder) {
26 | return add(regexBuilder.build());
27 | }
28 |
29 | /**
30 | * Appends the regular expression created using another builder instance to this builder. The regular expression
31 | * is surrounded in a non-capturing group {@code (?:...)}.
32 | * @param pattern The pattern.
33 | * @return This builder.
34 | */
35 | T add(ReadableRegexPattern pattern);
36 |
37 | /**
38 | * Appends a literal expression. All metacharacters are escaped.
39 | * @param literalValue The value to add.
40 | * @return This builder.
41 | */
42 | T literal(String literalValue);
43 |
44 | /**
45 | * Adds a digit. This is the same as {@code [0-9]}.
46 | * @return This builder.
47 | */
48 | T digit();
49 |
50 | /**
51 | * Adds a whitespace. This is the same as {@code \s}.
52 | * @return This builder.
53 | */
54 | T whitespace();
55 |
56 | /**
57 | * Adds a tab. This is the same as {@code \t}.
58 | * @return This builder.
59 | */
60 | T tab();
61 |
62 | /**
63 | * Adds either or block. This is the same as {@code (?:X|Y)}, where {@code X} and {@code Y} are given regular expressions.
64 | * @param regexBuilders Regular expressions for which one needs to match.
65 | * @return This builder.
66 | */
67 | T oneOf(ReadableRegex>... regexBuilders);
68 |
69 | /**
70 | * Adds a specified range. This is the same as {@code [a-z]}.
71 | *
72 | * Example: {@code range('a', 'f', '0', '9')} comes down to {@code [a-f0-9]}.
73 | * @param boundaries All the boundaries. You must supply an even amount of arguments.
74 | * @return This builder.
75 | */
76 | T range(char... boundaries);
77 |
78 | /**
79 | * Adds a negated specified range. This is the same as {@code [^a-z]}.
80 | *
81 | * Example: {@code notInRange('a', 'f', '0', '9')} comes down to {@code [^a-f0-9]}.
82 | * @param boundaries All the boundaries. You must supply an even amount of arguments.
83 | * @return This builder.
84 | */
85 | T notInRange(char... boundaries);
86 |
87 | /**
88 | * Adds a range with the specified characters. This is the same as {@code [...]}.
89 | *
90 | * Example: {@code anyCharacterOf("abc")} comes down to {@code [abc]}.
91 | * @param characters The characters to match.
92 | * @return This builder.
93 | */
94 | T anyCharacterOf(String characters);
95 |
96 | /**
97 | * Adds a negated range with the specified characters. This is the same as {@code [^...]}.
98 | *
99 | * Example: {@code anyCharacterExcept("abc")} comes down to {@code [^abc]}.
100 | * @param characters The characters to match.
101 | * @return This builder.
102 | */
103 | T anyCharacterExcept(String characters);
104 |
105 | /**
106 | * Adds a word character. This is the same as {@code \w}.
107 | * @return This builder.
108 | */
109 | T wordCharacter();
110 |
111 | /**
112 | * Adds a non word character. This is the same as {@code \W}.
113 | * @return This builder.
114 | */
115 | T nonWordCharacter();
116 |
117 | /**
118 | * Adds a word boundary. This is the same as {@code \b}.
119 | * @return This builder.
120 | */
121 | T wordBoundary();
122 |
123 | /**
124 | * Adds a non word boundary. This is the same as {@code \B}.
125 | * @return This builder.
126 | */
127 | T nonWordBoundary();
128 |
129 | /**
130 | * Adds any character. This is the same as {@code .}.
131 | *
132 | * Note that you have to enable {@link PatternFlag#DOT_ALL} to match line terminators.
133 | * @return This builder.
134 | */
135 | T anyCharacter();
136 |
137 | /**
138 | * Adds a start of line anchor. This is the same as {@code ^}.
139 | *
140 | * Note that this only works if the flag {@link PatternFlag#MULTILINE} is enabled. This is done automatically when
141 | * using this method. If you want to match the start of the input instead, please use {@link #startOfInput()}.
142 | * @return This builder.
143 | */
144 | T startOfLine();
145 |
146 | /**
147 | * Adds a start of input anchor. This is the same as {@code \A}.
148 | * @return This builder.
149 | */
150 | T startOfInput();
151 |
152 | /**
153 | * Adds an end of line anchor. This is the same as {@code $}.
154 | *
155 | * Note that this only works if the flag {@link PatternFlag#MULTILINE} is enabled. This is done automatically when
156 | * using this method. If you want to match the end of the input instead, please use {@link #endOfInput()}.
157 | * @return This builder.
158 | */
159 | T endOfLine();
160 |
161 | /**
162 | * Adds end of input anchor. This is the same as {@code \z}.
163 | * @return This builder.
164 | */
165 | T endOfInput();
166 | }
167 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/SyntacticSugarBuilder.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
4 |
5 | /**
6 | * Builder interface with methods to enhance user experience using shortcut methods.
7 | */
8 | public interface SyntacticSugarBuilder> extends StandaloneBlockBuilder, QuantifierBuilder, GroupBuilder {
9 | /*
10 | START of methods related to standalone blocks.
11 | */
12 |
13 | /**
14 | * Matches a word. This is the same as {@code \w+}
15 | *
16 | * Syntactic sugar for "{@link #wordCharacter()}.{@link #oneOrMore()}".
17 | * @return This builder.
18 | */
19 | default T word() {
20 | return wordCharacter().oneOrMore();
21 | }
22 |
23 | /**
24 | * Matches anything. This is the same as {@code .*}. Note that you need to enable the flag {@link PatternFlag#DOT_ALL}
25 | * to also match new lines with this method.
26 | *
27 | * Syntactic sugar for "{@link #anyCharacter()}.{@link #zeroOrMore()}".
28 | * @return This builder.
29 | */
30 | default T anything() {
31 | return anyCharacter().zeroOrMore();
32 | }
33 |
34 | /**
35 | * Adds a universal line break. This is the same as {@code \r\n|\n}.
36 | * @return This builder.
37 | */
38 | default T lineBreak() {
39 | return oneOf(regex("\\r\\n?"), regex("\\n"));
40 | }
41 |
42 | /*
43 | START of methods related to groups.
44 | */
45 |
46 | /**
47 | * Adds a regular expression inside a group.
48 | *
49 | * Syntactic sugar for "{@link #startGroup()}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
50 | * {@code .startGroup().add(regexBuilder).endGroup()}.
51 | * @param regexBuilder The regular expression.
52 | * @return This builder.
53 | */
54 | default T group(ReadableRegex> regexBuilder) {
55 | return startGroup().add(regexBuilder).endGroup();
56 | }
57 |
58 | /**
59 | * Adds a regular expression inside a group.
60 | *
61 | * Syntactic sugar for "{@link #startGroup(String)}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
62 | * @param groupName The name of the group.
63 | * @param regexBuilder The regular expression.
64 | * @return This builder.
65 | */
66 | default T group(String groupName, ReadableRegex> regexBuilder) {
67 | return startGroup(groupName).add(regexBuilder).endGroup();
68 | }
69 |
70 | /**
71 | * Adds a regular expression inside a positive lookbehind block.
72 | *
73 | * Syntactic sugar for "{@link #startPositiveLookbehind()}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
74 | * @param regexBuilder The regular expression.
75 | * @return This builder.
76 | */
77 | default T positiveLookbehind(ReadableRegex> regexBuilder) {
78 | return startPositiveLookbehind().add(regexBuilder).endGroup();
79 | }
80 |
81 | /**
82 | * Adds a regular expression inside a positive lookbehind block.
83 | *
84 | * Syntactic sugar for "{@link #startNegativeLookbehind()}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
85 | * @param regexBuilder The regular expression.
86 | * @return This builder.
87 | */
88 | default T negativeLookbehind(ReadableRegex> regexBuilder) {
89 | return startNegativeLookbehind().add(regexBuilder).endGroup();
90 | }
91 |
92 | /**
93 | * Adds a regular expression inside a positive lookbehind block.
94 | *
95 | * Syntactic sugar for "{@link #startPositiveLookahead()}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
96 | * @param regexBuilder The regular expression.
97 | * @return This builder.
98 | */
99 | default T positiveLookahead(ReadableRegex> regexBuilder) {
100 | return startPositiveLookahead().add(regexBuilder).endGroup();
101 | }
102 |
103 | /**
104 | * Adds a regular expression inside a positive lookbehind block.
105 | *
106 | * Syntactic sugar for "{@link #startNegativeLookahead()}.{@link #add(ReadableRegex)}.{@link #endGroup()}".
107 | * @param regexBuilder The regular expression.
108 | * @return This builder.
109 | */
110 | default T negativeLookahead(ReadableRegex> regexBuilder) {
111 | return startNegativeLookahead().add(regexBuilder).endGroup();
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/MethodOrderChecker.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal;
2 |
3 | import io.github.ricoapon.readableregex.FinishBuilder;
4 | import io.github.ricoapon.readableregex.GroupBuilder;
5 | import io.github.ricoapon.readableregex.IncorrectConstructionException;
6 | import io.github.ricoapon.readableregex.QuantifierBuilder;
7 | import io.github.ricoapon.readableregex.ReadableRegex;
8 | import io.github.ricoapon.readableregex.StandaloneBlockBuilder;
9 |
10 | /**
11 | * Class for maintaining the status of a building a regular expression and checking whether the execution order is valid.
12 | */
13 | public class MethodOrderChecker {
14 | /** The types of methods that can be executed by {@link ReadableRegex}. */
15 | enum Method {
16 | /** Methods from the interface {@link StandaloneBlockBuilder}. */
17 | STANDALONE_BLOCK,
18 | /** Methods from the interface {@link QuantifierBuilder}. */
19 | QUANTIFIER,
20 | /** Methods {@link QuantifierBuilder#reluctant()} and {@link QuantifierBuilder#possessive()}. */
21 | RELUCTANT_OR_POSSESSIVE,
22 | /** Methods from the interface {@link FinishBuilder}. */
23 | FINISH,
24 | /** Starting a group with a method from the interface {@link GroupBuilder}. */
25 | START_GROUP,
26 | /** Ending a group with the method {@link GroupBuilder#endGroup()}. */
27 | END_GROUP
28 | }
29 |
30 | /** Indicates if a quantifier is allowed as the next method. Start with {@code false}, because you cannot start with a quantifier. */
31 | private boolean isQuantifierPossibleAfterThisMethod = false;
32 |
33 | /** Indicates if the previous method was {@link Method#QUANTIFIER}. */
34 | private boolean wasPreviousMethodAQuantifier = false;
35 |
36 | /** Counts how many groups are started and are still left open. These must be closed before finishing. */
37 | private int nrOfGroupsStarted = 0;
38 |
39 | /**
40 | * Checks if a method can be called. If not, it will throw an {@link IncorrectConstructionException}.
41 | * @param method The method to execute.
42 | */
43 | public void checkCallingMethod(Method method) {
44 | if (method == Method.STANDALONE_BLOCK) {
45 | standaloneBlock();
46 | } else if (method == Method.QUANTIFIER) {
47 | quantifier();
48 | } else if (method == Method.RELUCTANT_OR_POSSESSIVE) {
49 | reluctantOrPossessive();
50 | } else if (method == Method.FINISH) {
51 | finish();
52 | } else if (method == Method.START_GROUP) {
53 | startGroup();
54 | } else if (method == Method.END_GROUP) {
55 | endGroup();
56 | }
57 | }
58 |
59 | private void standaloneBlock() {
60 | isQuantifierPossibleAfterThisMethod = true;
61 | wasPreviousMethodAQuantifier = false;
62 | }
63 |
64 | private void quantifier() {
65 | if (!isQuantifierPossibleAfterThisMethod) {
66 | throw new IncorrectConstructionException("You cannot add a quantifier after a quantifier. Remove one of the incorrect quantifiers. " +
67 | "Or, if you haven't done anything yet, you started with a quantifier. That is not possible.");
68 | }
69 |
70 | isQuantifierPossibleAfterThisMethod = false;
71 | wasPreviousMethodAQuantifier = true;
72 | }
73 |
74 | private void reluctantOrPossessive() {
75 | if (!wasPreviousMethodAQuantifier) {
76 | throw new IncorrectConstructionException("You can only use the reluctant or possessive method after a quantifier. " +
77 | "Remove the method call reluctant() or possessive(), or place it after a quantifier.");
78 | }
79 |
80 | isQuantifierPossibleAfterThisMethod = false;
81 | wasPreviousMethodAQuantifier = false;
82 | }
83 |
84 | private void finish() {
85 | if (nrOfGroupsStarted != 0) {
86 | throw new IncorrectConstructionException("You forgot to close all the groups that have started. Please close them all using the endGroup() method.");
87 | }
88 | }
89 |
90 | private void startGroup() {
91 | nrOfGroupsStarted++;
92 | isQuantifierPossibleAfterThisMethod = false;
93 | wasPreviousMethodAQuantifier = false;
94 | }
95 |
96 | private void endGroup() {
97 | if (nrOfGroupsStarted == 0) {
98 | throw new IncorrectConstructionException("You cannot close a group, since none have started. " +
99 | "Remove this method call or start a group.");
100 | }
101 | nrOfGroupsStarted--;
102 | isQuantifierPossibleAfterThisMethod = true;
103 | wasPreviousMethodAQuantifier = false;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/ReadableRegexBuilder.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal;
2 |
3 | import io.github.ricoapon.readableregex.PatternFlag;
4 | import io.github.ricoapon.readableregex.ReadableRegex;
5 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.List;
10 | import java.util.Objects;
11 | import java.util.regex.Pattern;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * Implementation that builds the regular expressions.
16 | */
17 | public abstract class ReadableRegexBuilder> implements ReadableRegex {
18 | /** The internal regular expression. This field should only be modified using the {@link #_addRegex(String)} method. */
19 | private final StringBuilder regexBuilder = new StringBuilder();
20 |
21 | /** Indicates whether the flag {@link PatternFlag#MULTILINE} should be enabled when building the pattern object. */
22 | private boolean enableMultilineFlag = false;
23 |
24 | /** List of group names in order. If the name is {@code null}, it means it is an unnamed group. */
25 | private final List groups = new ArrayList<>();
26 |
27 | @SuppressWarnings("MagicConstant")
28 | @Override
29 | public ReadableRegexPattern buildWithFlags(PatternFlag... patternFlags) {
30 | int flags = Arrays.stream(patternFlags).map(PatternFlag::getJdkPatternFlagCode)
31 | .reduce(0, (integer, integer2) -> integer | integer2);
32 |
33 | // If we should enable multiline, make sure it is part of the flags variable.
34 | if (enableMultilineFlag && (flags & PatternFlag.MULTILINE.getJdkPatternFlagCode()) == 0) {
35 | flags = flags | PatternFlag.MULTILINE.getJdkPatternFlagCode();
36 | }
37 |
38 | Pattern pattern = Pattern.compile(regexBuilder.toString(), flags);
39 | return new ReadableRegexPatternImpl(pattern, groups);
40 | }
41 |
42 | /**
43 | * @return {@code this} casted to {@code T}.
44 | */
45 | private T thisT() {
46 | //noinspection unchecked
47 | return (T) this;
48 | }
49 |
50 | /**
51 | * Adds the regular expression to {@link #regexBuilder}.
52 | * @param regex The regular expression.
53 | * @return This builder.
54 | */
55 | private T _addRegex(String regex) {
56 | Objects.requireNonNull(regex);
57 | regexBuilder.append(regex);
58 | return thisT();
59 | }
60 |
61 | @Override
62 | public T regexFromString(String regex) {
63 | return _addRegex(regex);
64 | }
65 |
66 | @Override
67 | public T add(ReadableRegexPattern pattern) {
68 | Objects.requireNonNull(pattern);
69 | String regexToInclude = pattern.toString();
70 |
71 | // Wrap in an unnamed group, to make sure that quantifiers work on the entire block.
72 | return _addRegex("(?:" + regexToInclude + ")");
73 | }
74 |
75 | @Override
76 | public T literal(String literalValue) {
77 | Objects.requireNonNull(literalValue);
78 | // Surround input with \Q\E to make sure that all the meta characters are escaped.
79 | // Wrap in an unnamed group, to make sure that quantifiers work on the entire block.
80 | return _addRegex("(?:\\Q" + literalValue + "\\E)");
81 | }
82 |
83 | @Override
84 | public T digit() {
85 | return _addRegex("\\d");
86 | }
87 |
88 | @Override
89 | public T whitespace() {
90 | return _addRegex("\\s");
91 | }
92 |
93 | @Override
94 | public T tab() {
95 | return _addRegex("\\t");
96 | }
97 |
98 | @Override
99 | public T oneOf(ReadableRegex>... regexBuilders) {
100 | String middlePart = Arrays.stream(regexBuilders)
101 | .map(ReadableRegex::build)
102 | .map(ReadableRegexPattern::toString)
103 | .collect(Collectors.joining("|"));
104 |
105 | return _addRegex("(?:" + middlePart + ")");
106 | }
107 |
108 | @Override
109 | public T range(char... boundaries) {
110 | if (boundaries.length % 2 != 0) {
111 | throw new IllegalArgumentException("You have to supply an even amount of boundaries.");
112 | } else if (boundaries.length == 0) {
113 | throw new IllegalArgumentException("An empty range is pointless. Please supply boundaries!");
114 | }
115 |
116 | StringBuilder expression = new StringBuilder("[");
117 | for (int i = 0; i < boundaries.length; i += 2) {
118 | expression.append(boundaries[i])
119 | .append('-')
120 | .append(boundaries[i + 1]);
121 | }
122 | expression.append("]");
123 |
124 | return _addRegex(expression.toString());
125 | }
126 |
127 | @Override
128 | public T notInRange(char... boundaries) {
129 | if (boundaries.length % 2 != 0) {
130 | throw new IllegalArgumentException("You have to supply an even amount of boundaries.");
131 | } else if (boundaries.length == 0) {
132 | throw new IllegalArgumentException("An empty range is pointless. Please supply boundaries!");
133 | }
134 |
135 | StringBuilder expression = new StringBuilder("[^");
136 | for (int i = 0; i < boundaries.length; i += 2) {
137 | expression.append(boundaries[i])
138 | .append('-')
139 | .append(boundaries[i + 1]);
140 | }
141 | expression.append("]");
142 |
143 | return _addRegex(expression.toString());
144 | }
145 |
146 | @Override
147 | public T anyCharacterOf(String characters) {
148 | Objects.requireNonNull(characters);
149 | if (characters.length() == 0) {
150 | throw new IllegalArgumentException("An empty range is pointless. Please supply boundaries!");
151 | }
152 |
153 | return _addRegex("[" + characters + "]");
154 | }
155 |
156 | @Override
157 | public T anyCharacterExcept(String characters) {
158 | Objects.requireNonNull(characters);
159 | if (characters.length() == 0) {
160 | throw new IllegalArgumentException("An empty range is pointless. Please supply boundaries!");
161 | }
162 |
163 | return _addRegex("[^" + characters + "]");
164 | }
165 |
166 | @Override
167 | public T wordCharacter() {
168 | return _addRegex("\\w");
169 | }
170 |
171 | @Override
172 | public T nonWordCharacter() {
173 | return _addRegex("\\W");
174 | }
175 |
176 | @Override
177 | public T wordBoundary() {
178 | return _addRegex("\\b");
179 | }
180 |
181 | @Override
182 | public T nonWordBoundary() {
183 | return _addRegex("\\B");
184 | }
185 |
186 | @Override
187 | public T anyCharacter() {
188 | return _addRegex(".");
189 | }
190 |
191 | @Override
192 | public T startOfLine() {
193 | enableMultilineFlag = true;
194 | // Surround with an unnamed group, to make sure that it can be followed up with quantifiers.
195 | return _addRegex("(?:^)");
196 | }
197 |
198 | @Override
199 | public T startOfInput() {
200 | return _addRegex("\\A");
201 | }
202 |
203 | @Override
204 | public T endOfLine() {
205 | enableMultilineFlag = true;
206 | // Surround with an unnamed group, to make sure that it can be followed up with quantifiers.
207 | return _addRegex("(?:$)");
208 | }
209 |
210 | @Override
211 | public T endOfInput() {
212 | return _addRegex("\\z");
213 | }
214 |
215 | @Override
216 | public T oneOrMore() {
217 | return _addRegex("+");
218 | }
219 |
220 | @Override
221 | public T optional() {
222 | return _addRegex("?");
223 | }
224 |
225 | @Override
226 | public T zeroOrMore() {
227 | return _addRegex("*");
228 | }
229 |
230 | private T _countRange(int n, Integer m) {
231 | if (n < 0 || m <= 0) {
232 | throw new IllegalArgumentException("The number of times the block should repeat must be larger than zero.");
233 | } else if (n > m) {
234 | throw new IllegalArgumentException("The ranges of the repeating block must be valid. Please make n smaller than m.");
235 | }
236 |
237 | if (Integer.MAX_VALUE == m) {
238 | return _addRegex("{" + n + ",}");
239 | }
240 |
241 | return _addRegex("{" + n + "," + m + "}");
242 | }
243 |
244 | @Override
245 | public T exactlyNTimes(int n) {
246 | return _countRange(n, n);
247 | }
248 |
249 | @Override
250 | public T atLeastNTimes(int n) {
251 | return _countRange(n, Integer.MAX_VALUE);
252 | }
253 |
254 | @Override
255 | public T betweenNAndMTimes(int n, int m) {
256 | return _countRange(n, m);
257 | }
258 |
259 | @Override
260 | public T reluctant() {
261 | return _addRegex("?");
262 | }
263 |
264 | @Override
265 | public T possessive() {
266 | return _addRegex("+");
267 | }
268 |
269 | @Override
270 | public T startGroup() {
271 | groups.add(null);
272 | return _addRegex("(");
273 | }
274 |
275 | @Override
276 | public T startGroup(String groupName) {
277 | Objects.requireNonNull(groupName);
278 | if (!Pattern.matches("[a-zA-Z][a-zA-Z0-9]*", groupName)) {
279 | throw new IllegalArgumentException("The group name '" + groupName + "' is not valid: it should start with a letter " +
280 | "and only contain letters and digits.");
281 | }
282 |
283 | groups.add(groupName);
284 | return _addRegex("(?<" + groupName + ">");
285 | }
286 |
287 | @Override
288 | public T startUnnamedGroup() {
289 | return _addRegex("(?:");
290 | }
291 |
292 | @Override
293 | public T startPositiveLookbehind() {
294 | return _addRegex("(?<=");
295 | }
296 |
297 | @Override
298 | public T startNegativeLookbehind() {
299 | return _addRegex("(?> extends ReadableRegexBuilder {
14 | /** Object for maintaining the status of calling methods. */
15 | private final MethodOrderChecker methodOrderChecker;
16 |
17 | /**
18 | * Constructor.
19 | * @param methodOrderChecker Object for checking that methods are called in the right order.
20 | */
21 | public ReadableRegexOrderChecker(MethodOrderChecker methodOrderChecker) {
22 | this.methodOrderChecker = methodOrderChecker;
23 | }
24 |
25 | @Override
26 | public ReadableRegexPattern buildWithFlags(PatternFlag... patternFlags) {
27 | methodOrderChecker.checkCallingMethod(FINISH);
28 | return super.buildWithFlags(patternFlags);
29 | }
30 |
31 | @Override
32 | public T regexFromString(String regex) {
33 | // We are not actually sure that the regex is a standalone block. If we don't do this however, it is never possible
34 | // to add a quantifier after this block. I leave the user responsible for the outcome.
35 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
36 | return super.regexFromString(regex);
37 | }
38 |
39 | @Override
40 | public T add(ReadableRegexPattern pattern) {
41 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
42 | return super.add(pattern);
43 | }
44 |
45 | @Override
46 | public T literal(String literalValue) {
47 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
48 | return super.literal(literalValue);
49 | }
50 |
51 | @Override
52 | public T digit() {
53 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
54 | return super.digit();
55 | }
56 |
57 | @Override
58 | public T whitespace() {
59 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
60 | return super.whitespace();
61 | }
62 |
63 | @Override
64 | public T tab() {
65 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
66 | return super.tab();
67 | }
68 |
69 | @Override
70 | public T oneOf(ReadableRegex>... regexBuilders) {
71 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
72 | return super.oneOf(regexBuilders);
73 | }
74 |
75 | @Override
76 | public T range(char... boundaries) {
77 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
78 | return super.range(boundaries);
79 | }
80 |
81 | @Override
82 | public T notInRange(char... boundaries) {
83 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
84 | return super.notInRange(boundaries);
85 | }
86 |
87 | @Override
88 | public T anyCharacterOf(String characters) {
89 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
90 | return super.anyCharacterOf(characters);
91 | }
92 |
93 | @Override
94 | public T anyCharacterExcept(String characters) {
95 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
96 | return super.anyCharacterExcept(characters);
97 | }
98 |
99 | @Override
100 | public T wordCharacter() {
101 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
102 | return super.wordCharacter();
103 | }
104 |
105 | @Override
106 | public T nonWordCharacter() {
107 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
108 | return super.nonWordCharacter();
109 | }
110 |
111 | @Override
112 | public T wordBoundary() {
113 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
114 | return super.wordBoundary();
115 | }
116 |
117 | @Override
118 | public T nonWordBoundary() {
119 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
120 | return super.nonWordBoundary();
121 | }
122 |
123 | @Override
124 | public T anyCharacter() {
125 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
126 | return super.anyCharacter();
127 | }
128 |
129 | @Override
130 | public T startOfLine() {
131 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
132 | return super.startOfLine();
133 | }
134 |
135 | @Override
136 | public T startOfInput() {
137 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
138 | return super.startOfInput();
139 | }
140 |
141 | @Override
142 | public T endOfLine() {
143 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
144 | return super.endOfLine();
145 | }
146 |
147 | @Override
148 | public T endOfInput() {
149 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
150 | return super.endOfInput();
151 | }
152 |
153 | @Override
154 | public T oneOrMore() {
155 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
156 | return super.oneOrMore();
157 | }
158 |
159 | @Override
160 | public T optional() {
161 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
162 | return super.optional();
163 | }
164 |
165 | @Override
166 | public T zeroOrMore() {
167 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
168 | return super.zeroOrMore();
169 | }
170 |
171 | @Override
172 | public T exactlyNTimes(int n) {
173 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
174 | return super.exactlyNTimes(n);
175 | }
176 |
177 | @Override
178 | public T atLeastNTimes(int n) {
179 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
180 | return super.atLeastNTimes(n);
181 | }
182 |
183 | @Override
184 | public T betweenNAndMTimes(int n, int m) {
185 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
186 | return super.betweenNAndMTimes(n, m);
187 | }
188 |
189 | @Override
190 | public T reluctant() {
191 | methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE);
192 | return super.reluctant();
193 | }
194 |
195 | @Override
196 | public T possessive() {
197 | methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE);
198 | return super.possessive();
199 | }
200 |
201 | @Override
202 | public T startGroup() {
203 | methodOrderChecker.checkCallingMethod(START_GROUP);
204 | return super.startGroup();
205 | }
206 |
207 | @Override
208 | public T startGroup(String groupName) {
209 | methodOrderChecker.checkCallingMethod(START_GROUP);
210 | return super.startGroup(groupName);
211 | }
212 |
213 | @Override
214 | public T startUnnamedGroup() {
215 | methodOrderChecker.checkCallingMethod(START_GROUP);
216 | return super.startUnnamedGroup();
217 | }
218 |
219 | @Override
220 | public T startPositiveLookbehind() {
221 | methodOrderChecker.checkCallingMethod(START_GROUP);
222 | return super.startPositiveLookbehind();
223 | }
224 |
225 | @Override
226 | public T startNegativeLookbehind() {
227 | methodOrderChecker.checkCallingMethod(START_GROUP);
228 | return super.startNegativeLookbehind();
229 | }
230 |
231 | @Override
232 | public T startPositiveLookahead() {
233 | methodOrderChecker.checkCallingMethod(START_GROUP);
234 | return super.startPositiveLookahead();
235 | }
236 |
237 | @Override
238 | public T startNegativeLookahead() {
239 | methodOrderChecker.checkCallingMethod(START_GROUP);
240 | return super.startNegativeLookahead();
241 | }
242 |
243 | @Override
244 | public T endGroup() {
245 | methodOrderChecker.checkCallingMethod(END_GROUP);
246 | return super.endGroup();
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/ReadableRegexPatternImpl.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal;
2 |
3 | import io.github.ricoapon.readableregex.PatternFlag;
4 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Set;
10 | import java.util.regex.Matcher;
11 | import java.util.regex.Pattern;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * Implementation of {@link ReadableRegexPattern}.
16 | */
17 | public class ReadableRegexPatternImpl implements ReadableRegexPattern {
18 | private final Pattern pattern;
19 |
20 | /** Maps group index to the name. If the name is null, it means it is an unnamed group. */
21 | private final List groups;
22 |
23 | public ReadableRegexPatternImpl(Pattern pattern, List groups) {
24 | this.pattern = pattern;
25 | this.groups = Collections.unmodifiableList(groups);
26 | }
27 |
28 | @Override
29 | public Matcher matches(String text) {
30 | return pattern.matcher(text);
31 | }
32 |
33 | @Override
34 | public Set enabledFlags() {
35 | return Arrays.stream(PatternFlag.values())
36 | .filter(flag -> (pattern.flags() & flag.getJdkPatternFlagCode()) != 0)
37 | .collect(Collectors.toSet());
38 | }
39 |
40 | @Override
41 | public List groups() {
42 | return groups;
43 | }
44 |
45 | @Override
46 | public Pattern getUnderlyingPattern() {
47 | return pattern;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return pattern.toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/instantiation/ParameterInfo.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal.instantiation;
2 |
3 | /**
4 | * Container for information needed about parameters of a constructor.
5 | */
6 | public class ParameterInfo {
7 | private final String name;
8 | private final Class> type;
9 |
10 | public ParameterInfo(String name, Class> type) {
11 | this.name = name;
12 | this.type = type;
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public Class> getType() {
20 | return type;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/instantiation/RegexObjectInstantiationImpl.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal.instantiation;
2 |
3 | import com.thoughtworks.paranamer.BytecodeReadingParanamer;
4 | import com.thoughtworks.paranamer.Paranamer;
5 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
6 | import io.github.ricoapon.readableregex.RegexObjectInstantiation;
7 | import io.github.ricoapon.readableregex.RegexObjectInstantiationException;
8 |
9 | import javax.inject.Inject;
10 | import java.lang.reflect.Constructor;
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 | import java.util.regex.Matcher;
16 | import java.util.stream.Collectors;
17 |
18 | /**
19 | * Implementation of {@link RegexObjectInstantiation}.
20 | * @param The type of the object to instantiate.
21 | */
22 | public class RegexObjectInstantiationImpl implements RegexObjectInstantiation {
23 | /**
24 | * See {@link RegexObjectInstantiation#instantiateObject(ReadableRegexPattern, String, Class)}.
25 | * @param pattern The pattern.
26 | * @param data The data.
27 | * @param clazz The class of the object to instantiate.
28 | * @return Instance of {@link T}.
29 | */
30 | public T constructObject(ReadableRegexPattern pattern, String data, Class clazz) {
31 | Constructor> constructor = determineConstructorForInjection(clazz);
32 | List parameterInfoList = determineParameterNamesAndTypes(constructor);
33 | checkThatPatternHaveANamedGroupForEachParameter(pattern, clazz, parameterInfoList);
34 | Matcher matcher = createExactMatcher(pattern, data);
35 | Object[] constructorArgs = createConstructorArgs(parameterInfoList, matcher);
36 | return createNewInstance(constructor, constructorArgs);
37 | }
38 |
39 | /**
40 | * Finds the constructor we should use for instantiation. We have the following criteria:
41 | *
42 | * - If there is only one constructor, use that one.
43 | * - If there is more than one constructor, use the constructor that is annotated with {@link Inject}
.
44 | * - If there are multiple constructors annotated with {@link Inject}, throw an exception.
45 | *
46 | * @param clazz The class of the object to instantiate.
47 | * @return The constructor.
48 | */
49 | private Constructor> determineConstructorForInjection(Class clazz) {
50 | if (clazz.getConstructors().length == 1) {
51 | return clazz.getConstructors()[0];
52 | }
53 |
54 | List> validConstructors = Arrays.stream(clazz.getConstructors())
55 | .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
56 | .collect(Collectors.toList());
57 |
58 | if (validConstructors.size() > 1) {
59 | throw new RegexObjectInstantiationException("The class " + clazz.getName() + " has more than one constructor annotated " +
60 | "with @Inject. This is not possible. Fix your code by making sure at most one constructor is annotated with @Inject.");
61 | }
62 |
63 | return validConstructors.get(0);
64 | }
65 |
66 | /**
67 | * Creates a list containing the needed information about the constructor parameters. This contains:
68 | *
69 | * - The name of the parameter.
70 | * - The type of the parameter.
71 | *
72 | * @param constructor The constructor.
73 | * @return List with {@link ParameterInfo}.
74 | */
75 | private List determineParameterNamesAndTypes(Constructor> constructor) {
76 | Paranamer paranamer = new BytecodeReadingParanamer();
77 |
78 | String[] parameterNames = paranamer.lookupParameterNames(constructor);
79 | Class>[] parameterTypes = constructor.getParameterTypes();
80 |
81 | List parameterInfos = new ArrayList<>();
82 | for (int i = 0; i < constructor.getParameters().length; i++) {
83 | parameterInfos.add(new ParameterInfo(parameterNames[i], parameterTypes[i]));
84 | }
85 | return parameterInfos;
86 | }
87 |
88 | /**
89 | * Checks that there exists a parameter name for which there does not exist a group in the pattern.
90 | * @param pattern The pattern.
91 | * @param clazz The class of the object to instantiate.
92 | * @param parameterInfoList The information about the parameters of the constructor.
93 | * @throws RegexObjectInstantiationException If the check failed.
94 | */
95 | private void checkThatPatternHaveANamedGroupForEachParameter(ReadableRegexPattern pattern, Class clazz, List parameterInfoList)
96 | throws RegexObjectInstantiationException {
97 | for (ParameterInfo parameterInfo : parameterInfoList) {
98 | if (!pattern.groups().contains(parameterInfo.getName())) {
99 | throw new RegexObjectInstantiationException("The constructor of the class " + clazz.getName() + " has a parameter with the name '" +
100 | parameterInfo.getName() + "'. But this name does not occur in the given pattern. You can fix this by " +
101 | "changing the pattern to add a group with this name.");
102 | }
103 | }
104 | }
105 |
106 | /**
107 | * Creates a {@link Matcher} based on the given pattern and data.
108 | * @param pattern The pattern.
109 | * @param data The data.
110 | * @return {@link Matcher} for which {@link Matcher#matches()} is already called and returns {@code true}.
111 | * @throws RegexObjectInstantiationException If there is no exact match.
112 | */
113 | private Matcher createExactMatcher(ReadableRegexPattern pattern, String data) throws RegexObjectInstantiationException {
114 | Matcher matcher = pattern.matches(data);
115 | if (!matcher.matches()) {
116 | throw new RegexObjectInstantiationException("The given pattern does not match the given string. Make sure to write your pattern " +
117 | "in such a way that you match the COMPLETE string.");
118 | }
119 | return matcher;
120 | }
121 |
122 | /**
123 | * Creates an array of objects that can be used to call the constructor.
124 | * @param parameterInfoList The information about the parameters of the constructor.
125 | * @param matcher The matcher containing the parameter values.
126 | * @return Constructor arguments.
127 | */
128 | private Object[] createConstructorArgs(List parameterInfoList, Matcher matcher) {
129 | Object[] constructorArgs = new Object[parameterInfoList.size()];
130 | int index = 0;
131 |
132 | for (ParameterInfo parameterInfo : parameterInfoList) {
133 | String argAsString = matcher.group(parameterInfo.getName());
134 | constructorArgs[index] = StringConverter.convertStringTo(parameterInfo.getType(), argAsString);
135 | index++;
136 | }
137 |
138 | return constructorArgs;
139 | }
140 |
141 | /**
142 | * Creates an instance of type {@link T}. Throws an exception if anything goes wrong.
143 | * @param constructor The constructor.
144 | * @param constructorArgs The arguments of the constructor.
145 | * @return Instance of {@link T}.
146 | * @throws RegexObjectInstantiationException If the object could not be instantiated.
147 | */
148 | private T createNewInstance(Constructor> constructor, Object[] constructorArgs) throws RegexObjectInstantiationException {
149 | try {
150 | //noinspection unchecked
151 | return (T) constructor.newInstance(constructorArgs);
152 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
153 | throw new RegexObjectInstantiationException("Could not instantiate class.", e);
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/instantiation/StringConverter.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal.instantiation;
2 |
3 | import io.github.ricoapon.readableregex.RegexObjectInstantiationException;
4 |
5 | import java.util.Collections;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.function.Function;
9 |
10 | /**
11 | * Class with methods to help convert {@link String}s to any other type.
12 | */
13 | public class StringConverter {
14 | private static final Map, Function> CONVERTER_MAP;
15 |
16 | static {
17 | Map, Function> map = new HashMap<>();
18 | CONVERTER_MAP = Collections.unmodifiableMap(map);
19 |
20 | map.put(Byte.class, Byte::valueOf);
21 | map.put(byte.class, Byte::parseByte);
22 | map.put(Short.class, Short::valueOf);
23 | map.put(short.class, Short::parseShort);
24 | map.put(Integer.class, Integer::valueOf);
25 | map.put(int.class, Integer::parseInt);
26 | map.put(Long.class, Long::valueOf);
27 | map.put(long.class, Long::parseLong);
28 | map.put(Float.class, Float::valueOf);
29 | map.put(float.class, Float::parseFloat);
30 | map.put(Double.class, Double::valueOf);
31 | map.put(double.class, Double::parseDouble);
32 | map.put(Boolean.class, Boolean::valueOf);
33 | map.put(boolean.class, Boolean::parseBoolean);
34 | map.put(Character.class, (s) -> s.charAt(0));
35 | map.put(char.class, (s) -> s.charAt(0));
36 | map.put(String.class, Function.identity());
37 | }
38 |
39 | /**
40 | * Converts a {@link String} to an object of a specified class.
41 | * @param clazz The class of the object to convert to.
42 | * @param s The {@link String} to convert.
43 | * @param The type of the object to convert to.
44 | * @return The converted object.
45 | */
46 | public static T convertStringTo(Class clazz, String s) {
47 | if (!CONVERTER_MAP.containsKey(clazz)) {
48 | throw new RegexObjectInstantiationException("Injecting an object of class " + clazz.getName() + " is not supported.");
49 | }
50 |
51 | //noinspection unchecked
52 | return (T) CONVERTER_MAP.get(clazz).apply(s);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/internal/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal implementation of the interfaces.
3 | */
4 | package io.github.ricoapon.readableregex.internal;
5 |
--------------------------------------------------------------------------------
/src/main/java/io/github/ricoapon/readableregex/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * All the public interfaces and classes.
3 | */
4 | package io.github.ricoapon.readableregex;
5 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/Constants.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | /**
4 | * Class with useful constants that can be used in test cases.
5 | */
6 | public class Constants {
7 | public final static String A_TO_Z_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
8 | public final static String A_TO_Z_UPPERCASE = A_TO_Z_LOWERCASE.toUpperCase();
9 | public final static String WORD_CHARACTERS = A_TO_Z_LOWERCASE + A_TO_Z_UPPERCASE + "_";
10 | public static final String DIGITS = "0123456789";
11 | public static final String WHITESPACES = " \t\n\f\r";
12 | public static final String NON_LETTERS = ";'[]{}|?/";
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/FinishTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
7 | import static org.hamcrest.MatcherAssert.assertThat;
8 | import static org.hamcrest.Matchers.equalTo;
9 | import static org.junit.jupiter.api.Assertions.assertThrows;
10 |
11 | /**
12 | * Tests related to methods that are inside {@link FinishBuilder}.
13 | */
14 | @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT", justification = "Lambda inside throwNpeWhenTextToMatchIsNull " +
15 | "triggers SpotBugs, but we know we don't use the return value. We expect to throw an NPE. " +
16 | "For some reason, this only has to be ignored after commit f1a8d20a9611d1f940823e16721d6efaeb4d16ed. Before the " +
17 | "(unrelated!) code was committed, this did not happen.")
18 | class FinishTests {
19 | private final static String REGEX = "a1?";
20 | private final ReadableRegex> readableRegex = regex(REGEX);
21 |
22 | @Test
23 | void underlyingPatternIsExposed() {
24 | assertThat(readableRegex.buildJdkPattern().toString(), equalTo(REGEX));
25 | assertThat(readableRegex.build().getUnderlyingPattern().toString(), equalTo(REGEX));
26 | }
27 |
28 | @Test
29 | void toStringReturnsPattern() {
30 | assertThat(readableRegex.build().toString(), equalTo(REGEX));
31 | }
32 |
33 | @Test
34 | void throwNpeWhenTextToMatchIsNull() {
35 | assertThrows(NullPointerException.class, () -> readableRegex.build().matches(null));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/GroupTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.params.ParameterizedTest;
5 | import org.junit.jupiter.params.provider.ValueSource;
6 |
7 | import java.util.regex.Matcher;
8 |
9 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
10 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.doesntMatchAnythingFrom;
11 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.matchesSomethingFrom;
12 | import static org.hamcrest.MatcherAssert.assertThat;
13 | import static org.hamcrest.Matchers.equalTo;
14 | import static org.junit.jupiter.api.Assertions.assertThrows;
15 |
16 | /**
17 | * Tests related to methods inside {@link GroupBuilder}.
18 | */
19 | class GroupTests {
20 | @Test
21 | void nestedGroupsAreCaptured() {
22 | // Test using start/end syntax.
23 | ReadableRegexPattern pattern = regex().digit().startGroup().digit().startGroup().digit().endGroup().endGroup().build();
24 | Matcher matcher = pattern.matches("123");
25 |
26 | assertThat(matcher.matches(), equalTo(true));
27 | assertThat(matcher.group(1), equalTo("23"));
28 | assertThat(matcher.group(2), equalTo("3"));
29 | }
30 |
31 | @Test
32 | void nestedNamedGroupsAreCaptured() {
33 | // Test using start/end syntax.
34 | String firstGroupName = "first";
35 | String secondGroupName = "second";
36 | ReadableRegexPattern pattern = regex().digit().startGroup(firstGroupName).digit().startGroup(secondGroupName).digit().endGroup().endGroup().build();
37 | Matcher matcher = pattern.matches("123");
38 |
39 | assertThat(matcher.matches(), equalTo(true));
40 | assertThat(matcher.group(firstGroupName), equalTo("23"));
41 | assertThat(matcher.group(secondGroupName), equalTo("3"));
42 | }
43 |
44 | @ParameterizedTest
45 | @ValueSource(strings = {"0a", "a[", "a_", ""})
46 | void invalidGroupNameThrowsIllegalArgumentException(String groupName) {
47 | assertThrows(IllegalArgumentException.class, () -> regex().startGroup(groupName));
48 | }
49 |
50 | @Test
51 | void unnamedGroupsDontCapture() {
52 | ReadableRegexPattern pattern = regex().startUnnamedGroup().digit().endGroup()
53 | .group(regex().digit()).build();
54 |
55 | Matcher matcher = pattern.matches("12");
56 | assertThat(matcher.matches(), equalTo(true));
57 | assertThat(matcher.group(1), equalTo("2"));
58 | }
59 |
60 | @Test
61 | void lookbehindsWork() {
62 | // Test using start/end syntax.
63 | ReadableRegexPattern pattern = regex().startPositiveLookbehind().digit().endGroup().whitespace().build();
64 | assertThat(pattern, matchesSomethingFrom("1 "));
65 | assertThat(pattern, doesntMatchAnythingFrom(" "));
66 |
67 | // Test using start/end syntax.
68 | pattern = regex().startNegativeLookbehind().digit().endGroup().whitespace().build();
69 | assertThat(pattern, doesntMatchAnythingFrom("1 "));
70 | assertThat(pattern, matchesSomethingFrom(" "));
71 | }
72 |
73 | @Test
74 | void lookaheadsWork() {
75 | // Test using start/end syntax.
76 | ReadableRegexPattern pattern = regex().whitespace().startPositiveLookahead().digit().endGroup().build();
77 | assertThat(pattern, matchesSomethingFrom(" 1"));
78 | assertThat(pattern, doesntMatchAnythingFrom(" "));
79 |
80 | // Test using start/end syntax.
81 | pattern = regex().whitespace().startNegativeLookahead().digit().endGroup().build();
82 | assertThat(pattern, doesntMatchAnythingFrom(" 1"));
83 | assertThat(pattern, matchesSomethingFrom(" "));
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/PatternFlagTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
6 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.*;
7 | import static org.hamcrest.MatcherAssert.assertThat;
8 | import static org.hamcrest.Matchers.containsInAnyOrder;
9 | import static org.hamcrest.Matchers.empty;
10 |
11 | /**
12 | * Tests related to enabling specific pattern flags.
13 | */
14 | public class PatternFlagTests {
15 | @Test
16 | void byDefaultNoFlagsAreEnabled() {
17 | ReadableRegexPattern pattern = regex().build();
18 |
19 | assertThat(pattern.enabledFlags(), empty());
20 | }
21 |
22 | @Test
23 | void enabledFlagsAreReturned() {
24 | ReadableRegexPattern pattern = regex().buildWithFlags(PatternFlag.CASE_INSENSITIVE, PatternFlag.DOT_ALL);
25 |
26 | assertThat(pattern.enabledFlags(), containsInAnyOrder(PatternFlag.CASE_INSENSITIVE, PatternFlag.DOT_ALL));
27 | }
28 |
29 | @Test
30 | void caseInsensitiveWorks() {
31 | // No flag means case sensitive matches.
32 | ReadableRegexPattern pattern = regex().literal("a").build();
33 | assertThat(pattern, matchesExactly("a"));
34 | assertThat(pattern, doesntMatchAnythingFrom("A"));
35 |
36 | pattern = regex().literal("a").buildWithFlags(PatternFlag.CASE_INSENSITIVE);
37 | assertThat(pattern, matchesExactly("a"));
38 | assertThat(pattern, matchesExactly("A"));
39 | }
40 |
41 | @Test
42 | void dotAllWorks() {
43 | // No flag means .* does not match new line symbols.
44 | ReadableRegexPattern pattern = regex().anything().build();
45 | assertThat(pattern, doesntMatchExactly("a\na"));
46 |
47 | pattern = regex().anything().buildWithFlags(PatternFlag.DOT_ALL);
48 | assertThat(pattern, matchesExactly("a\na"));
49 | }
50 |
51 | @Test
52 | void multilineWorks() {
53 | // No flag means ^ matches the start of the entire input.
54 | ReadableRegexPattern pattern = regex().regexFromString("^a").build();
55 | assertThat(pattern, doesntMatchAnythingFrom("\na"));
56 |
57 | pattern = regex().regexFromString("^a").buildWithFlags(PatternFlag.MULTILINE);
58 | assertThat(pattern, matchesSomethingFrom("\na"));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/QuantifierTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.regex.Matcher;
6 |
7 | import static io.github.ricoapon.readableregex.Constants.DIGITS;
8 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
9 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.doesntMatchExactly;
10 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.matchesExactly;
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 | import static org.hamcrest.Matchers.equalTo;
13 | import static org.junit.jupiter.api.Assertions.assertThrows;
14 |
15 | /**
16 | * Tests related to methods that are inside {@link QuantifierBuilder}.
17 | */
18 | class QuantifierTests {
19 | @Test
20 | void oneOrMoreMatchesCorrectly() {
21 | ReadableRegexPattern pattern = regex().digit().oneOrMore().build();
22 |
23 | assertThat(pattern, matchesExactly(DIGITS));
24 | assertThat(pattern, doesntMatchExactly(""));
25 | }
26 |
27 | @Test
28 | void optionalMatchesCorrectly() {
29 | ReadableRegexPattern pattern = regex().literal("a").digit().optional().build();
30 |
31 | assertThat(pattern, matchesExactly("a1"));
32 | assertThat(pattern, matchesExactly("a"));
33 | assertThat(pattern, doesntMatchExactly(""));
34 | }
35 |
36 | @Test
37 | void zeroOrMoreMatchesCorrectly() {
38 | ReadableRegexPattern pattern = regex().digit().zeroOrMore().build();
39 |
40 | assertThat(pattern, matchesExactly(""));
41 | assertThat(pattern, matchesExactly("1"));
42 | assertThat(pattern, matchesExactly("11111"));
43 | assertThat(pattern, doesntMatchExactly("a"));
44 | }
45 |
46 | @Test
47 | void countQuantifiersMatchCorrectly() {
48 | ReadableRegexPattern pattern = regex()
49 | .literal("a").exactlyNTimes(2)
50 | .literal("b").atLeastNTimes(2)
51 | .literal("c").atMostNTimes(2)
52 | .literal("d").betweenNAndMTimes(1, 3)
53 | .build();
54 |
55 | assertThat(pattern, matchesExactly("aabbccdd"));
56 | assertThat(pattern, matchesExactly("aabbbddd"));
57 | assertThat(pattern, doesntMatchExactly("abbbddd"));
58 | assertThat(pattern, doesntMatchExactly("aaabbbddd"));
59 | assertThat(pattern, doesntMatchExactly("aabddd"));
60 | assertThat(pattern, doesntMatchExactly("aabbcccddd"));
61 | assertThat(pattern, doesntMatchExactly("aabbccc"));
62 | assertThat(pattern, doesntMatchExactly("aabbcccdddd"));
63 | }
64 |
65 | @Test
66 | void countQuantifiersThrowIaeForInvalidArguments() {
67 | assertThrows(IllegalArgumentException.class, () -> regex().digit().exactlyNTimes(-1));
68 | assertThrows(IllegalArgumentException.class, () -> regex().digit().exactlyNTimes(0));
69 | regex().digit().exactlyNTimes(1);
70 |
71 | assertThrows(IllegalArgumentException.class, () -> regex().digit().atLeastNTimes(-1));
72 | regex().digit().atLeastNTimes(0);
73 | regex().digit().atLeastNTimes(1);
74 |
75 | assertThrows(IllegalArgumentException.class, () -> regex().digit().betweenNAndMTimes(10, 1));
76 | assertThrows(IllegalArgumentException.class, () -> regex().digit().betweenNAndMTimes(-4, -5));
77 | assertThrows(IllegalArgumentException.class, () -> regex().digit().betweenNAndMTimes(-1, 4));
78 | regex().digit().betweenNAndMTimes(0, 4);
79 |
80 | assertThrows(IllegalArgumentException.class, () -> regex().digit().atMostNTimes(-1));
81 | assertThrows(IllegalArgumentException.class, () -> regex().digit().atMostNTimes(0));
82 | regex().digit().atMostNTimes(1);
83 | }
84 |
85 | @Test
86 | void greedyReluctantPossessiveWorksCorrectly() {
87 | ReadableRegexPattern patternGreedy = regex().anything().literal("foo").build();
88 | ReadableRegexPattern patternReluctant = regex().anything().reluctant().literal("foo").build();
89 | ReadableRegexPattern patternPossessive = regex().anything().possessive().literal("foo").build();
90 |
91 | String text = "xfooxxxxxxfoo";
92 | assertThat(patternGreedy, matchesExactly(text));
93 |
94 | Matcher matcher = patternReluctant.matches(text);
95 | assertThat(matcher.find(), equalTo(true));
96 | assertThat(matcher.group(), equalTo("xfoo"));
97 | assertThat(matcher.find(), equalTo(true));
98 | assertThat(matcher.group(), equalTo("xxxxxxfoo"));
99 |
100 | assertThat(patternPossessive, doesntMatchExactly(text));
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/ReadableRegexPatternTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
6 | import static org.hamcrest.MatcherAssert.assertThat;
7 | import static org.hamcrest.Matchers.contains;
8 | import static org.hamcrest.Matchers.equalTo;
9 |
10 | class ReadableRegexPatternTest {
11 | @Test
12 | void matchesExactlyWorks() {
13 | ReadableRegexPattern pattern = regex().digit().build();
14 |
15 | assertThat(pattern.matchesTextExactly("1"), equalTo(true));
16 | assertThat(pattern.matchesTextExactly("a"), equalTo(false));
17 | }
18 |
19 | @Test
20 | void groupsAreRecordedInTheCorrectOrder_and_unnamedGroupsAreNull_and_numberOfGroupsCountsAllGroupsg() {
21 | ReadableRegexPattern pattern = regex()
22 | .group("first", regex().digit())
23 | .group(regex().digit())
24 | .group("third", regex().digit())
25 | .build();
26 |
27 | assertThat(pattern.groups(), contains("first", null, "third"));
28 | assertThat(pattern.nrOfGroups(), equalTo(3));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/ReadmeTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import io.github.ricoapon.readableregex.matchers.PatternMatchMatcher;
5 | import org.junit.jupiter.api.Nested;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import javax.inject.Inject;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
13 | import static io.github.ricoapon.readableregex.RegexObjectInstantiation.instantiateObject;
14 | import static org.hamcrest.MatcherAssert.assertThat;
15 | import static org.hamcrest.Matchers.contains;
16 | import static org.hamcrest.Matchers.equalTo;
17 |
18 | /**
19 | * All the code in the README should be identical to the code in this file. This way, we make sure that the code in the
20 | * README compiles and works as expected.
21 | *
22 | * Note that we cannot use the class {@link PatternMatchMatcher}. It should be possible to compile all the examples in
23 | * any project that is using Hamcrest as well.
24 | *
25 | * Also note that we cannot use these tests for coverage itself. These examples are ONLY meant for the README. The tests
26 | * will overlap with other tests, that is ok.
27 | */
28 | @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC", justification = "@Nested classes should be non-static, but SpotBugs wants them static." +
29 | "See https://github.com/spotbugs/spotbugs/issues/560 for the bug (open since 2018).")
30 | class ReadmeTests {
31 | @Nested
32 | class Examples {
33 | @Test
34 | void example1() {
35 | ReadableRegexPattern pattern =
36 | regex() // Always start with the regex method to start the builder.
37 | .literal("http") // Literals are escaped automatically, no need to do this yourself.
38 | .literal("s").optional() // You can follow up with optional to make the "s" optional.
39 | .literal("://")
40 | .anyCharacterExcept(" ").zeroOrMore() // This comes down to [^ ]*.
41 | .build(); // Create the pattern with the final method.
42 |
43 | // The matchesText will return a boolean whether we have an *exact* match or not!
44 | assertThat(pattern.matchesTextExactly("https://www.github.com"), equalTo(true));
45 |
46 | // toString() method will return the underlying pattern. Not really readable though, that is why we have this library!
47 | assertThat(pattern.toString(), equalTo("(?:\\Qhttp\\E)(?:\\Qs\\E)?(?:\\Q://\\E)[^ ]*"));
48 | }
49 |
50 | @Test
51 | void example2() {
52 | ReadableRegexPattern pattern = regex()
53 | .startGroup() // You can use this method to start capturing the expression inside a group.
54 | .word()
55 | .endGroup() // This ends the last group.
56 | .whitespace()
57 | .startGroup("secondWord") // You can also give names to your group.
58 | .word()
59 | .endGroup()
60 | .build();
61 |
62 | Matcher matcher = pattern.matches("abc def");
63 | assertThat(matcher.matches(), equalTo(true));
64 |
65 | // Groups can always be found based on the order they are used.
66 | assertThat(matcher.group(1), equalTo("abc"));
67 | assertThat(matcher.group(2), equalTo("def"));
68 |
69 | // If you need details about the groups afterwards, this is not possible using the JDK Pattern.
70 | // However, using this library, this is now possible:
71 | assertThat(pattern.groups(), contains(null, "secondWord"));
72 |
73 | // If you have given the group a name, you can also find it based on the name.
74 | assertThat(matcher.group("secondWord"), equalTo("def"));
75 | }
76 |
77 | @Test
78 | void example3() {
79 | // It does not matter if you have already built the pattern, you can include it anyway.
80 | ReadableRegex> digits = regex().startGroup().digit().oneOrMore().endGroup().whitespace();
81 | ReadableRegexPattern word = regex().startGroup().word().endGroup().whitespace().build();
82 |
83 | ReadableRegexPattern pattern = regex()
84 | .add(digits)
85 | .add(digits)
86 | .add(word)
87 | .add(digits)
88 | .literal("END")
89 | .build();
90 |
91 | Matcher matcher = pattern.matches("12\t11\thello\t0000\tEND");
92 | assertThat(matcher.matches(), equalTo(true));
93 | // Note that captures are always a String!
94 | assertThat(matcher.group(1), equalTo("12"));
95 | assertThat(matcher.group(2), equalTo("11"));
96 | assertThat(matcher.group(3), equalTo("hello"));
97 | assertThat(matcher.group(4), equalTo("0000"));
98 | }
99 |
100 | @Test
101 | void example4() {
102 | ReadableRegexPattern pattern = regex()
103 | .oneOf(regex().literal("abc"), regex().digit()) // The oneOf method represents "or".
104 | .whitespace()
105 | // If we want to add a quantifier over a larger expression, we can encapsulate it with the add method,
106 | // which encloses the expression in an unnamed group.
107 | .add(regex().literal("a").digit()).exactlyNTimes(3)
108 | .whitespace()
109 | // Alternatively, you can use the startUnnamedGroup() for this to avoid nested structures.
110 | .startUnnamedGroup().literal("b").digit().endGroup().atMostNTimes(2)
111 | .build();
112 |
113 | System.out.println(pattern.toString());
114 | assertThat(pattern.matchesTextExactly("abc a1a2a3 b2"), equalTo(true));
115 | assertThat(pattern.matchesTextExactly("1 a3a6a9 "), equalTo(true));
116 | }
117 | }
118 |
119 | @Nested
120 | class Quantifiers {
121 | @Test
122 | void example1() {
123 | ReadableRegexPattern greedyPattern = regex().anything().literal("foo").build();
124 | ReadableRegexPattern reluctantPattern = regex().anything().reluctant().literal("foo").build();
125 | ReadableRegexPattern possessivePattern = regex().anything().possessive().literal("foo").build();
126 |
127 | String text = "xfooxxxxxxfoo";
128 | assertThat(greedyPattern.matchesTextExactly(text), equalTo(true));
129 |
130 | Matcher matcher = reluctantPattern.matches(text);
131 | assertThat(matcher.find(), equalTo(true));
132 | assertThat(matcher.group(), equalTo("xfoo"));
133 | assertThat(matcher.find(), equalTo(true));
134 | assertThat(matcher.group(), equalTo("xxxxxxfoo"));
135 |
136 | matcher = possessivePattern.matches(text);
137 | assertThat(matcher.find(), equalTo(false));
138 | }
139 | }
140 |
141 | @Nested
142 | class WorkingAroundTheLimitsOfTheLibrary {
143 | @Test
144 | void example1() {
145 | ReadableRegexPattern pattern1 = regex()
146 | .regexFromString("[a-z&&[^p]]") // With this method, you can add any kind of expression.
147 | .build();
148 |
149 | // Or you could use the overloaded variant of the regex method, which is the same:
150 | ReadableRegexPattern pattern2 = regex("[a-z&&[^p]]").build();
151 |
152 | assertThat(pattern1.matchesTextExactly("p"), equalTo(false));
153 | assertThat(pattern1.matchesTextExactly("c"), equalTo(true));
154 | assertThat(pattern2.matchesTextExactly("p"), equalTo(false));
155 | assertThat(pattern2.matchesTextExactly("c"), equalTo(true));
156 | }
157 |
158 | @Test
159 | void example2() {
160 | ReadableRegexPattern pattern = regex().literal(".").build();
161 |
162 | Pattern jdkPattern = pattern.getUnderlyingPattern();
163 | assertThat(jdkPattern.split("a.b.c"), equalTo(new String[]{"a", "b", "c"}));
164 | }
165 | }
166 |
167 | // You have to extend from ExtendableReadableRegex, where you fill in your own class as generic type.
168 | public static class TestExtension extends ExtendableReadableRegex {
169 |
170 | // It is highly advised to create your own static method "regex()". This way you can easily instantiate
171 | // your class and in your existing code you only have to change your import statement.
172 | public static TestExtension regex() {
173 | return new TestExtension();
174 | }
175 |
176 | // In your own extension you can add any method you like.
177 | public TestExtension digitWhitespaceDigit() {
178 | // For the implementation of your extension, you can only use the publicly available methods. All variables
179 | // and other methods are made private in the instance.
180 | // If you want to add arbitrary expressions, you can always use the method "regexFromString(...)".
181 | return digit().whitespace().digit();
182 | }
183 |
184 | // You can also override existing methods! To make sure that the code doesn't break, please always end
185 | // with calling the super method.
186 | @Override
187 | public ReadableRegexPattern buildWithFlags(PatternFlag... patternFlags) {
188 | return super.buildWithFlags(PatternFlag.DOT_ALL);
189 | }
190 | }
191 |
192 | /**
193 | * Note that this code occurs in both the README and in the Javadoc of {@link ExtendableReadableRegex}.
194 | * Don't forget to change both if you make any changes to this code!
195 | */
196 | @Nested
197 | class ExtendingTheBuilder {
198 | @Test
199 | void example1() {
200 | ReadableRegexPattern pattern = TestExtension.regex().digitWhitespaceDigit().build();
201 |
202 | assertThat(pattern.matchesTextExactly("1 3"), equalTo(true));
203 | assertThat(pattern.enabledFlags(), contains(PatternFlag.DOT_ALL));
204 | }
205 | }
206 |
207 | public static class MyPojo {
208 | public final String name;
209 | public final int id;
210 |
211 | @Inject // If you have only one constructor, you can leave this out.
212 | public MyPojo(String name, int id) {
213 | this.name = name;
214 | this.id = id;
215 | }
216 | }
217 |
218 | @Nested
219 | class InstantiatingObjects {
220 | @Test
221 | void example() {
222 | String data = "name: example, id: 15";
223 | ReadableRegexPattern pattern = regex()
224 | .literal("name: ").group("name", regex().word())
225 | .literal(", id: ").group("id", regex().digit().oneOrMore()).build();
226 |
227 | MyPojo myPojo = instantiateObject(pattern, data, MyPojo.class);
228 |
229 | assertThat(myPojo.name, equalTo("example"));
230 | // Types are automatically converted!
231 | assertThat(myPojo.id, equalTo(15));
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/StandaloneBlockTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import org.junit.jupiter.api.Nested;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static io.github.ricoapon.readableregex.Constants.*;
8 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
9 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.*;
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 | import static org.hamcrest.Matchers.contains;
12 | import static org.junit.jupiter.api.Assertions.assertThrows;
13 |
14 | /**
15 | * Tests related to methods that are inside {@link StandaloneBlockBuilder}.
16 | */
17 | @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC", justification = "@Nested classes should be non-static, but SpotBugs wants them static." +
18 | "See https://github.com/spotbugs/spotbugs/issues/560 for the bug (open since 2018).")
19 | class StandaloneBlockTests {
20 | @Nested
21 | class RegexFromString {
22 | @Test
23 | void nullAsArgumentThrowsNpe() {
24 | assertThrows(NullPointerException.class, () -> regex().regexFromString(null));
25 | }
26 |
27 | @Test
28 | void inputIsNotSurroundedWithUnnamedGroup() {
29 | ReadableRegexPattern pattern = regex().regexFromString("ab").oneOrMore().build();
30 |
31 | assertThat(pattern, matchesExactly("abbbb"));
32 | assertThat(pattern, doesntMatchExactly("aaabbb"));
33 | assertThat(pattern, doesntMatchAnythingFrom("a"));
34 | }
35 | }
36 |
37 | @Nested
38 | class Add {
39 | @SuppressWarnings("ConstantConditions")
40 | @Test
41 | void nullAsArgumentThrowsNpe() {
42 | assertThrows(NullPointerException.class, () -> regex().add((ReadableRegex>) null));
43 | assertThrows(NullPointerException.class, () -> regex().add((ReadableRegexPattern) null));
44 | }
45 |
46 | @Test
47 | void inputIsCapturedInUnnamedGroup() {
48 | ReadableRegexPattern pattern = regex().add(regex().literal("a").digit()).oneOrMore().build();
49 |
50 | assertThat(pattern, matchesExactly("a1a2a3"));
51 | assertThat(pattern, doesntMatchExactly("a111"));
52 |
53 | // Same test, but now applying "build()".
54 | pattern = regex().add(regex().literal("a").digit().build()).oneOrMore().build();
55 |
56 | assertThat(pattern, matchesExactly("a1a2a3"));
57 | assertThat(pattern, doesntMatchExactly("a111"));
58 | }
59 | }
60 |
61 | @Nested
62 | class Digits {
63 | @Test
64 | void digitOnlyMatchesDigits() {
65 | ReadableRegexPattern pattern = regex().digit().build();
66 |
67 | // Matches exactly every character inside DIGITS.
68 | for (String digit : DIGITS.split("")) {
69 | assertThat(pattern, matchesExactly(digit));
70 | }
71 | assertThat(pattern, doesntMatchAnythingFrom(WORD_CHARACTERS));
72 | assertThat(pattern, doesntMatchAnythingFrom(NON_LETTERS));
73 | assertThat(pattern, doesntMatchAnythingFrom(WHITESPACES));
74 | }
75 | }
76 |
77 | @Nested
78 | class Literals {
79 | @Test
80 | void nullAsArgumentThrowsNpe() {
81 | assertThrows(NullPointerException.class, () -> regex().literal(null));
82 | }
83 |
84 | @Test
85 | void literalCharactersAreEscaped() {
86 | ReadableRegexPattern pattern = regex()
87 | .literal("a.()[]\\/|?.+*")
88 | .build();
89 |
90 | assertThat(pattern, matchesExactly("a.()[]\\/|?.+*"));
91 | }
92 |
93 | @Test
94 | void literalCanBeCombinedWithMetaCharacters() {
95 | ReadableRegexPattern pattern = regex()
96 | .literal(".").digit().whitespace().literal("*")
97 | .build();
98 |
99 | assertThat(pattern, matchesExactly(".1 *"));
100 | assertThat(pattern, matchesExactly(".2\t*"));
101 | assertThat(pattern, doesntMatchExactly("a1 *"));
102 | }
103 |
104 | @Test
105 | void literalsAreStandaloneBlocks() {
106 | ReadableRegexPattern pattern = regex().digit().literal("a").oneOrMore().build();
107 |
108 | assertThat(pattern, matchesExactly("1aaaa"));
109 | assertThat(pattern, doesntMatchExactly("1a1a"));
110 | }
111 | }
112 |
113 | @Nested
114 | class Whitespace {
115 | @Test
116 | void whitespaceOnlyMatchesWhitespaces() {
117 | ReadableRegexPattern pattern = regex().whitespace().build();
118 |
119 | // Matches exactly every character inside WHITESPACES.
120 | for (String digit : WHITESPACES.split("")) {
121 | assertThat(pattern, matchesExactly(digit));
122 | }
123 | assertThat(pattern, matchesExactly(WHITESPACES.substring(0, 1)));
124 | assertThat(pattern, doesntMatchAnythingFrom(WORD_CHARACTERS));
125 | assertThat(pattern, doesntMatchAnythingFrom(NON_LETTERS));
126 | assertThat(pattern, doesntMatchAnythingFrom(DIGITS));
127 | }
128 | }
129 |
130 | @Nested
131 | class Tab {
132 | @Test
133 | void tabOnlyMatchesTab() {
134 | ReadableRegexPattern pattern = regex().tab().build();
135 |
136 | assertThat(pattern, matchesExactly("\t"));
137 | assertThat(pattern, doesntMatchAnythingFrom(WHITESPACES.replaceAll("\t", "")));
138 | }
139 | }
140 |
141 | @Nested
142 | class OneOf {
143 | @Test
144 | void oneOfCanBeUsedWithZeroOrOneArguments() {
145 | ReadableRegexPattern pattern = regex().oneOf().oneOf(regex().literal("b")).build();
146 |
147 | assertThat(pattern, matchesExactly("b"));
148 | }
149 |
150 | @Test
151 | void oneOfCanBeUsedWithMultipleArguments() {
152 | ReadableRegexPattern pattern = regex().oneOf(regex().literal("b"), regex().literal("c")).build();
153 |
154 | assertThat(pattern, matchesExactly("b"));
155 | assertThat(pattern, matchesExactly("c"));
156 | assertThat(pattern, doesntMatchAnythingFrom(""));
157 | }
158 |
159 | @Test
160 | void oneOfAreStandaloneBlocks() {
161 | ReadableRegexPattern pattern = regex().literal("a").oneOf(regex().literal("b")).optional().build();
162 |
163 | assertThat(pattern, matchesExactly("a"));
164 | assertThat(pattern, matchesExactly("ab"));
165 | assertThat(pattern, doesntMatchAnythingFrom(""));
166 | }
167 | }
168 |
169 | @Nested
170 | class RangeAndNotInRange {
171 | @Test
172 | void throwIaeWhenZeroOrOddNumberOfArgumentsIsSupplied() {
173 | assertThrows(IllegalArgumentException.class, () -> regex().range('a'));
174 | assertThrows(IllegalArgumentException.class, () -> regex().range());
175 | assertThrows(IllegalArgumentException.class, () -> regex().notInRange('a'));
176 | assertThrows(IllegalArgumentException.class, () -> regex().notInRange());
177 | }
178 |
179 | @Test
180 | void canBeUsedWithMultipleArguments() {
181 | ReadableRegexPattern pattern = regex().range('a', 'e', 'x', 'z').notInRange('a', 'z', 'A', 'Z', '0', '9').build();
182 |
183 | assertThat(pattern, matchesExactly("b|"));
184 | assertThat(pattern, matchesExactly("y~"));
185 | assertThat(pattern, doesntMatchAnythingFrom("f|"));
186 | assertThat(pattern, doesntMatchAnythingFrom("yZ"));
187 | }
188 | }
189 |
190 | @Nested
191 | class AnyCharacterOfAndExcept {
192 | @Test
193 | void throwNpeWhenArgumentIsNullOrZeroLengthString() {
194 | assertThrows(NullPointerException.class, () -> regex().anyCharacterOf(null));
195 | assertThrows(IllegalArgumentException.class, () -> regex().anyCharacterOf(""));
196 | assertThrows(NullPointerException.class, () -> regex().anyCharacterExcept(null));
197 | assertThrows(IllegalArgumentException.class, () -> regex().anyCharacterExcept(""));
198 | }
199 |
200 | @Test
201 | void characterRangesAreMatched() {
202 | ReadableRegexPattern pattern = regex().anyCharacterOf("aeiou")
203 | .anyCharacterExcept("abc")
204 | .build();
205 |
206 | assertThat(pattern, matchesExactly("ix"));
207 | assertThat(pattern, doesntMatchAnythingFrom("ia"));
208 | assertThat(pattern, doesntMatchAnythingFrom("xx"));
209 | }
210 | }
211 |
212 | @Nested
213 | class WordCharacter {
214 | @Test
215 | void matchCorrectCharacter() {
216 | ReadableRegexPattern pattern = regex().wordCharacter().nonWordCharacter().build();
217 |
218 | assertThat(pattern, matchesExactly("a."));
219 | assertThat(pattern, doesntMatchAnythingFrom("ab"));
220 | assertThat(pattern, doesntMatchAnythingFrom(".a"));
221 | }
222 | }
223 |
224 | @Nested
225 | class WordBoundary {
226 | @Test
227 | void wordBoundaryMatchesCorrectly() {
228 | ReadableRegexPattern pattern = regex().literal("abc").wordBoundary().literal(" ").build();
229 |
230 | assertThat(pattern, matchesExactly("abc "));
231 | }
232 |
233 | @Test
234 | void nonWordBoundaryMatchesCorrectly() {
235 | ReadableRegexPattern pattern = regex().literal("a").nonWordBoundary().literal("b").build();
236 |
237 | assertThat(pattern, matchesExactly("ab"));
238 | }
239 | }
240 |
241 | /**
242 | * No tests are needed for the method {@link StandaloneBlockBuilder#anyCharacter()}, because this is already covered by other tests:
243 | *
244 | * - {@link PatternFlagTests#dotAllWorks()}
245 | * - {@link SyntacticSugarTests.StandaloneBlock#anythingWorks()}
246 | *
247 | */
248 | @Nested
249 | class AnyCharacter {
250 | }
251 |
252 | @Nested
253 | class StartOfLine {
254 | @Test
255 | void quantifierAfterWorks() {
256 | ReadableRegexPattern pattern = regex().startOfLine().exactlyNTimes(1).literal("a").build();
257 |
258 | assertThat(pattern, matchesExactly("a"));
259 | assertThat(pattern, matchesSomethingFrom("\na"));
260 | assertThat(pattern, doesntMatchAnythingFrom("ba"));
261 | }
262 |
263 | @Test
264 | void methodEnablesMultilineFlag() {
265 | ReadableRegexPattern pattern = regex().startOfLine().build();
266 | ReadableRegexPattern pattern2 = regex().startOfLine().buildWithFlags(PatternFlag.MULTILINE);
267 |
268 | assertThat(pattern.enabledFlags(), contains(PatternFlag.MULTILINE));
269 | assertThat(pattern2.enabledFlags(), contains(PatternFlag.MULTILINE));
270 | }
271 | }
272 |
273 | @Nested
274 | class StartOfInput {
275 | @Test
276 | void worksCorrectlyWithStartOfLine() {
277 | ReadableRegexPattern pattern = regex().startOfInput().regexFromString("\n").startOfLine().literal("a").build();
278 |
279 | assertThat(pattern, matchesExactly("\na"));
280 | assertThat(pattern, doesntMatchAnythingFrom("\n\na"));
281 | }
282 | }
283 |
284 | @Nested
285 | class EndOfLine {
286 | @Test
287 | void quantifierAfterWorks() {
288 | ReadableRegexPattern pattern = regex().literal("a").endOfLine().exactlyNTimes(1).build();
289 |
290 | assertThat(pattern, matchesExactly("a"));
291 | assertThat(pattern, matchesSomethingFrom("a\n"));
292 | assertThat(pattern, doesntMatchAnythingFrom("ab"));
293 | }
294 |
295 | @Test
296 | void methodEnablesMultilineFlag() {
297 | ReadableRegexPattern pattern = regex().endOfLine().build();
298 | ReadableRegexPattern pattern2 = regex().endOfLine().buildWithFlags(PatternFlag.MULTILINE);
299 |
300 | assertThat(pattern.enabledFlags(), contains(PatternFlag.MULTILINE));
301 | assertThat(pattern2.enabledFlags(), contains(PatternFlag.MULTILINE));
302 | }
303 | }
304 |
305 | @Nested
306 | class EndOfInput {
307 | @Test
308 | void worksCorrectlyWithEndOfLine() {
309 | ReadableRegexPattern pattern = regex().literal("a").endOfLine().regexFromString("\n").endOfInput().build();
310 |
311 | assertThat(pattern, matchesExactly("a\n"));
312 | assertThat(pattern, doesntMatchAnythingFrom("a\n\n"));
313 | }
314 | }
315 | }
316 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/SyntacticSugarTests.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 | import org.junit.jupiter.api.Nested;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.util.regex.Matcher;
8 |
9 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
10 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.*;
11 | import static org.hamcrest.MatcherAssert.assertThat;
12 | import static org.hamcrest.Matchers.equalTo;
13 |
14 | /**
15 | * Tests related to methods that are inside {@link SyntacticSugarBuilder}.
16 | */
17 | @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC", justification = "@Nested classes should be non-static, but SpotBugs wants them static." +
18 | "See https://github.com/spotbugs/spotbugs/issues/560 for the bug (open since 2018).")
19 | class SyntacticSugarTests {
20 | @Nested
21 | class StandaloneBlock {
22 | @Test
23 | void wordWorks() {
24 | String groupName = "result";
25 | ReadableRegexPattern pattern = regex().startGroup(groupName).word().endGroup().build();
26 |
27 | Matcher matcher = pattern.matches("abc de_f1 ghi|jkl");
28 | assertThat(matcher.find(), equalTo(true));
29 | assertThat(matcher.group(groupName), equalTo("abc"));
30 | assertThat(matcher.find(), equalTo(true));
31 | assertThat(matcher.group(groupName), equalTo("de_f1"));
32 | assertThat(matcher.find(), equalTo(true));
33 | assertThat(matcher.group(groupName), equalTo("ghi"));
34 | assertThat(matcher.find(), equalTo(true));
35 | assertThat(matcher.group(groupName), equalTo("jkl"));
36 | }
37 |
38 | @Test
39 | void anythingWorks() {
40 | ReadableRegexPattern pattern = regex().anything().build();
41 |
42 | assertThat(pattern, matchesExactly(""));
43 | assertThat(pattern, matchesExactly(Constants.WORD_CHARACTERS));
44 | assertThat(pattern, matchesExactly(Constants.DIGITS));
45 | assertThat(pattern, matchesExactly(Constants.NON_LETTERS));
46 | }
47 |
48 | @Test
49 | void lineBreakWorks() {
50 | ReadableRegexPattern pattern = regex().lineBreak().build();
51 |
52 | assertThat(pattern, matchesExactly("\n"));
53 | assertThat(pattern, matchesExactly("\r\n"));
54 | assertThat(pattern, matchesExactly("\r"));
55 | assertThat(pattern, doesntMatchAnythingFrom(" "));
56 | }
57 | }
58 |
59 | @Nested
60 | class Group {
61 | @Test
62 | void groupWorks() {
63 | ReadableRegexPattern pattern = regex().digit().group("firstGroupName", regex().digit().group(regex().digit())).build();
64 | Matcher matcher = pattern.matches("123");
65 |
66 | assertThat(matcher.matches(), equalTo(true));
67 | assertThat(matcher.group("firstGroupName"), equalTo("23"));
68 | assertThat(matcher.group(2), equalTo("3"));
69 | }
70 |
71 | @Test
72 | void lookbehindWorks() {
73 | ReadableRegexPattern pattern = regex().positiveLookbehind(regex().digit()).whitespace().build();
74 | assertThat(pattern, matchesSomethingFrom("1 "));
75 | assertThat(pattern, doesntMatchAnythingFrom(" "));
76 |
77 | pattern = regex().negativeLookbehind(regex().digit()).whitespace().build();
78 | assertThat(pattern, doesntMatchAnythingFrom("1 "));
79 | assertThat(pattern, matchesSomethingFrom(" "));
80 | }
81 |
82 | @Test
83 | void lookaheadWorks() {
84 | ReadableRegexPattern pattern = regex().whitespace().positiveLookahead(regex().digit()).build();
85 | assertThat(pattern, matchesSomethingFrom(" 1"));
86 | assertThat(pattern, doesntMatchAnythingFrom(" "));
87 |
88 | pattern = regex().whitespace().negativeLookahead(regex().digit()).build();
89 | assertThat(pattern, doesntMatchAnythingFrom(" 1"));
90 | assertThat(pattern, matchesSomethingFrom(" "));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/extendable/ExtendableReadableRegexTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.extendable;
2 |
3 | import io.github.ricoapon.readableregex.PatternFlag;
4 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.doesntMatchAnythingFrom;
8 | import static io.github.ricoapon.readableregex.matchers.PatternMatchMatcher.matchesExactly;
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.contains;
11 |
12 | /**
13 | * Tests related to extending the builder using {@link io.github.ricoapon.readableregex.ExtendableReadableRegex}.
14 | */
15 | class ExtendableReadableRegexTest {
16 | @Test
17 | void methodCanBeAdded() {
18 | ReadableRegexPattern pattern = TestExtension.regex().literal("a").digitWhitespaceDigit().literal("a").build();
19 |
20 | assertThat(pattern, matchesExactly("a1 4a"));
21 | assertThat(pattern, doesntMatchAnythingFrom("1 1"));
22 | assertThat(pattern, doesntMatchAnythingFrom("a11a"));
23 | }
24 |
25 | @Test
26 | void methodCanBeOverwritten() {
27 | ReadableRegexPattern pattern = TestExtension.regex().build();
28 |
29 | assertThat(pattern.enabledFlags(), contains(PatternFlag.DOT_ALL));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/extendable/TestExtension.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.extendable;
2 |
3 | import io.github.ricoapon.readableregex.ExtendableReadableRegex;
4 | import io.github.ricoapon.readableregex.PatternFlag;
5 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
6 |
7 | /**
8 | * Dummy extension that adds a new method and overrides an existing method.
9 | */
10 | public class TestExtension extends ExtendableReadableRegex {
11 | public static TestExtension regex() {
12 | return new TestExtension();
13 | }
14 |
15 | public TestExtension digitWhitespaceDigit() {
16 | return digit().whitespace().digit();
17 | }
18 |
19 | @Override
20 | public ReadableRegexPattern buildWithFlags(PatternFlag... patternFlags) {
21 | // Always build with exactly one flag: DOT_ALL.
22 | return super.buildWithFlags(PatternFlag.DOT_ALL);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/ConstructorParamUnknownType.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | @SuppressWarnings({"FieldCanBeLocal", "unused"})
4 | public class ConstructorParamUnknownType {
5 | private final ConstructorParamUnknownType unknownType;
6 |
7 | public ConstructorParamUnknownType(ConstructorParamUnknownType unknownType) {
8 | this.unknownType = unknownType;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/MultipleConstructorsWithMultipleInjectAnnotations.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | import javax.inject.Inject;
4 |
5 | @SuppressWarnings({"MultipleInjectedConstructorsForClass", "unused"})
6 | public class MultipleConstructorsWithMultipleInjectAnnotations {
7 |
8 | @Inject
9 | public MultipleConstructorsWithMultipleInjectAnnotations(int n) {
10 |
11 | }
12 |
13 | @Inject
14 | public MultipleConstructorsWithMultipleInjectAnnotations(int n, boolean uselessParam) {
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/MultipleConstructorsWithSingleInjectAnnotation.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | import javax.inject.Inject;
4 |
5 | @SuppressWarnings("unused")
6 | public class MultipleConstructorsWithSingleInjectAnnotation {
7 | private final int n;
8 |
9 | @Inject
10 | public MultipleConstructorsWithSingleInjectAnnotation(int n) {
11 | this.n = n;
12 | }
13 |
14 | public MultipleConstructorsWithSingleInjectAnnotation(int n, boolean uselessParam) {
15 | this.n = n;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/PrimitiveAndBoxedPrimitiveTypesAndString.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | public class PrimitiveAndBoxedPrimitiveTypesAndString {
4 | public final Byte byteBoxed;
5 | public final byte bytePrimitive;
6 | public final Short shortBoxed;
7 | public final short shortPrimitive;
8 | public final Integer integerBoxed;
9 | public final int integerPrimitive;
10 | public final Long longBoxed;
11 | public final long longPrimitive;
12 | public final Float floatBoxed;
13 | public final float floatPrimitive;
14 | public final Double doubleBoxed;
15 | public final double doublePrimitive;
16 | public final Boolean booleanBoxed;
17 | public final boolean booleanPrimitive;
18 | public final Character characterBoxed;
19 | public final char characterPrimitive;
20 | public final String string;
21 |
22 | public PrimitiveAndBoxedPrimitiveTypesAndString(Byte byteBoxed, byte bytePrimitive, Short shortBoxed, short shortPrimitive, Integer integerBoxed, int integerPrimitive, Long longBoxed, long longPrimitive, Float floatBoxed, float floatPrimitive, Double doubleBoxed, double doublePrimitive, Boolean booleanBoxed, boolean booleanPrimitive, Character characterBoxed, char characterPrimitive, String string) {
23 | this.byteBoxed = byteBoxed;
24 | this.bytePrimitive = bytePrimitive;
25 | this.shortBoxed = shortBoxed;
26 | this.shortPrimitive = shortPrimitive;
27 | this.integerBoxed = integerBoxed;
28 | this.integerPrimitive = integerPrimitive;
29 | this.longBoxed = longBoxed;
30 | this.longPrimitive = longPrimitive;
31 | this.floatBoxed = floatBoxed;
32 | this.floatPrimitive = floatPrimitive;
33 | this.doubleBoxed = doubleBoxed;
34 | this.doublePrimitive = doublePrimitive;
35 | this.booleanBoxed = booleanBoxed;
36 | this.booleanPrimitive = booleanPrimitive;
37 | this.characterBoxed = characterBoxed;
38 | this.characterPrimitive = characterPrimitive;
39 | this.string = string;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/RegexObjectInstantiationTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
4 | import io.github.ricoapon.readableregex.RegexObjectInstantiation;
5 | import io.github.ricoapon.readableregex.RegexObjectInstantiationException;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import java.lang.reflect.Constructor;
9 | import java.util.regex.Pattern;
10 |
11 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
12 | import static io.github.ricoapon.readableregex.RegexObjectInstantiation.instantiateObject;
13 | import static org.hamcrest.MatcherAssert.assertThat;
14 | import static org.hamcrest.Matchers.equalTo;
15 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
16 | import static org.junit.jupiter.api.Assertions.assertThrows;
17 |
18 | class RegexObjectInstantiationTest {
19 | @Test
20 | void primitivesCanBeInjectedAsBoxedOrPrimitive_AndStringCanBeInjected() {
21 | // Given
22 | String value = "Byte:1byte:1Short:500short:500Integer:35000integer:35000Long:3000000000long:3000000000" +
23 | "Float:3.6float:3.6Double:3.6double:3.6Boolean:trueboolean:trueCharacter:ccharacter:cString:abc";
24 | ReadableRegexPattern pattern = regex()
25 | .literal("Byte:").group("byteBoxed", regex().digit())
26 | .literal("byte:").group("bytePrimitive", regex().digit())
27 | .literal("Short:").group("shortBoxed", regex().digit().oneOrMore())
28 | .literal("short:").group("shortPrimitive", regex().digit().oneOrMore())
29 | .literal("Integer:").group("integerBoxed", regex().digit().oneOrMore())
30 | .literal("integer:").group("integerPrimitive", regex().digit().oneOrMore())
31 | .literal("Long:").group("longBoxed", regex().digit().oneOrMore())
32 | .literal("long:").group("longPrimitive", regex().digit().oneOrMore())
33 | .literal("Float:").group("floatBoxed", regex().anyCharacterOf("0-9\\.").oneOrMore())
34 | .literal("float:").group("floatPrimitive", regex().anyCharacterOf("0-9\\.").oneOrMore())
35 | .literal("Double:").group("doubleBoxed", regex().anyCharacterOf("0-9\\.").oneOrMore())
36 | .literal("double:").group("doublePrimitive", regex().anyCharacterOf("0-9\\.").oneOrMore())
37 | .literal("Boolean:").group("booleanBoxed", regex().word())
38 | .literal("boolean:").group("booleanPrimitive", regex().word())
39 | .literal("Character:").group("characterBoxed", regex().anyCharacter())
40 | .literal("character:").group("characterPrimitive", regex().anyCharacter())
41 | .literal("String:").group("string", regex().word())
42 | .build();
43 |
44 | // When
45 | PrimitiveAndBoxedPrimitiveTypesAndString primitiveAndBoxedPrimitiveTypesAndString =
46 | instantiateObject(pattern, value, PrimitiveAndBoxedPrimitiveTypesAndString.class);
47 |
48 | // Then
49 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.byteBoxed, equalTo((byte) 1));
50 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.bytePrimitive, equalTo((byte) 1));
51 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.shortBoxed, equalTo((short) 500));
52 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.shortPrimitive, equalTo((short) 500));
53 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.integerBoxed, equalTo(35000));
54 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.integerPrimitive, equalTo(35000));
55 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.longBoxed, equalTo(3000000000L));
56 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.longPrimitive, equalTo(3000000000L));
57 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.floatBoxed, equalTo(3.6f));
58 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.floatPrimitive, equalTo(3.6f));
59 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.doubleBoxed, equalTo(3.6d));
60 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.doublePrimitive, equalTo(3.6d));
61 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.booleanBoxed, equalTo(Boolean.TRUE));
62 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.booleanPrimitive, equalTo(true));
63 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.characterBoxed, equalTo('c'));
64 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.characterPrimitive, equalTo('c'));
65 | assertThat(primitiveAndBoxedPrimitiveTypesAndString.string, equalTo("abc"));
66 | }
67 |
68 | @Test
69 | void throwIfConstructorIsNotValid() {
70 | ReadableRegexPattern pattern = regex().group("n", regex().digit()).build();
71 | String data = "1";
72 |
73 | assertThrows(RegexObjectInstantiationException.class, () -> instantiateObject(pattern, data, MultipleConstructorsWithMultipleInjectAnnotations.class));
74 | assertDoesNotThrow(() -> instantiateObject(pattern, data, MultipleConstructorsWithSingleInjectAnnotation.class));
75 | assertDoesNotThrow(() -> instantiateObject(pattern, data, SingleConstructorWithInjectAnnotation.class));
76 | assertDoesNotThrow(() -> instantiateObject(pattern, data, SingleConstructorWithoutInjectAnnotation.class));
77 | }
78 |
79 | @Test
80 | void throwIfConstructorParamIsUnknownType() {
81 | ReadableRegexPattern pattern = regex().group("unknownType", regex()).build();
82 | String data = "";
83 |
84 | assertThrows(RegexObjectInstantiationException.class, () -> instantiateObject(pattern, data, ConstructorParamUnknownType.class));
85 | }
86 |
87 | @Test
88 | void throwIfParameterNameDoesNotOccurAsGroupName() {
89 | ReadableRegexPattern pattern = regex().build();
90 | String data = "";
91 |
92 | assertThrows(RegexObjectInstantiationException.class, () -> instantiateObject(pattern, data, SingleConstructorWithoutInjectAnnotation.class));
93 | }
94 |
95 | @Test
96 | void throwIfMatchIsNotExact() {
97 | ReadableRegexPattern pattern = regex().group("n", regex().digit()).build();
98 | String data = "1a";
99 |
100 | assertThrows(RegexObjectInstantiationException.class, () -> instantiateObject(pattern, data, SingleConstructorWithoutInjectAnnotation.class));
101 | }
102 |
103 | private static class NotInstantiatable {
104 | public NotInstantiatable() {
105 | }
106 | }
107 |
108 | @Test
109 | void throwIfClassCannotBeInstantiated() {
110 | ReadableRegexPattern pattern = regex().build();
111 | String data = "";
112 |
113 | assertThrows(RegexObjectInstantiationException.class, () -> instantiateObject(pattern, data, NotInstantiatable.class));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/SingleConstructorWithInjectAnnotation.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | import javax.inject.Inject;
4 |
5 | @SuppressWarnings({"FieldCanBeLocal", "unused"})
6 | public class SingleConstructorWithInjectAnnotation {
7 | private final int n;
8 |
9 | @Inject
10 | public SingleConstructorWithInjectAnnotation(int n) {
11 | this.n = n;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/SingleConstructorWithoutInjectAnnotation.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | @SuppressWarnings({"FieldCanBeLocal", "unused"})
4 | public class SingleConstructorWithoutInjectAnnotation {
5 | private final int n;
6 |
7 | public SingleConstructorWithoutInjectAnnotation(int n) {
8 | this.n = n;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/instantiation/StringConverterTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.instantiation;
2 |
3 | import io.github.ricoapon.readableregex.internal.instantiation.StringConverter;
4 | import org.junit.jupiter.api.Test;
5 |
6 | class StringConverterTest {
7 | @Test
8 | void callConstructorForCodeCoverage() {
9 | //noinspection InstantiationOfUtilityClass
10 | new StringConverter();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/internal/MethodOrderCheckerTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal;
2 |
3 | import io.github.ricoapon.readableregex.IncorrectConstructionException;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static io.github.ricoapon.readableregex.internal.MethodOrderChecker.Method.*;
8 | import static org.junit.jupiter.api.Assertions.assertThrows;
9 |
10 | class MethodOrderCheckerTest {
11 | private MethodOrderChecker methodOrderChecker;
12 |
13 | @BeforeEach
14 | void setUp() {
15 | methodOrderChecker = new MethodOrderChecker();
16 | }
17 |
18 | @Test
19 | void testFor100PercentCodeCoverage() {
20 | // We know there are a fixed number of enums. This means that the if-statements will never reach the branch
21 | // false-false...-false. However, if we input null, we do reach this case. Ugly way to achieve this, but it works.
22 | // Also note that we cannot use a switch statement anymore, since that doesn't allow null.
23 | methodOrderChecker.checkCallingMethod(null);
24 | }
25 |
26 | @Test
27 | void quantifierCanNotBeDoneAtTheStart() {
28 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(QUANTIFIER));
29 | }
30 |
31 | @Test
32 | void quantifierIsPossibleAfterStandaloneBlockButNotAfterQualifier() {
33 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
34 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
35 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(QUANTIFIER));
36 | }
37 |
38 | @Test
39 | void cannotCloseGroupIfNoneStarted() {
40 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(END_GROUP));
41 | }
42 |
43 | @Test
44 | void cannotFinishWhenGroupsAreOpen() {
45 | methodOrderChecker.checkCallingMethod(START_GROUP);
46 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(FINISH));
47 | }
48 |
49 | @Test
50 | void quantifierIsNotPossibleAfterStartingGroup() {
51 | methodOrderChecker.checkCallingMethod(START_GROUP);
52 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(QUANTIFIER));
53 | }
54 |
55 | @Test
56 | void quantifierIsPossibleAfterEndingGroup() {
57 | methodOrderChecker.checkCallingMethod(START_GROUP);
58 | methodOrderChecker.checkCallingMethod(END_GROUP);
59 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
60 | }
61 |
62 | @Test
63 | void reluctantOrPossessiveIsOnlyPossibleAfterQuantifier() {
64 | methodOrderChecker.checkCallingMethod(STANDALONE_BLOCK);
65 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE));
66 |
67 | methodOrderChecker.checkCallingMethod(START_GROUP);
68 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE));
69 |
70 | methodOrderChecker.checkCallingMethod(END_GROUP);
71 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE));
72 |
73 | methodOrderChecker.checkCallingMethod(QUANTIFIER);
74 | methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE);
75 | assertThrows(IncorrectConstructionException.class, () -> methodOrderChecker.checkCallingMethod(RELUCTANT_OR_POSSESSIVE));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/internal/ReadableRegexOrderCheckerTest.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.internal;
2 |
3 | import io.github.ricoapon.readableregex.PatternFlag;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import static io.github.ricoapon.readableregex.ReadableRegex.regex;
8 | import static io.github.ricoapon.readableregex.internal.MethodOrderChecker.Method.*;
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.hamcrest.Matchers.equalTo;
11 |
12 | class ReadableRegexOrderCheckerTest {
13 | /** Dummy implementation of {@link MethodOrderChecker} to check {@link MethodOrderChecker#checkCallingMethod(Method)}. */
14 | private static class DummyOrderChecker extends MethodOrderChecker {
15 | public Method calledMethod;
16 |
17 | @Override
18 | public void checkCallingMethod(Method method) {
19 | calledMethod = method;
20 | }
21 | }
22 |
23 | private ReadableRegexOrderChecker> readableRegexOrderChecker;
24 | private DummyOrderChecker dummyOrderChecker;
25 |
26 | @BeforeEach
27 | void setUp() {
28 | dummyOrderChecker = new DummyOrderChecker();
29 |
30 | // Call digit, so that quantifiers can be called directly after. Does not matter for others.
31 | readableRegexOrderChecker = new ReadableRegexOrderChecker<>(dummyOrderChecker);
32 | }
33 |
34 | @Test
35 | void build_Finish() {
36 | readableRegexOrderChecker.build();
37 | assertThat(dummyOrderChecker.calledMethod, equalTo(FINISH));
38 | }
39 |
40 | @Test
41 | void buildWithFlags_Finish() {
42 | readableRegexOrderChecker.buildWithFlags(PatternFlag.CASE_INSENSITIVE, PatternFlag.MULTILINE);
43 | assertThat(dummyOrderChecker.calledMethod, equalTo(FINISH));
44 | }
45 |
46 | @Test
47 | void regexFromString_StandaloneBlock() {
48 | readableRegexOrderChecker.regexFromString("");
49 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
50 | }
51 |
52 | @Test
53 | void add_StandaloneBlock() {
54 | readableRegexOrderChecker.add(regex());
55 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
56 |
57 | readableRegexOrderChecker.add(regex().build());
58 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
59 | }
60 |
61 | @Test
62 | void literal_StandaloneBlock() {
63 | readableRegexOrderChecker.literal("");
64 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
65 | }
66 |
67 | @Test
68 | void digit_StandaloneBlock() {
69 | readableRegexOrderChecker.digit();
70 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
71 | }
72 |
73 | @Test
74 | void whitespace_StandaloneBlock() {
75 | readableRegexOrderChecker.whitespace();
76 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
77 | }
78 |
79 | @Test
80 | void tab_StandaloneBlock() {
81 | readableRegexOrderChecker.tab();
82 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
83 | }
84 |
85 | @Test
86 | void oneOf_StandaloneBlock() {
87 | readableRegexOrderChecker.oneOf();
88 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
89 | }
90 |
91 | @Test
92 | void range_StandaloneBlock() {
93 | readableRegexOrderChecker.range('a', 'b');
94 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
95 | }
96 |
97 | @Test
98 | void notInRange_StandaloneBlock() {
99 | readableRegexOrderChecker.notInRange('a', 'b');
100 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
101 | }
102 |
103 | @Test
104 | void anyCharacterOf_StandaloneBlock() {
105 | readableRegexOrderChecker.anyCharacterOf("abc");
106 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
107 | }
108 |
109 | @Test
110 | void anyCharacterExcept_StandaloneBlock() {
111 | readableRegexOrderChecker.anyCharacterExcept("abc");
112 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
113 | }
114 |
115 | @Test
116 | void wordCharacter_StandaloneBlock() {
117 | readableRegexOrderChecker.wordCharacter();
118 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
119 | }
120 |
121 | @Test
122 | void nonWordCharacter_StandaloneBlock() {
123 | readableRegexOrderChecker.nonWordCharacter();
124 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
125 | }
126 |
127 | @Test
128 | void wordBoundary_StandaloneBlock() {
129 | readableRegexOrderChecker.wordBoundary();
130 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
131 | }
132 |
133 | @Test
134 | void nonWordBoundary_StandaloneBlock() {
135 | readableRegexOrderChecker.nonWordBoundary();
136 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
137 | }
138 |
139 | @Test
140 | void anyCharacter_StandaloneBlock() {
141 | readableRegexOrderChecker.anyCharacter();
142 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
143 | }
144 |
145 | @Test
146 | void startOfLine_StandaloneBlock() {
147 | readableRegexOrderChecker.startOfLine();
148 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
149 | }
150 |
151 | @Test
152 | void startOfInput_StandaloneBlock() {
153 | readableRegexOrderChecker.startOfInput();
154 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
155 | }
156 |
157 | @Test
158 | void endOfLine_StandaloneBlock() {
159 | readableRegexOrderChecker.endOfLine();
160 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
161 | }
162 |
163 | @Test
164 | void endOfInput_StandaloneBlock() {
165 | readableRegexOrderChecker.endOfInput();
166 | assertThat(dummyOrderChecker.calledMethod, equalTo(STANDALONE_BLOCK));
167 | }
168 |
169 | @Test
170 | void oneOrMore_Quantifier() {
171 | readableRegexOrderChecker.digit().oneOrMore();
172 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
173 | }
174 |
175 | @Test
176 | void optional_Quantifier() {
177 | readableRegexOrderChecker.digit().optional();
178 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
179 | }
180 |
181 | @Test
182 | void zeroOrMore_Quantifier() {
183 | readableRegexOrderChecker.digit().zeroOrMore();
184 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
185 | }
186 |
187 | @Test
188 | void exactlyNTimes_Quantifier() {
189 | readableRegexOrderChecker.digit().exactlyNTimes(2);
190 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
191 | }
192 |
193 | @Test
194 | void etLeastNTimes_Quantifier() {
195 | readableRegexOrderChecker.digit().atLeastNTimes(2);
196 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
197 | }
198 |
199 | @Test
200 | void betweenNAndMTimes_Quantifier() {
201 | readableRegexOrderChecker.digit().betweenNAndMTimes(2, 4);
202 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
203 | }
204 |
205 | @Test
206 | void atMostNTimes_Quantifier() {
207 | readableRegexOrderChecker.digit().atMostNTimes(2);
208 | assertThat(dummyOrderChecker.calledMethod, equalTo(QUANTIFIER));
209 | }
210 |
211 | @Test
212 | void reluctant_ReluctantOrPossessive() {
213 | readableRegexOrderChecker.digit().atMostNTimes(2).reluctant();
214 | assertThat(dummyOrderChecker.calledMethod, equalTo(RELUCTANT_OR_POSSESSIVE));
215 | }
216 |
217 | @Test
218 | void possessive_ReluctantOrPossessive() {
219 | readableRegexOrderChecker.digit().atMostNTimes(2).possessive();
220 | assertThat(dummyOrderChecker.calledMethod, equalTo(RELUCTANT_OR_POSSESSIVE));
221 | }
222 |
223 | @Test
224 | void startGroup_StartGroup() {
225 | readableRegexOrderChecker.startGroup();
226 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
227 |
228 | readableRegexOrderChecker.startGroup("a");
229 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
230 | }
231 |
232 | @Test
233 | void startUnnamedGroup_StartGroup() {
234 | readableRegexOrderChecker.startUnnamedGroup();
235 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
236 | }
237 |
238 | @Test
239 | void startLook_StartGroup() {
240 | readableRegexOrderChecker.startPositiveLookbehind();
241 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
242 |
243 | readableRegexOrderChecker.startNegativeLookbehind();
244 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
245 |
246 | readableRegexOrderChecker.startPositiveLookahead();
247 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
248 |
249 | readableRegexOrderChecker.startNegativeLookahead();
250 | assertThat(dummyOrderChecker.calledMethod, equalTo(START_GROUP));
251 | }
252 |
253 | @Test
254 | void endGroup_EndGroup() {
255 | readableRegexOrderChecker.startGroup().endGroup();
256 | assertThat(dummyOrderChecker.calledMethod, equalTo(END_GROUP));
257 | }
258 |
259 | @Test
260 | void group_StandaloneBlock() {
261 | readableRegexOrderChecker.group(regex());
262 | assertThat(dummyOrderChecker.calledMethod, equalTo(END_GROUP));
263 |
264 | readableRegexOrderChecker.group("a", regex());
265 | assertThat(dummyOrderChecker.calledMethod, equalTo(END_GROUP));
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/test/java/io/github/ricoapon/readableregex/matchers/PatternMatchMatcher.java:
--------------------------------------------------------------------------------
1 | package io.github.ricoapon.readableregex.matchers;
2 |
3 | import io.github.ricoapon.readableregex.ReadableRegexPattern;
4 | import org.hamcrest.Description;
5 | import org.hamcrest.TypeSafeMatcher;
6 |
7 | import java.util.regex.Matcher;
8 |
9 | /**
10 | * Hamcrest matcher for checking whether the {@link ReadableRegexPattern} matches a given {@link String}.
11 | */
12 | public class PatternMatchMatcher extends TypeSafeMatcher {
13 | enum MatchStrategy {
14 | MATCH_EXACTLY, NOT_MATCH_EXACTLY, MATCH_SOMETHING, NOT_MATCH_ANYTHING
15 | }
16 | private final String textToMatch;
17 | private final MatchStrategy matchStrategy;
18 |
19 | public PatternMatchMatcher(String textToMatch, MatchStrategy matchStrategy) {
20 | this.textToMatch = textToMatch;
21 | this.matchStrategy = matchStrategy;
22 | }
23 |
24 | public static PatternMatchMatcher matchesExactly(String textToMatch) {
25 | return new PatternMatchMatcher(textToMatch, MatchStrategy.MATCH_EXACTLY);
26 | }
27 |
28 | public static PatternMatchMatcher doesntMatchExactly(String textToMatch) {
29 | return new PatternMatchMatcher(textToMatch, MatchStrategy.NOT_MATCH_EXACTLY);
30 | }
31 |
32 | public static PatternMatchMatcher doesntMatchAnythingFrom(String textToMatch) {
33 | return new PatternMatchMatcher(textToMatch, MatchStrategy.NOT_MATCH_ANYTHING);
34 | }
35 |
36 | public static PatternMatchMatcher matchesSomethingFrom(String textToMatch) {
37 | return new PatternMatchMatcher(textToMatch, MatchStrategy.MATCH_SOMETHING);
38 | }
39 |
40 | @Override
41 | protected boolean matchesSafely(ReadableRegexPattern item) {
42 | Matcher matcher = item.matches(textToMatch);
43 | if (MatchStrategy.MATCH_EXACTLY.equals(matchStrategy)) {
44 | return matcher.matches();
45 | } else if (MatchStrategy.NOT_MATCH_EXACTLY.equals(matchStrategy)) {
46 | return !matcher.matches();
47 | } else if (MatchStrategy.MATCH_SOMETHING.equals(matchStrategy)) {
48 | return matcher.find();
49 | } else if (MatchStrategy.NOT_MATCH_ANYTHING.equals(matchStrategy)) {
50 | return !matcher.find();
51 | }
52 |
53 | throw new RuntimeException("Match strategy was not set correctly. Fix the static construction methods.");
54 | }
55 |
56 | @Override
57 | public void describeTo(Description description) {
58 | description.appendText("Regex should match '").appendText(textToMatch).appendText("'");
59 | }
60 |
61 | @Override
62 | protected void describeMismatchSafely(ReadableRegexPattern item, Description mismatchDescription) {
63 | mismatchDescription.appendText("'").appendText(item.toString()).appendText("'")
64 | .appendText(" doesn't match");
65 | }
66 | }
67 |
--------------------------------------------------------------------------------