Skip to content

Commit 0e7bbb7

Browse files
committed
checkstyle and refactoring
1 parent a5a7796 commit 0e7bbb7

File tree

4 files changed

+134
-67
lines changed

4 files changed

+134
-67
lines changed

src/main/java/com/arpnetworking/utility/RegexAndMapReplacer.java

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package com.arpnetworking.utility;
1717

18-
import scala.Int;
19-
2018
import java.util.Map;
2119
import java.util.regex.Matcher;
2220

@@ -33,7 +31,7 @@ public final class RegexAndMapReplacer {
3331
* @param input input string to match against
3432
* @param replace replacement string
3533
* @param variables map of variables to include
36-
* @return
34+
* @return a string with replacement tokens replaced
3735
*/
3836
public static String replaceAll(final Matcher matcher, final String input, final String replace, final Map<String, String> variables) {
3937

@@ -59,73 +57,87 @@ public static String replaceAll(final Matcher matcher, final String input, final
5957

6058
private static void appendReplacement(final Matcher matcher, final String replacement, final StringBuilder replacementBuilder,
6159
final Map<String, String> variables) {
62-
final StringBuilder tokenStringBuilder = new StringBuilder();
63-
boolean inEscape = false;
64-
boolean readingReplaceToken = false;
65-
boolean inReplaceBrackets = false;
66-
boolean replaceTokenNumeric = true; // assume token is numeric, any non-numeric character will set this to false
67-
for (int x = 0; x < replacement.length(); x++) {
60+
final StringBuilder tokenBuilder = new StringBuilder();
61+
int x = -1;
62+
while (x < replacement.length() - 1) {
63+
x++;
6864
final char c = replacement.charAt(x);
69-
if (!inEscape && c == '\\') {
70-
inEscape = true;
71-
continue;
72-
}
73-
if (inEscape) {
74-
final StringBuilder builder;
75-
if (readingReplaceToken) {
76-
builder = tokenStringBuilder;
65+
if (c == '\\') {
66+
x++;
67+
processEscapedCharacter(replacement, x, replacementBuilder);
68+
} else {
69+
if (c == '$') {
70+
x += writeReplacementToken(replacement, x, replacementBuilder, matcher, variables, tokenBuilder);
7771
} else {
78-
builder = replacementBuilder;
72+
replacementBuilder.append(c);
7973
}
80-
if (c == '\\' || c == '$' || c == '}') {
81-
builder.append(c);
82-
inEscape = false;
74+
}
75+
}
76+
}
77+
78+
private static void processEscapedCharacter(final String replacement, final int x, final StringBuilder builder) {
79+
if (x >= replacement.length()) {
80+
throw new IllegalArgumentException(
81+
String.format("Improper escaping in replacement, must not have trailing '\\' at col %d: %s", x, replacement));
82+
}
83+
final Character c = replacement.charAt(x);
84+
if (c == '\\' || c == '$' || c == '}') {
85+
builder.append(c);
86+
} else {
87+
throw new IllegalArgumentException(
88+
String.format("Improperly escaped '%s' in replacement at col %d: %s", c, x, replacement));
89+
}
90+
}
91+
92+
private static int writeReplacementToken(final String replacement, final int offset, final StringBuilder output,
93+
final Matcher matcher, final Map<String, String> variables, final StringBuilder tokenBuilder) {
94+
boolean inReplaceBrackets = false;
95+
boolean tokenNumeric = true;
96+
tokenBuilder.setLength(0); // reset the shared builder
97+
int x = offset + 1;
98+
char c = replacement.charAt(x);
99+
100+
// Optionally consume the opening brace
101+
if (c == '{') {
102+
inReplaceBrackets = true;
103+
x++;
104+
c = replacement.charAt(x);
105+
}
106+
107+
if (inReplaceBrackets) {
108+
// Consume until we hit the }
109+
while (x < replacement.length() - 1 && c != '}') {
110+
if (c == '\\') {
111+
x++;
112+
processEscapedCharacter(replacement, x, tokenBuilder);
83113
} else {
84-
throw new IllegalArgumentException("Improperly escaped '" + String.valueOf(c) + "' in replacement at col " + x + ": " + replacement);
114+
tokenBuilder.append(c);
85115
}
86-
} else if (readingReplaceToken) {
87-
if (c == '{') {
88-
inReplaceBrackets = true;
89-
continue;
90-
} else if (c == '}' && inReplaceBrackets) {
91-
final String replaceToken = tokenStringBuilder.toString();
92-
replacementBuilder.append(getReplacement(matcher, replaceToken, replaceTokenNumeric, variables));
93-
tokenStringBuilder.setLength(0);
94-
inReplaceBrackets = false;
95-
readingReplaceToken = false;
96-
continue;
97-
} else if (!Character.isDigit(c)) {
98-
if (inReplaceBrackets) {
99-
replaceTokenNumeric = false;
100-
tokenStringBuilder.append(c);
101-
} else {
102-
final String replaceToken = tokenStringBuilder.toString();
103-
if (replaceToken.isEmpty()) {
104-
throw new IllegalArgumentException("Non-numeric replacements must be of the form ${val}. Missing '{' at col "
105-
+ x + ": " + replacement);
106-
}
107-
replacementBuilder.append(getReplacement(matcher, replaceToken, replaceTokenNumeric, variables));
108-
tokenStringBuilder.setLength(0);
109-
readingReplaceToken = false;
110-
x--; // We can't process the character because we are no longer in the numeric group syntax, we need to process the
111-
// $n replacement and then evaluate this character again.
112-
continue;
113-
}
114-
} else {
115-
tokenStringBuilder.append(c);
116+
if (tokenNumeric && !Character.isDigit(c)) {
117+
tokenNumeric = false;
116118
}
117-
} else {
118-
if (c == '$') {
119-
readingReplaceToken = true;
119+
x++;
120+
c = replacement.charAt(x);
121+
}
122+
if (c != '}') {
123+
throw new IllegalArgumentException("Invalid replacement token, expected '}' at col " + x + ": " + replacement);
124+
}
125+
x++; // Consume the }
126+
output.append(getReplacement(matcher, tokenBuilder.toString(), tokenNumeric, variables));
127+
} else {
128+
// Consume until we hit a non-digit character
129+
while (x < replacement.length()) {
130+
c = replacement.charAt(x);
131+
if (Character.isDigit(c)) {
132+
tokenBuilder.append(c);
120133
} else {
121-
replacementBuilder.append(c);
134+
break;
122135
}
136+
x++;
123137
}
138+
output.append(getReplacement(matcher, tokenBuilder.toString(), true, variables));
124139
}
125-
if (tokenStringBuilder.length() > 0 && replaceTokenNumeric) {
126-
final String replaceToken = tokenStringBuilder.toString();
127-
replacementBuilder.append(getReplacement(matcher, replaceToken, true, variables));
128-
}
140+
return x - offset - 1;
129141
}
130142

131143
private static String getReplacement(final Matcher matcher, final String replaceToken, final boolean numeric,
@@ -142,5 +154,5 @@ private static String getReplacement(final Matcher matcher, final String replace
142154
}
143155
}
144156

145-
private RegexAndMapReplacer () { }
157+
private RegexAndMapReplacer() { }
146158
}

src/test/java/com/arpnetworking/metrics/mad/sources/MappingSourceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void setUp() {
5454
"foo/([^/]*)/bar", ImmutableList.of("foo/bar"),
5555
"cat/([^/]*)/dog", ImmutableList.of("cat/dog", "cat/dog/$1"),
5656
"tagged/([^/]*)/dog", ImmutableList.of("tagged/dog;animal=$1"),
57-
"tagged/([^/]*)/dog", ImmutableList.of("tagged/$animal/dog")))
57+
"tagged/([^/]*)/animal", ImmutableList.of("tagged/$animal/animal")))
5858
.setSource(_mockSource);
5959
}
6060

src/test/java/com/arpnetworking/utility/RegexAndMapBenchmarkTest.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
1+
/**
2+
* Copyright 2018 Inscope Metrics, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package com.arpnetworking.utility;
217

318
import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
419
import com.carrotsearch.junitbenchmarks.BenchmarkRule;
5-
import com.google.common.collect.ImmutableMap;
620
import org.junit.Rule;
721
import org.junit.Test;
822
import org.junit.rules.TestRule;
923

1024
import java.util.Collections;
1125
import java.util.regex.Pattern;
1226

27+
/**
28+
* Benchmark tests comparing the RegexAndMapReplacer class against the built-in regex replacement.
29+
*
30+
* @author Brandon Arp (brandon dot arp at inscopemetrics dot com)
31+
*/
1332
public class RegexAndMapBenchmarkTest {
14-
@Rule
15-
public TestRule benchmarkRun = new BenchmarkRule();
1633

1734
@BenchmarkOptions(benchmarkRounds = 2000000, warmupRounds = 50000)
1835
@Test
@@ -26,7 +43,9 @@ public void testRegex() {
2643
final String result = PATTERN.matcher(INPUT).replaceAll(REPLACE);
2744
}
2845

46+
@Rule
47+
public TestRule _benchmarkRun = new BenchmarkRule();
2948
private static final String REPLACE = "this is a ${g1} pattern called ${g2}";
30-
private static Pattern PATTERN = Pattern.compile("(?<g1>test)/pattern/(?<g2>foo)");
49+
private static final Pattern PATTERN = Pattern.compile("(?<g1>test)/pattern/(?<g2>foo)");
3150
private static final String INPUT = "test/pattern/foo";
3251
}

src/test/java/com/arpnetworking/utility/RegexAndMapReplacerTest.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/**
2+
* Copyright 2018 Inscope Metrics, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package com.arpnetworking.utility;
217

318
import com.google.common.collect.ImmutableMap;
@@ -8,6 +23,11 @@
823
import java.util.Map;
924
import java.util.regex.Pattern;
1025

26+
/**
27+
* Tests for the {@link RegexAndMapReplacer} class.
28+
*
29+
* @author Brandon Arp (brandon dot arp at inscopemetrics dot com)
30+
*/
1131
public class RegexAndMapReplacerTest {
1232
@Test
1333
public void testNoMatch() {
@@ -26,6 +46,22 @@ public void testInvalidEscape() {
2646
final String result = RegexAndMapReplacer.replaceAll(pattern.matcher(input), input, replace, Collections.emptyMap());
2747
}
2848

49+
@Test(expected = IllegalArgumentException.class)
50+
public void testMissingClosingCurly() {
51+
final Pattern pattern = Pattern.compile("test");
52+
final String input = "test";
53+
final String replace = "${0"; // no ending }
54+
final String result = RegexAndMapReplacer.replaceAll(pattern.matcher(input), input, replace, Collections.emptyMap());
55+
}
56+
57+
@Test(expected = IllegalArgumentException.class)
58+
public void testInvalidEscapeAtEnd() {
59+
final Pattern pattern = Pattern.compile("test");
60+
final String input = "test";
61+
final String replace = "${0}\\"; // trailing \
62+
final String result = RegexAndMapReplacer.replaceAll(pattern.matcher(input), input, replace, Collections.emptyMap());
63+
}
64+
2965
@Test
3066
public void testNumericWithClosingCurly() {
3167
final Pattern pattern = Pattern.compile("test");
@@ -158,6 +194,6 @@ private void testExpression(final Pattern pattern, final String input, final Str
158194
try {
159195
final String stockResult = pattern.matcher(input).replaceAll(replace);
160196
Assert.assertEquals(expected, stockResult);
161-
} catch (final IllegalArgumentException ignored) {}
197+
} catch (final IllegalArgumentException ignored) { }
162198
}
163199
}

0 commit comments

Comments
 (0)