java parameter replacement in a String - java

I'ms looking for a way to replace my variables in a string by their value. Here is my string lookalike:
"cp $myfile1 $myfile2"
In fact, I looked the javadoc and it seems that I could use split() method from String class which is good but I have also seen an article on which it seems that it is possible to replace all my variables with regex and replaceAll() method. Unfortunately I didnt find any example on the last solution.
Is it possible to use replaceAll in my case (with an example)?

No, you can't use String.replaceAll in this case. (You could replace all $... substrings, but each replacement would depend on the actual variable being replaced.)
Here's an example that does a simultaneous replacement which depends on the substring being replaced:
import java.util.*;
import java.util.regex.*;
class Test {
public static void main(String[] args) {
Map<String, String> variables = new HashMap<String, String>() {{
put("myfile1", "/path/to/file1");
put("myfile2", "/path/to/file2");
}};
String input = "cp $myfile1 $myfile2";
// Create a matcher for pattern $\S+
Matcher m = Pattern.compile("\\$(\\S+)").matcher(input);
StringBuffer sb = new StringBuffer();
while (m.find())
m.appendReplacement(sb, variables.get(m.group(1)));
m.appendTail(sb);
System.out.println(sb.toString());
}
}
Output:
cp /path/to/file1 /path/to/file2
(adapted from over here: Replace multiple substrings at once)

I would stick to java and use
public void replace(String s, String placeholder, String value) {
return s.replace(placeholder, value);
}
You could even do multiple replacements with this approach:
public String replace(String s, Map<String, String> placeholderValueMap) {
Iterator<String> iter = placeholderValueMap.keySet().iterator();
while(iter.hasNext()) {
String key = iter.next();
String value = placeholderValueMap.get(key);
s = s.replace(key, value);
}
return s;
}
You could use it like this:
String yourString = "cp $myfile1 $myfile2";
Map<String, String> placeholderValueMap = new HashMap<String, String>();
placeholderValueMap.put("$myfile1", "fileOne");
placeholderValueMap.put("$myfile2", "fileTwo");
someClass.replace(yourString, placeholderValueMap);

Related

Using Regular Expressions to Extract a Value in from a string in Java

I have a string as a#1-b#2-c#3-d#4-e#5-f#6-g#7-h#8-i#9-j#0-k#10-l#11.
I want to create a program such that if I give value as a then it should return a#1, If I give b then it should return b#2 from given string. I am very new to java regular expressions.
Yes, a simple regex should do the trick. Just prepend your input to a regex matching # followed by some numbers (assuming that's the pattern):
String str = "a#1-b#2-c#3-d#4-e#5-f#6-g#7-h#8-i#9-j#0-k#10-l#11";
String input = "a";
Matcher m = Pattern.compile(input + "#\\d+").matcher(str);
if (m.find()) {
System.out.println(m.group());
}
Probably using RegExpo for such simple task is overhead. Just string search:
public static String get(char ch) {
final String str = "a#1-b#2-c#3-d#4-e#5-f#6-g#7-h#8-i#9-j#0-k#10-l#11";
int pos = str.indexOf(ch);
if (pos < 0)
return null;
int end = str.indexOf('-', pos);
return end < 0 ? str.substring(pos) : str.substring(pos, end);
}
Not better than #shmosel's answer, but if you need to repeatedly extract values, you can build a Map once, then each retrieval will be faster (but initial Map construction will be slow):-
Map<String, String> map = Arrays.stream(str.split("-"))
.collect(Collectors.toMap(o -> o.substring(0, o.indexOf('#')).trim(), Function.identity()));
Here's the full code:-
String str = "a#1-b#2-c#3-d#4-e#5-f#6-g#7-h#8-i#9-j#0-k#10-l#11";
Map<String, String> map = Arrays.stream(str.split("-"))
.collect(Collectors.toMap(o -> o.substring(0, o.indexOf('#')).trim(), Function.identity()));
System.out.println(map.get("a"));
Output: a#1

ReplaceAll with java8 lambda functions

