16
16
package com .arpnetworking .utility ;
17
17
18
18
import com .google .common .collect .ImmutableList ;
19
+ import com .google .common .collect .ImmutableMap ;
20
+ import com .google .common .collect .Maps ;
19
21
20
22
import java .util .LinkedHashMap ;
21
23
import java .util .Map ;
22
24
import java .util .regex .Matcher ;
23
25
import java .util .regex .Pattern ;
26
+ import java .util .stream .Collectors ;
24
27
25
28
/**
26
29
* A regex replacement utility that can also replace tokens not found in the regex.
27
30
*
28
- * $n where n is a number in the replace string is replaced by the pattern's match group n
29
31
* ${name} in the replace string is replaced by the pattern's named capture, or by the value of the variable
30
32
* with that name from the variables map
31
33
* \ 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 {
43
45
* @param variables map of variables to include
44
46
* @return a string with replacement tokens replaced
45
47
*/
46
- public static Replacement replaceAll (
48
+ public static Replacement replaceAll (
47
49
final Pattern pattern ,
48
50
final String input ,
49
51
final String replace ,
50
52
final LinkedHashMap <String , Map <String , String >> variables ) {
51
53
final Matcher matcher = pattern .matcher (input );
52
54
boolean found = matcher .find ();
53
55
if (found ) {
54
- final ImmutableList .Builder <String > variablesUsedBuilder = ImmutableList . builder ();
56
+ final Map < String , ImmutableList .Builder <String >> variablesUsed = Maps . newHashMap ();
55
57
final StringBuilder builder = new StringBuilder ();
56
58
int lastMatchedIndex = 0 ;
57
59
do {
58
60
builder .append (input .substring (lastMatchedIndex , matcher .start ()));
59
61
lastMatchedIndex = matcher .end ();
60
- appendReplacement (matcher , replace , builder , variables , variablesUsedBuilder );
62
+ appendReplacement (matcher , replace , builder , variables , variablesUsed );
61
63
found = matcher .find ();
62
64
} while (found );
63
65
// Append left-over string after the matches
64
66
if (lastMatchedIndex < input .length () - 1 ) {
65
67
builder .append (input .substring (lastMatchedIndex , input .length ()));
66
68
}
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 );
68
76
}
69
- return new Replacement (input , ImmutableList .of ());
77
+ return new Replacement (input , ImmutableMap .of ());
70
78
}
71
79
72
80
private static void appendReplacement (
73
81
final Matcher matcher ,
74
82
final String replacement ,
75
83
final StringBuilder replacementBuilder ,
76
84
final LinkedHashMap <String , Map <String , String >> variables ,
77
- final ImmutableList .Builder <String > variablesUsedBuilder ) {
85
+ final Map < String , ImmutableList .Builder <String >> variablesUsed ) {
78
86
final StringBuilder tokenBuilder = new StringBuilder ();
79
87
int x = -1 ;
80
88
while (x < replacement .length () - 1 ) {
@@ -85,7 +93,7 @@ private static void appendReplacement(
85
93
processEscapedCharacter (replacement , x , replacementBuilder );
86
94
} else {
87
95
if (c == '$' ) {
88
- x += writeReplacementToken (replacement , x , replacementBuilder , matcher , variables , tokenBuilder , variablesUsedBuilder );
96
+ x += writeReplacementToken (replacement , x , replacementBuilder , matcher , variables , tokenBuilder , variablesUsed );
89
97
} else {
90
98
replacementBuilder .append (c );
91
99
}
@@ -114,7 +122,7 @@ private static int writeReplacementToken(
114
122
final Matcher matcher ,
115
123
final LinkedHashMap <String , Map <String , String >> variables ,
116
124
final StringBuilder tokenBuilder ,
117
- final ImmutableList .Builder <String > variablesUsedBuilder ) {
125
+ final Map < String , ImmutableList .Builder <String >> variablesUsed ) {
118
126
boolean inReplaceBrackets = false ;
119
127
boolean tokenNumeric = true ;
120
128
tokenBuilder .setLength (0 ); // reset the shared builder
@@ -147,85 +155,68 @@ private static int writeReplacementToken(
147
155
throw new IllegalArgumentException ("Invalid replacement token, expected '}' at col " + x + ": " + replacement );
148
156
}
149
157
x ++; // Consume the }
150
- output .append (getReplacement (matcher , tokenBuilder .toString (), tokenNumeric , variables , variablesUsedBuilder ));
158
+ output .append (getReplacement (matcher , tokenBuilder .toString (), variables , variablesUsed ));
151
159
} 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 ) {
163
160
throw new IllegalArgumentException (
164
161
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" ,
166
163
x ,
167
164
replacement ));
168
- }
169
- output .append (getReplacement (matcher , tokenBuilder .toString (), true , variables , variablesUsedBuilder ));
170
165
}
171
166
return x - offset - 1 ;
172
167
}
173
168
174
169
private static String getReplacement (
175
170
final Matcher matcher ,
176
171
final String replaceToken ,
177
- final boolean numeric ,
178
172
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 );
183
185
} 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 );
201
191
202
- } else {
203
- return replacePrefix (matcher , variables , variablesUsedBuilder , prefix , variableName );
204
- }
192
+ } else {
193
+ return replacePrefix (matcher , variables , variablesUsed , prefix , variableName );
205
194
}
206
195
}
207
196
208
197
private static String replacePrefix (
209
198
final Matcher matcher ,
210
199
final LinkedHashMap <String , Map <String , String >> variables ,
211
- final ImmutableList .Builder <String > variablesUsedBuilder ,
200
+ final Map < String , ImmutableList .Builder <String >> variablesUsed ,
212
201
final String prefix ,
213
202
final String variableName ) {
214
203
if (prefix .equals ("capture" )) {
215
204
if (isNumeric (variableName )) {
216
205
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 );
223
208
}
224
209
}
210
+
211
+ try {
212
+ return matcher .group (variableName );
213
+ } catch (final IllegalArgumentException ignored ) { // No group with this name
214
+ return "" ;
215
+ }
225
216
}
226
217
227
218
// 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 );
229
220
230
221
final Map <String , String > variableMap = variables .get (prefix );
231
222
if (variableMap == null ) {
@@ -237,9 +228,16 @@ private static String replacePrefix(
237
228
private static String replaceNonPrefix (
238
229
final Matcher matcher ,
239
230
final LinkedHashMap <String , Map <String , String >> variables ,
240
- final ImmutableList .Builder <String > variablesUsedBuilder ,
231
+ final Map < String , ImmutableList .Builder <String >> variablesUsed ,
241
232
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
243
241
try {
244
242
return matcher .group (variableName );
245
243
} catch (final IllegalArgumentException e ) { // No group with this name
@@ -248,7 +246,7 @@ private static String replaceNonPrefix(
248
246
final Map <String , String > variableMap = entry .getValue ();
249
247
final String replacement = variableMap .get (variableName );
250
248
if (replacement != null ) {
251
- variablesUsedBuilder . add ( String . format ( "%s:%s" , entry .getKey (), variableName ) );
249
+ variablesUsed . computeIfAbsent ( entry .getKey (), key -> ImmutableList . builder ()). add ( variableName );
252
250
return replacement ;
253
251
}
254
252
}
@@ -280,17 +278,17 @@ public String getReplacement() {
280
278
return _replacement ;
281
279
}
282
280
283
- public ImmutableList <String > getVariablesMatched () {
281
+ public ImmutableMap < String , ImmutableList <String > > getVariablesMatched () {
284
282
return _variablesMatched ;
285
283
}
286
284
287
- private Replacement (final String replacement , final ImmutableList <String > variablesMatched ) {
285
+ private Replacement (final String replacement , final ImmutableMap < String , ImmutableList <String > > variablesMatched ) {
288
286
289
287
_replacement = replacement ;
290
288
_variablesMatched = variablesMatched ;
291
289
}
292
290
293
291
private final String _replacement ;
294
- private final ImmutableList <String > _variablesMatched ;
292
+ private final ImmutableMap < String , ImmutableList <String > > _variablesMatched ;
295
293
}
296
294
}
0 commit comments