Skip to content

Commit da66614

Browse files
committed
added prefix support and precedence
1 parent 71df1b7 commit da66614

File tree

4 files changed

+188
-22
lines changed

4 files changed

+188
-22
lines changed

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,12 @@
4343
import net.sf.oval.constraint.NotNull;
4444

4545
import java.util.Collections;
46+
import java.util.LinkedHashMap;
47+
import java.util.List;
4648
import java.util.Map;
4749
import java.util.regex.Matcher;
4850
import java.util.regex.Pattern;
51+
import java.util.stream.Collectors;
4952

5053
/**
5154
* Implementation of {@link Source} which wraps another {@link Source}
@@ -124,6 +127,10 @@ public void notify(final Observable observable, final Object event) {
124127
// Merge the metrics in the record together
125128
final Record record = (Record) event;
126129
final Map<Key, Map<String, MergingMetric>> mergedMetrics = Maps.newHashMap();
130+
final LinkedHashMap<String, Map<String, String>> variablesMap = new LinkedHashMap<>();
131+
variablesMap.put("dimension", record.getDimensions());
132+
variablesMap.put("env", System.getenv());
133+
127134
for (TransformationSet transformation : _transformations) {
128135
for (final Map.Entry<String, ? extends Metric> metric : record.getMetrics().entrySet()) {
129136
boolean found = false;
@@ -134,9 +141,12 @@ public void notify(final Observable observable, final Object event) {
134141
if (matcher.find()) {
135142
for (final String replacement : findAndReplace.getValue()) {
136143
final RegexAndMapReplacer.Replacement rep =
137-
RegexAndMapReplacer.replaceAll(metricPattern, metricName, replacement, record.getDimensions());
144+
RegexAndMapReplacer.replaceAll(metricPattern, metricName, replacement, variablesMap);
138145
final String replacedString = rep.getReplacement();
139-
final ImmutableList<String> consumedDimensions = rep.getVariablesMatched();
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());
140150

141151
final int tagsStart = replacedString.indexOf(';');
142152
if (tagsStart == -1) {
@@ -197,7 +207,7 @@ public void notify(final Observable observable, final Object event) {
197207
private Key getModifiedDimensions(
198208
final ImmutableMap<String, String> inputDimensions,
199209
final Map<String, String> add,
200-
final ImmutableList<String> remove,
210+
final List<String> remove,
201211
final TransformationSet transformation) {
202212
final Map<String, String> finalTags = Maps.newHashMap(inputDimensions);
203213
// Remove the dimensions that we consumed in the replacement

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

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

1818
import com.google.common.collect.ImmutableList;
19-
import com.google.common.collect.ImmutableMap;
2019

20+
import java.util.LinkedHashMap;
2121
import java.util.Map;
2222
import java.util.regex.Matcher;
2323
import java.util.regex.Pattern;
@@ -43,11 +43,11 @@ public final class RegexAndMapReplacer {
4343
* @param variables map of variables to include
4444
* @return a string with replacement tokens replaced
4545
*/
46-
public static Replacement replaceAll(
46+
public static Replacement replaceAll(
4747
final Pattern pattern,
4848
final String input,
4949
final String replace,
50-
final ImmutableMap<String, String> variables) {
50+
final LinkedHashMap<String, Map<String, String>> variables) {
5151
final Matcher matcher = pattern.matcher(input);
5252
boolean found = matcher.find();
5353
if (found) {
@@ -73,7 +73,7 @@ private static void appendReplacement(
7373
final Matcher matcher,
7474
final String replacement,
7575
final StringBuilder replacementBuilder,
76-
final Map<String, String> variables,
76+
final LinkedHashMap<String, Map<String, String>> variables,
7777
final ImmutableList.Builder<String> variablesUsedBuilder) {
7878
final StringBuilder tokenBuilder = new StringBuilder();
7979
int x = -1;
@@ -112,7 +112,7 @@ private static int writeReplacementToken(
112112
final int offset,
113113
final StringBuilder output,
114114
final Matcher matcher,
115-
final Map<String, String> variables,
115+
final LinkedHashMap<String, Map<String, String>> variables,
116116
final StringBuilder tokenBuilder,
117117
final ImmutableList.Builder<String> variablesUsedBuilder) {
118118
boolean inReplaceBrackets = false;
@@ -175,25 +175,105 @@ private static String getReplacement(
175175
final Matcher matcher,
176176
final String replaceToken,
177177
final boolean numeric,
178-
final Map<String, String> variables,
178+
final LinkedHashMap<String, Map<String, String>> variables,
179179
final ImmutableList.Builder<String> variablesUsedBuilder) {
180180
if (numeric) {
181181
final int replaceGroup = Integer.parseInt(replaceToken);
182182
return matcher.group(replaceGroup);
183183
} else {
184-
try {
185-
return matcher.group(replaceToken);
186-
} catch (final IllegalArgumentException e) { // No group with this name
187-
variablesUsedBuilder.add(replaceToken);
188-
return variables.getOrDefault(replaceToken, "");
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);
201+
202+
} else {
203+
return replacePrefix(matcher, variables, variablesUsedBuilder, prefix, variableName);
189204
}
190205
}
191206
}
192207

208+
private static String replacePrefix(
209+
final Matcher matcher,
210+
final LinkedHashMap<String, Map<String, String>> variables,
211+
final ImmutableList.Builder<String> variablesUsedBuilder,
212+
final String prefix,
213+
final String variableName) {
214+
if (prefix.equals("capture")) {
215+
if (isNumeric(variableName)) {
216+
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 "";
223+
}
224+
}
225+
}
226+
227+
// Only record variables that are not captures
228+
variablesUsedBuilder.add(String.format("%s:%s", prefix, variableName));
229+
230+
final Map<String, String> variableMap = variables.get(prefix);
231+
if (variableMap == null) {
232+
throw new IllegalArgumentException(String.format("could not find map for variables with prefix '%s'", prefix));
233+
}
234+
return variableMap.getOrDefault(variableName, "");
235+
}
236+
237+
private static String replaceNonPrefix(
238+
final Matcher matcher,
239+
final LinkedHashMap<String, Map<String, String>> variables,
240+
final ImmutableList.Builder<String> variablesUsedBuilder,
241+
final String variableName) {
242+
// First try the capture group with the name
243+
try {
244+
return matcher.group(variableName);
245+
} catch (final IllegalArgumentException e) { // No group with this name
246+
// Walk through the variable maps in order to find the first match
247+
for (final Map.Entry<String, Map<String, String>> entry : variables.entrySet()) {
248+
final Map<String, String> variableMap = entry.getValue();
249+
final String replacement = variableMap.get(variableName);
250+
if (replacement != null) {
251+
variablesUsedBuilder.add(String.format("%s:%s", entry.getKey(), variableName));
252+
return replacement;
253+
}
254+
}
255+
}
256+
return "";
257+
}
258+
259+
private static boolean isNumeric(final String string) {
260+
boolean isNumeric = true;
261+
for (int x = 0; x < string.length(); x++) {
262+
if (!Character.isDigit(string.charAt(x))) {
263+
isNumeric = false;
264+
break;
265+
}
266+
}
267+
return isNumeric;
268+
}
269+
193270
private RegexAndMapReplacer() { }
194271

195272
/**
196273
* Describes the replacement string and variables used in it's creation.
274+
*
275+
* The "replacement" field is the resulting string.
276+
* The "variablesMatched" field is a list of input variables that were matched, in prefix:variable form
197277
*/
198278
public static final class Replacement {
199279
public String getReplacement() {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717

1818
import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
1919
import com.carrotsearch.junitbenchmarks.BenchmarkRule;
20-
import com.google.common.collect.ImmutableMap;
2120
import org.junit.Rule;
2221
import org.junit.Test;
2322
import org.junit.rules.TestRule;
2423

24+
import java.util.LinkedHashMap;
2525
import java.util.regex.Pattern;
2626

2727
/**
@@ -34,7 +34,7 @@ public final class RegexAndMapBenchmarkTest {
3434
@BenchmarkOptions(benchmarkRounds = 2000000, warmupRounds = 50000)
3535
@Test
3636
public void testRegexAndMap() {
37-
final String result = RegexAndMapReplacer.replaceAll(PATTERN, INPUT, REPLACE, ImmutableMap.of()).getReplacement();
37+
final String result = RegexAndMapReplacer.replaceAll(PATTERN, INPUT, REPLACE, new LinkedHashMap<>()).getReplacement();
3838
}
3939

4040
@BenchmarkOptions(benchmarkRounds = 2000000, warmupRounds = 50000)

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

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

18+
import com.google.common.collect.ImmutableList;
1819
import com.google.common.collect.ImmutableMap;
1920
import org.junit.Assert;
2021
import org.junit.Test;
2122

23+
import java.util.LinkedHashMap;
24+
import java.util.Map;
2225
import java.util.regex.Pattern;
2326

2427
/**
@@ -41,23 +44,31 @@ public void testInvalidEscape() {
4144
final Pattern pattern = Pattern.compile("test");
4245
final String input = "test";
4346
final String replace = "${\\avariable}"; // \a is an invalid escape sequence
44-
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, ImmutableMap.of()).getReplacement();
47+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
4548
}
4649

4750
@Test(expected = IllegalArgumentException.class)
4851
public void testMissingClosingCurly() {
4952
final Pattern pattern = Pattern.compile("test");
5053
final String input = "test";
5154
final String replace = "${0"; // no ending }
52-
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, ImmutableMap.of()).getReplacement();
55+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
5356
}
5457

5558
@Test(expected = IllegalArgumentException.class)
5659
public void testInvalidEscapeAtEnd() {
5760
final Pattern pattern = Pattern.compile("test");
5861
final String input = "test";
5962
final String replace = "${0}\\"; // trailing \
60-
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, ImmutableMap.of()).getReplacement();
63+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
64+
}
65+
66+
@Test(expected = IllegalArgumentException.class)
67+
public void testInvalidNoVariableName() {
68+
final Pattern pattern = Pattern.compile("test");
69+
final String input = "test";
70+
final String replace = "${prefix:}"; // variable prefix, but no name
71+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
6172
}
6273

6374
@Test
@@ -74,7 +85,7 @@ public void testInvalidReplacementTokenMissingOpen() {
7485
final Pattern pattern = Pattern.compile("test");
7586
final String input = "test";
7687
final String replace = "$variable"; // replacement variable has no {
77-
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, ImmutableMap.of()).getReplacement();
88+
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, new LinkedHashMap<>()).getReplacement();
7889
}
7990

8091
@Test
@@ -167,6 +178,54 @@ public void testSingleMatchPartialMultipleGroupNameOverridesVariablesReplace() {
167178
testExpression(pattern, input, replace, expected, ImmutableMap.of("g1", "bad", "g2", "value"));
168179
}
169180

181+
@Test
182+
public void testMatchPrefix() {
183+
final Pattern pattern = Pattern.compile("input_string_(?<var>.*)_(.*)");
184+
final String input = "input_string_ending_ending2";
185+
final String replace = "${prefix1:var}_${prefix2:var}_${capture:var}_${capture:2}";
186+
final String expected = "var1_var2_ending_ending2";
187+
final LinkedHashMap<String, Map<String, String>> variables = new LinkedHashMap<>();
188+
variables.put("prefix1", ImmutableMap.of("var", "var1"));
189+
variables.put("prefix2", ImmutableMap.of("var", "var2"));
190+
final RegexAndMapReplacer.Replacement replacement = testExpression(pattern, input, replace, expected, variables);
191+
Assert.assertEquals(ImmutableList.of("prefix1:var", "prefix2:var"), replacement.getVariablesMatched());
192+
}
193+
194+
@Test
195+
public void testMatchPrecedence() {
196+
final Pattern pattern = Pattern.compile("input_string_(?<var>.*)");
197+
final String input = "input_string_ending";
198+
final String replace = "${var}_${var2}_${var3}";
199+
final String expected = "ending_1-var2_2-var3";
200+
final LinkedHashMap<String, Map<String, String>> variables = new LinkedHashMap<>();
201+
variables.put("prefix1", ImmutableMap.of("var", "1-var", "var2", "1-var2"));
202+
variables.put("prefix2", ImmutableMap.of("var", "2-var", "var2", "2-var2", "var3", "2-var3"));
203+
final RegexAndMapReplacer.Replacement replacement = testExpression(pattern, input, replace, expected, variables);
204+
Assert.assertEquals(ImmutableList.of("prefix1:var2", "prefix2:var3"), replacement.getVariablesMatched());
205+
}
206+
207+
@Test
208+
public void testMatchMissingVars() {
209+
final Pattern pattern = Pattern.compile("input_string");
210+
final String input = "input_string_ending";
211+
final String replace = "${prefix1:missing}1${capture:missing}2${missing}";
212+
final String expected = "12_ending";
213+
final LinkedHashMap<String, Map<String, String>> variables = new LinkedHashMap<>();
214+
variables.put("prefix1", ImmutableMap.of("var", "var1"));
215+
testExpression(pattern, input, replace, expected, variables);
216+
}
217+
218+
@Test(expected = IllegalArgumentException.class)
219+
public void testMatchInvalidPrefix() {
220+
final Pattern pattern = Pattern.compile("input_string_(?<var>.*)");
221+
final String input = "input_string_ending";
222+
final String replace = "${notfound:var}_value";
223+
final String expected = "illegal arg exception";
224+
final LinkedHashMap<String, Map<String, String>> variables = new LinkedHashMap<>();
225+
variables.put("prefix1", ImmutableMap.of("var", "var1"));
226+
testExpression(pattern, input, replace, expected, variables);
227+
}
228+
170229
@Test
171230
public void testMultipleMatchFullStaticReplace() {
172231
final Pattern pattern = Pattern.compile("test");
@@ -185,13 +244,30 @@ public void testMultipleMatchPartialStaticReplace() {
185244
testExpression(pattern, input, replace, expected, ImmutableMap.of());
186245
}
187246

188-
private void testExpression(final Pattern pattern, final String input, final String replace, final String expected,
247+
private RegexAndMapReplacer.Replacement testExpression(
248+
final Pattern pattern,
249+
final String input,
250+
final String replace,
251+
final String expected,
189252
final ImmutableMap<String, String> variables) {
190-
final String result = RegexAndMapReplacer.replaceAll(pattern, input, replace, variables).getReplacement();
253+
final LinkedHashMap<String, Map<String, String>> variablesMap = new LinkedHashMap<>();
254+
variablesMap.put("myvars", variables);
255+
return testExpression(pattern, input, replace, expected, variablesMap);
256+
}
257+
258+
private RegexAndMapReplacer.Replacement testExpression(
259+
final Pattern pattern,
260+
final String input,
261+
final String replace,
262+
final String expected,
263+
final LinkedHashMap<String, Map<String, String>> variables) {
264+
final RegexAndMapReplacer.Replacement replacement = RegexAndMapReplacer.replaceAll(pattern, input, replace, variables);
265+
final String result = replacement.getReplacement();
191266
Assert.assertEquals(expected, result);
192267
try {
193268
final String stockResult = pattern.matcher(input).replaceAll(replace);
194269
Assert.assertEquals(expected, stockResult);
195270
} catch (final IllegalArgumentException ignored) { }
271+
return replacement;
196272
}
197273
}

0 commit comments

Comments
 (0)