Given the following variables
templateText = "Hi ${name}";
variables.put("name", "Joe");
I would like to replace the placeholder ${name} with the value "Joe" using the following code (that does not work)
variables.keySet().forEach(k -> templateText.replaceAll("\\${\\{"+ k +"\\}" variables.get(k)));
However, if I do the "old-style" way, everything works perfectly:
for (Entry<String, String> entry : variables.entrySet()){
String regex = "\\$\\{" + entry.getKey() + "\\}";
templateText = templateText.replaceAll(regex, entry.getValue());
}
Surely I am missing something here :)
Java 8
The proper way to implement this has not changed in Java 8, it is based on appendReplacement()/appendTail():
Pattern variablePattern = Pattern.compile("\\$\\{(.+?)\\}");
Matcher matcher = variablePattern.matcher(templateText);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, variables.get(matcher.group(1)));
}
matcher.appendTail(result);
System.out.println(result);
Note that, as mentioned by drrob in the comments, the replacement String of appendReplacement() may contain group references using the $ sign, and escaping using \. If this is not desired, or if your replacement String can potentially contain those characters, you should escape them using Matcher.quoteReplacement().
Being more functional in Java 8
If you want a more Java-8-style version, you can extract the search-and-replace boiler plate code into a generalized method that takes a replacement Function:
private static StringBuffer replaceAll(String templateText, Pattern pattern,
Function<Matcher, String> replacer) {
Matcher matcher = pattern.matcher(templateText);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, replacer.apply(matcher));
}
matcher.appendTail(result);
return result;
}
and use it as
Pattern variablePattern = Pattern.compile("\\$\\{(.+?)\\}");
StringBuffer result = replaceAll(templateText, variablePattern,
m -> variables.get(m.group(1)));
Note that having a Pattern as parameter (instead of a String) allows it to be stored as a constant instead of recompiling it every time.
Same remark applies as above concerning $ and \ – you may want to enforce the quoteReplacement() inside the replaceAll() method if you don't want your replacer function to handle it.
Java 9 and above
Java 9 introduced Matcher.replaceAll(Function) which basically implements the same thing as the functional version above. See Jesse Glick's answer for more details.
you also can using Stream.reduce(identity,accumulator,combiner).
identity
identity is the initial value for reducing function which is accumulator.
accumulator
accumulator reducing identity to result, which is the identity for the next reducing if the stream is sequentially.
combiner
this function never be called in sequentially stream. it calculate the next identity from identity & result in parallel stream.
BinaryOperator<String> combinerNeverBeCalledInSequentiallyStream=(identity,t) -> {
throw new IllegalStateException("Can't be used in parallel stream");
};
String result = variables.entrySet().stream()
.reduce(templateText
, (it, var) -> it.replaceAll(format("\\$\\{%s\\}", var.getKey())
, var.getValue())
, combinerNeverBeCalledInSequentiallyStream);
import java.util.HashMap;
import java.util.Map;
public class Repl {
public static void main(String[] args) {
Map<String, String> variables = new HashMap<>();
String templateText = "Hi, ${name} ${secondname}! My name is ${name} too :)";
variables.put("name", "Joe");
variables.put("secondname", "White");
templateText = variables.keySet().stream().reduce(templateText, (acc, e) -> acc.replaceAll("\\$\\{" + e + "\\}", variables.get(e)));
System.out.println(templateText);
}
}
output:
Hi, Joe White! My name is Joe too :)
However, it's not the best idea to reinvent the wheel and the preferred way to achieve what you want would be to use apache commons lang as stated here.
Map<String, String> valuesMap = new HashMap<String, String>();
valuesMap.put("animal", "quick brown fox");
valuesMap.put("target", "lazy dog");
String templateString = "The ${animal} jumped over the ${target}.";
StrSubstitutor sub = new StrSubstitutor(valuesMap);
String resolvedString = sub.replace(templateString);
Your code should be changed like below,
String templateText = "Hi ${name}";
Map<String,String> variables = new HashMap<>();
variables.put("name", "Joe");
templateText = variables.keySet().stream().reduce(templateText, (originalText, key) -> originalText.replaceAll("\\$\\{" + key + "\\}", variables.get(key)));
Performing replaceAll repeatedly, i.e. for every replaceable variable, can become quiet expensive, especially as the number of variables might grow. This doesn’t become more efficient when using the Stream API. The regex package contains the necessary building blocks to do this more efficiently:
public static String replaceAll(String template, Map<String,String> variables) {
String pattern = variables.keySet().stream()
.map(Pattern::quote)
.collect(Collectors.joining("|", "\\$\\{(", ")\\}"));
Matcher m = Pattern.compile(pattern).matcher(template);
if(!m.find()) {
return template;
}
StringBuffer sb = new StringBuffer();
do {
m.appendReplacement(sb, Matcher.quoteReplacement(variables.get(m.group(1))));
} while(m.find());
m.appendTail(sb);
return sb.toString();
}
If you are performing the operation with the same Map very often, you may consider keeping the result of Pattern.compile(pattern), as it is immutable and safely shareable.
On the other hand, if you are using this operation with different maps frequently, it might be an option to use a generic pattern instead, combined with handling the possibility that the particular variable is not in the map. The adds the option to report occurrences of the ${…} pattern with an unknown variable:
private static Pattern VARIABLE = Pattern.compile("\\$\\{([^}]*)\\}");
public static String replaceAll(String template, Map<String,String> variables) {
Matcher m = VARIABLE.matcher(template);
if(!m.find())
return template;
StringBuffer sb = new StringBuffer();
do {
m.appendReplacement(sb,
Matcher.quoteReplacement(variables.getOrDefault(m.group(1), m.group(0))));
} while(m.find());
m.appendTail(sb);
return sb.toString();
}
m.group(0) is the actual match, so using this as a fall-back for the replacement string establishes the original behavior of not replacing ${…} occurrences when the key is not in the map. As said, alternative behaviors, like reporting the absent key or using a different fall-back text, are possible.
To update #didier-l’s answer, in Java 9 this is a one-liner!
Pattern.compile("[$][{](.+?)[}]").matcher(templateText).replaceAll(m -> variables.get(m.group(1)))

