Skip to content

Commit 35b351d

Browse files
committed
remove $n replacements, numerics to normal precedence
1 parent 945d41d commit 35b351d

File tree

5 files changed

+100
-82
lines changed

5 files changed

+100
-82
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ ex:
324324
tranformations is a list of TransformationSet objects. Each TransformationSet has an inject (Map\<String, DimensionInjection\>),
325325
remove (List\<String\>) and a findAndReplace (Map\<String, List\<String\>\>).
326326

327-
A DimensionInjection is just a value and a boolean of whether or not to overwrite existing values.
327+
A DimensionInjection is just a value and a boolean of whether or not to overwrite existing values. Of omitted, overwrite defaults to true.
328328

329329
The keys in findAndReplace are regular expressions used to match metrics. If matched, the list of replacements is
330330
executed, allowing for a single input metric to be recorded multiple times with different names or dimensions.

src/main/java/com/arpnetworking/metrics/mad/sources/TransformingSource.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import java.util.Map;
4949
import java.util.regex.Matcher;
5050
import java.util.regex.Pattern;
51-
import java.util.stream.Collectors;
5251

5352
/**
5453
* Implementation of {@link Source} which wraps another {@link Source}
@@ -143,10 +142,8 @@ public void notify(final Observable observable, final Object event) {
143142
final RegexAndMapReplacer.Replacement rep =
144143
RegexAndMapReplacer.replaceAll(metricPattern, metricName, replacement, variablesMap);
145144
final String replacedString = rep.getReplacement();
146-
final List<String> consumedDimensions = rep.getVariablesMatched().stream()
147-
.filter(var -> var.startsWith("dimension:") || var.indexOf(':') == -1) // Only dimension vars
148-
.map(var -> var.substring(var.indexOf(":") + 1)) // Strip the prefix
149-
.collect(Collectors.toList());
145+
final List<String> consumedDimensions = rep.getVariablesMatched()
146+
.getOrDefault("dimension", ImmutableList.of());
150147

151148
final int tagsStart = replacedString.indexOf(';');
152149
if (tagsStart == -1) {

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

Lines changed: 61 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@
1616
package com.arpnetworking.utility;
1717

1818
import com.google.common.collect.ImmutableList;
19+
import com.google.common.collect.ImmutableMap;
20+
import com.google.common.collect.Maps;
1921

2022
import java.util.LinkedHashMap;
2123
import java.util.Map;
2224
import java.util.regex.Matcher;
2325
import java.util.regex.Pattern;
26+
import java.util.stream.Collectors;
2427

2528
/**
2629
* A regex replacement utility that can also replace tokens not found in the regex.
2730
*
28-
* $n where n is a number in the replace string is replaced by the pattern's match group n
2931
* ${name} in the replace string is replaced by the pattern's named capture, or by the value of the variable
3032
* with that name from the variables map
3133
* \ is used as an escape character and may be used to escape '$', '{', and '}' characters so that they will
@@ -43,38 +45,44 @@ public final class RegexAndMapReplacer {
4345
* @param variables map of variables to include
4446
* @return a string with replacement tokens replaced
4547
*/
46-
public static Replacement replaceAll(
48+
public static Replacement replaceAll(
4749
final Pattern pattern,
4850
final String input,
4951
final String replace,
5052
final LinkedHashMap<String, Map<String, String>> variables) {
5153
final Matcher matcher = pattern.matcher(input);
5254
boolean found = matcher.find();
5355
if (found) {
54-
final ImmutableList.Builder<String> variablesUsedBuilder = ImmutableList.builder();
56+
final Map<String, ImmutableList.Builder<String>> variablesUsed = Maps.newHashMap();
5557
final StringBuilder builder = new StringBuilder();
5658
int lastMatchedIndex = 0;
5759
do {
5860
builder.append(input.substring(lastMatchedIndex, matcher.start()));
5961
lastMatchedIndex = matcher.end();
60-
appendReplacement(matcher, replace, builder, variables, variablesUsedBuilder);
62+
appendReplacement(matcher, replace, builder, variables, variablesUsed);
6163
found = matcher.find();
6264
} while (found);
6365
// Append left-over string after the matches
6466
if (lastMatchedIndex < input.length() - 1) {
6567
builder.append(input.substring(lastMatchedIndex, input.length()));
6668
}
67-
return new Replacement(builder.toString(), variablesUsedBuilder.build());
69+
final ImmutableMap<String, ImmutableList<String>> immutableVars = variablesUsed.entrySet()
70+
.stream()
71+
.collect(
72+
Collectors.collectingAndThen(
73+
Collectors.toMap(ImmutableMap.Entry::getKey, entry -> entry.getValue().build()),
74+
ImmutableMap::copyOf));
75+
return new Replacement(builder.toString(), immutableVars);
6876
}
69-
return new Replacement(input, ImmutableList.of());
77+
return new Replacement(input, ImmutableMap.of());
7078
}
7179

7280
private static void appendReplacement(
7381
final Matcher matcher,
7482
final String replacement,
7583
final StringBuilder replacementBuilder,
7684
final LinkedHashMap<String, Map<String, String>> variables,
77-
final ImmutableList.Builder<String> variablesUsedBuilder) {
85+
final Map<String, ImmutableList.Builder<String>> variablesUsed) {
7886
final StringBuilder tokenBuilder = new StringBuilder();
7987
int x = -1;
8088
while (x < replacement.length() - 1) {
@@ -85,7 +93,7 @@ private static void appendReplacement(
8593
processEscapedCharacter(replacement, x, replacementBuilder);
8694
} else {
8795
if (c == '$') {
88-
x += writeReplacementToken(replacement, x, replacementBuilder, matcher, variables, tokenBuilder, variablesUsedBuilder);
96+
x += writeReplacementToken(replacement, x, replacementBuilder, matcher, variables, tokenBuilder, variablesUsed);
8997
} else {
9098
replacementBuilder.append(c);
9199
}
@@ -114,7 +122,7 @@ private static int writeReplacementToken(
114122
final Matcher matcher,
115123
final LinkedHashMap<String, Map<String, String>> variables,
116124
final StringBuilder tokenBuilder,
117-
final ImmutableList.Builder<String> variablesUsedBuilder) {
125+
final Map<String, ImmutableList.Builder<String>> variablesUsed) {
118126
boolean inReplaceBrackets = false;
119127
boolean tokenNumeric = true;
120128
tokenBuilder.setLength(0); // reset the shared builder
@@ -147,85 +155,68 @@ private static int writeReplacementToken(
147155
throw new IllegalArgumentException("Invalid replacement token, expected '}' at col " + x + ": " + replacement);
148156
}
149157
x++; // Consume the }
150-
output.append(getReplacement(matcher, tokenBuilder.toString(), tokenNumeric, variables, variablesUsedBuilder));
158+
output.append(getReplacement(matcher, tokenBuilder.toString(), variables, variablesUsed));
151159
} else {
152-
// Consume until we hit a non-digit character
153-
while (x < replacement.length()) {
154-
c = replacement.charAt(x);
155-
if (Character.isDigit(c)) {
156-
tokenBuilder.append(c);
157-
} else {
158-
break;
159-
}
160-
x++;
161-
}
162-
if (tokenBuilder.length() == 0) {
163160
throw new IllegalArgumentException(
164161
String.format(
165-
"Invalid replacement token, non-numeric tokens must be surrounded by { } at col %d: %s",
162+
"Invalid replacement token, tokens must be surrounded by { } at col %d: %s",
166163
x,
167164
replacement));
168-
}
169-
output.append(getReplacement(matcher, tokenBuilder.toString(), true, variables, variablesUsedBuilder));
170165
}
171166
return x - offset - 1;
172167
}
173168

174169
private static String getReplacement(
175170
final Matcher matcher,
176171
final String replaceToken,
177-
final boolean numeric,
178172
final LinkedHashMap<String, Map<String, String>> variables,
179-
final ImmutableList.Builder<String> variablesUsedBuilder) {
180-
if (numeric) {
181-
final int replaceGroup = Integer.parseInt(replaceToken);
182-
return matcher.group(replaceGroup);
173+
final Map<String, ImmutableList.Builder<String>> variablesUsed) {
174+
final int prefixSeparatorIndex = replaceToken.indexOf(':');
175+
final String prefix;
176+
final String variableName;
177+
if (prefixSeparatorIndex == replaceToken.length() - 1) {
178+
throw new IllegalArgumentException(
179+
String.format("found prefix in variable replacement, but no variable name found: '%s'",
180+
replaceToken));
181+
}
182+
if (prefixSeparatorIndex != -1) {
183+
prefix = replaceToken.substring(0, prefixSeparatorIndex);
184+
variableName = replaceToken.substring(prefixSeparatorIndex + 1);
183185
} else {
184-
final int prefixSeparatorIndex = replaceToken.indexOf(':');
185-
final String prefix;
186-
final String variableName;
187-
if (prefixSeparatorIndex == replaceToken.length() - 1) {
188-
throw new IllegalArgumentException(
189-
String.format("found prefix in variable replacement, but no variable name found: '%s'",
190-
replaceToken));
191-
}
192-
if (prefixSeparatorIndex != -1) {
193-
prefix = replaceToken.substring(0, prefixSeparatorIndex);
194-
variableName = replaceToken.substring(prefixSeparatorIndex + 1);
195-
} else {
196-
prefix = "";
197-
variableName = replaceToken;
198-
}
199-
if (prefix.isEmpty()) {
200-
return replaceNonPrefix(matcher, variables, variablesUsedBuilder, variableName);
186+
prefix = "";
187+
variableName = replaceToken;
188+
}
189+
if (prefix.isEmpty()) {
190+
return replaceNonPrefix(matcher, variables, variablesUsed, variableName);
201191

202-
} else {
203-
return replacePrefix(matcher, variables, variablesUsedBuilder, prefix, variableName);
204-
}
192+
} else {
193+
return replacePrefix(matcher, variables, variablesUsed, prefix, variableName);
205194
}
206195
}
207196

208197
private static String replacePrefix(
209198
final Matcher matcher,
210199
final LinkedHashMap<String, Map<String, String>> variables,
211-
final ImmutableList.Builder<String> variablesUsedBuilder,
200+
final Map<String, ImmutableList.Builder<String>> variablesUsed,
212201
final String prefix,
213202
final String variableName) {
214203
if (prefix.equals("capture")) {
215204
if (isNumeric(variableName)) {
216205
final Integer groupIndex = Integer.valueOf(variableName);
217-
return matcher.group(groupIndex);
218-
} else {
219-
try {
220-
return matcher.group(variableName);
221-
} catch (final IllegalArgumentException ignored2) { // No group with this name
222-
return "";
206+
if (groupIndex >= 0 && groupIndex <= matcher.groupCount()) {
207+
return matcher.group(groupIndex);
223208
}
224209
}
210+
211+
try {
212+
return matcher.group(variableName);
213+
} catch (final IllegalArgumentException ignored) { // No group with this name
214+
return "";
215+
}
225216
}
226217

227218
// Only record variables that are not captures
228-
variablesUsedBuilder.add(String.format("%s:%s", prefix, variableName));
219+
variablesUsed.computeIfAbsent(prefix, key -> ImmutableList.builder()).add(variableName);
229220

230221
final Map<String, String> variableMap = variables.get(prefix);
231222
if (variableMap == null) {
@@ -237,9 +228,16 @@ private static String replacePrefix(
237228
private static String replaceNonPrefix(
238229
final Matcher matcher,
239230
final LinkedHashMap<String, Map<String, String>> variables,
240-
final ImmutableList.Builder<String> variablesUsedBuilder,
231+
final Map<String, ImmutableList.Builder<String>> variablesUsed,
241232
final String variableName) {
242-
// First try the capture group with the name
233+
// First try to check against the capture group number
234+
if (isNumeric(variableName)) {
235+
final int replaceGroup = Integer.parseInt(variableName);
236+
if (replaceGroup >= 0 && replaceGroup <= matcher.groupCount()) {
237+
return matcher.group(replaceGroup);
238+
}
239+
}
240+
// Then try the capture group with the name
243241
try {
244242
return matcher.group(variableName);
245243
} catch (final IllegalArgumentException e) { // No group with this name
@@ -248,7 +246,7 @@ private static String replaceNonPrefix(
248246
final Map<String, String> variableMap = entry.getValue();
249247
final String replacement = variableMap.get(variableName);
250248
if (replacement != null) {
251-
variablesUsedBuilder.add(String.format("%s:%s", entry.getKey(), variableName));
249+
variablesUsed.computeIfAbsent(entry.getKey(), key -> ImmutableList.builder()).add(variableName);
252250
return replacement;
253251
}
254252
}
@@ -280,17 +278,17 @@ public String getReplacement() {
280278
return _replacement;
281279
}
282280

283-
public ImmutableList<String> getVariablesMatched() {
281+
public ImmutableMap<String, ImmutableList<String>> getVariablesMatched() {
284282
return _variablesMatched;
285283
}
286284

287-
private Replacement(final String replacement, final ImmutableList<String> variablesMatched) {
285+
private Replacement(final String replacement, final ImmutableMap<String, ImmutableList<String>> variablesMatched) {
288286

289287
_replacement = replacement;
290288
_variablesMatched = variablesMatched;
291289
}
292290

293291
private final String _replacement;
294-
private final ImmutableList<String> _variablesMatched;
292+
private final ImmutableMap<String, ImmutableList<String>> _variablesMatched;
295293
}
296294
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ public void setUp() {
5151
_transformSetBuilder = new TransformingSource.TransformationSet.Builder()
5252
.setFindAndReplace(ImmutableMap.of(
5353
"foo/([^/]*)/bar", ImmutableList.of("foo/bar"),
54-
"cat/([^/]*)/dog", ImmutableList.of("cat/dog", "cat/dog/$1"),
55-
"tagged/([^/]*)/dog", ImmutableList.of("tagged/dog;animal=$1"),
54+
"cat/([^/]*)/dog", ImmutableList.of("cat/dog", "cat/dog/${1}"),
55+
"tagged/([^/]*)/dog", ImmutableList.of("tagged/dog;animal=${1}"),
5656
"named/(?<animal>[^/]*)", ImmutableList.of("named/extracted_animal;extracted=${animal}"),
5757
"tagged/([^/]*)/animal", ImmutableList.of("tagged/${animal}/animal")));
5858
_transformingSourceBuilder = new TransformingSource.Builder()

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

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,28 +71,27 @@ public void testInvalidNoVariableName() {
7171
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
7272
}
7373

74-
@Test
75-
public void testNumericWithClosingCurly() {
74+
@Test(expected = IllegalArgumentException.class)
75+
public void testInvalidReplacementTokenMissingOpen() {
7676
final Pattern pattern = Pattern.compile("test");
7777
final String input = "test";
78-
final String replace = "$0}";
79-
final String expected = "test}";
80-
testExpression(pattern, input, replace, expected, ImmutableMap.of());
78+
final String replace = "$variable"; // replacement variable has no {
79+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
8180
}
8281

8382
@Test(expected = IllegalArgumentException.class)
84-
public void testInvalidReplacementTokenMissingOpen() {
83+
public void testInvalidReplacementTokenNumeric() {
8584
final Pattern pattern = Pattern.compile("test");
8685
final String input = "test";
87-
final String replace = "$variable"; // replacement variable has no {
86+
final String replace = "$0"; // replacement variable has no {
8887
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
8988
}
9089

9190
@Test
9291
public void testGroup0Replace() {
9392
final Pattern pattern = Pattern.compile("test");
9493
final String input = "test";
95-
final String replace = "$0";
94+
final String replace = "${0}";
9695
final String expected = "test";
9796
testExpression(pattern, input, replace, expected, ImmutableMap.of());
9897
}
@@ -128,7 +127,7 @@ public void testSingleMatchPartialStaticReplacePrefix() {
128127
public void testSingleMatchPartialMultipleGroupNumberReplace() {
129128
final Pattern pattern = Pattern.compile("(test)/pattern/(foo)");
130129
final String input = "test/pattern/foo";
131-
final String replace = "this is a $1 pattern called $2";
130+
final String replace = "this is a ${1} pattern called ${2}";
132131
final String expected = "this is a test pattern called foo";
133132
testExpression(pattern, input, replace, expected, ImmutableMap.of());
134133
}
@@ -142,6 +141,26 @@ public void testSingleMatchPartialMultipleGroupNameReplace() {
142141
testExpression(pattern, input, replace, expected, ImmutableMap.of());
143142
}
144143

144+
@Test
145+
public void testSingleVariableReplaceAsNumber() {
146+
final Pattern pattern = Pattern.compile("test/pattern/foo");
147+
final String input = "test/pattern/foo";
148+
final String replace = "this is a ${1} pattern";
149+
final String expected = "this is a test pattern";
150+
testExpression(pattern, input, replace, expected, ImmutableMap.of("1", "test"));
151+
}
152+
153+
@Test
154+
public void testSingleVariableReplaceAsNumberPrefixed() {
155+
final Pattern pattern = Pattern.compile("test/pattern/foo");
156+
final String input = "test/pattern/foo";
157+
final String replace = "this is a ${prefix1:1} pattern";
158+
final String expected = "this is a test pattern";
159+
final LinkedHashMap<String, Map<String, String>> variables = new LinkedHashMap<>();
160+
variables.put("prefix1", ImmutableMap.of("1", "test"));
161+
testExpression(pattern, input, replace, expected, variables);
162+
}
163+
145164
@Test
146165
public void testSingleMatchPartialMultipleVariableReplace() {
147166
final Pattern pattern = Pattern.compile("test/pattern/foo");
@@ -188,7 +207,9 @@ public void testMatchPrefix() {
188207
variables.put("prefix1", ImmutableMap.of("var", "var1"));
189208
variables.put("prefix2", ImmutableMap.of("var", "var2"));
190209
final RegexAndMapReplacer.Replacement replacement = testExpression(pattern, input, replace, expected, variables);
191-
Assert.assertEquals(ImmutableList.of("prefix1:var", "prefix2:var"), replacement.getVariablesMatched());
210+
Assert.assertEquals(
211+
ImmutableMap.of("prefix1", ImmutableList.of("var"), "prefix2", ImmutableList.of("var")),
212+
replacement.getVariablesMatched());
192213
}
193214

194215
@Test
@@ -201,7 +222,9 @@ public void testMatchPrecedence() {
201222
variables.put("prefix1", ImmutableMap.of("var", "1-var", "var2", "1-var2"));
202223
variables.put("prefix2", ImmutableMap.of("var", "2-var", "var2", "2-var2", "var3", "2-var3"));
203224
final RegexAndMapReplacer.Replacement replacement = testExpression(pattern, input, replace, expected, variables);
204-
Assert.assertEquals(ImmutableList.of("prefix1:var2", "prefix2:var3"), replacement.getVariablesMatched());
225+
Assert.assertEquals(
226+
ImmutableMap.of("prefix1", ImmutableList.of("var2"), "prefix2", ImmutableList.of("var3")),
227+
replacement.getVariablesMatched());
205228
}
206229

207230
@Test

0 commit comments

Comments
 (0)