Java construrctor with string params

How can I extract attributes values from the string parameter ?
public class Pays{
public Pays(String paysDescriptions) {
//implementation
}
}
pays= new Pays("p1:Europe:France, p2:Amerique:Canada");
Edit:
I gave an answer below to people who have never used this type of constructor (like me :p ) and who may need some explanations.
You should try using String.split(String regex) API.
Break the parameter paysDescriptions using comma(,) as regex, then
Break the individual items using colon(:) as regex
Example:
public Pays(String paysDescriptions) {
String[] split_1 = paysDescriptions.split(",");
for (String split : split_1) {
String[] split_2 = split.split(":");
for (String sp : split_2) {
System.out.println(sp); // use sp.trim() if spaces after comma
// not required.
}
}
}
I misunderstand the logic because it's the first time I saw this kind of conctructor..I have only the unit test class and I should implement the code for the source one. So I've used a Map<String,String[]> to split parameters and then I can access to the various attributes of my class.
Map<String, String[]> paysMap = new HashMap<String, String[]>();
public Pays(String paysDescriptions) {
String s = paysDescriptions;
String[] pairs = s.split(",");
for (int i=0;i<pairs.length;i++) {
String pair = pairs[i];
String[] keyValue = pair.split(":");
paysMap.put(String.valueOf(keyValue[0]),new String[] {String.valueOf(keyValue[1]), String.valueOf(keyValue[2])});
}
}

Place all text in quotes into ArrayList

I'm looking for an easy way to take a string and have all values in quotes placed into an ArrayList
Eg
The "car" was "faster" than the "other"
I would like to have an ArrayList that contains
car, faster, other
I think I might need to use RegEx for this but I'm wondering if there is another simpler way.
Using a regex, it is actually quite easy. Note: this solution supposes that there cannot be nested quotes:
private static final Pattern QUOTED = Pattern.compile("\"([^\"]+)\"");
// ...
public List<String> getQuotedWords(final String input)
{
// Note: Java 7 type inference used; in Java 6, use new ArrayList<String>()
final List<String> ret = new ArrayList<>();
final Matcher m = QUOTED.matcher(input);
while (m.find())
ret.add(m.group(1));
return ret;
}
The regex is:
" # find a quote, followed by
([^"]+) # one or more characters not being a quote, captured, followed by
" # a quote
Of course, since this is in a Java string quotes need to be quoted... Hence the Java string for this regex: "\"([^\"]+)\"".
Use this script to parse the input:
public static void main(String[] args) {
String input = "The \"car\" was \"faster\" than the \"other\"";
List<String> output = new ArrayList<String>();
Pattern pattern = Pattern.compile("\"\\w+\"");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
output.add(matcher.group().replaceAll("\"",""));
}
}
Output list contains:
[car,faster,other]
You can use Apache common String Utils substringsBetween method
String[] arr = StringUtils.substringsBetween(input, "\"", "\"");
List<String> = new ArrayList<String>(Arrays.asList(arr));

Replacing variable in String

I have a string which has the below content
String msg = "The variables &CHAG_EF and &CHAG_DNE_DTE can be embedded"
String test1 = "22/10/2010 00:10:12"
String test2 = "25/10/2010 00:01:12"
I need to search the string variable msg for the string "&CHAG_EF" and if it exists replace with the value of test1 and also search the string variable msg for the string "&CHAG_DNE_DTE" and if it exists replace with the value of test2.
How can i replace them?
So simple!
String msg2 = msg.replace("&CHAG_EF", test1).replace("&CHAG_DNE_DTE", test2);
Firstly, Strings can't be modified in Java so you'll need to create new versions with the correct modified values. There are two ways to approach this problem:
Dynamically hard-code all the replacements like other posters have suggested. This isn't scalable with large strings or a large number of replacements; or
You loop through the String looking for potential variables. If they're in your replacement Map then replace them. This is very similar to How to create dynamic Template String.
The code for (2) looks something like this:
public static String replaceAll(String text, Map<String, String> params) {
Pattern p = Pattern.compile("&(\\w+)");
Matcher m = p.matcher(text);
boolean result = m.find();
if (result) {
StringBuffer sb = new StringBuffer();
do {
String replacement = params.get(m.group(1));
if (replacement == null) {
replacement = m.group();
}
m.appendReplacement(sb, replacement);
result = m.find();
} while (result);
m.appendTail(sb);
return sb.toString();
}
return text;
}
Strings in Java are immutable, so any of the "manipulation" methods return a new string rather than modifying the existing one. One nice thing about this is that you can chain operations in many cases, like this:
String result = msg.replace("&CHAG_EF", test1)
.replace("&CHAG_DNE_DTE", test2);
msg = msg.replace("&CHAG_EF", test1).replace("&CHAG_DNE_DTE", test2);

Categories

Resources