How to generalize utility function using method references/java 8 - java

I've run into a common situation where i have a list of objects and need to generate a comma separated string with a single property, which are then each surrounded by single quotes.
2 Examples
public String partIDsToString(List<Part> parts){
StringBuilder sb = new StringBuilder();
for(Part part : parts)
sb.append("'"+part.getPartNumber() + "',");
return sb.substring(0,sb.length()-1);
}
public String companyIDsToString(List<Company> parts){
StringBuilder sb = new StringBuilder();
for(Company c : parts)
sb.append("'"+c.getId() + "',");
return sb.substring(0,sb.length()-1);
}
I'll need to create more methods like this in the future, and was wondering if there was a way to generalize this functionality, im looking for something like this.
public String objectPropertyToString(List<Object> list, Method getProperty){
StringBuilder sb = new StringBuilder();
for(Object obj: list)
sb.append("'"+obj.getProperty() + "',");
return sb.substring(0,sb.length()-1);
}
List<Company> companies = getCompaniesList();//not important
String result = objectPropertyToString(companies , Company::getId);
List<Part> parts= getPartsList();//not important
String result = objectPropertyToString(parts, Part::getPartNumber);
Can this be done using method references/lambdas, or any other way?

Stream.map() and Collectors.joining() are your friends here.
companies.stream()
.map(Company::getId)
.map(s -> "'" + s + "'")
.collect(joining(","));
You can create a helper method, but in my judgement the above is succinct enough that it isn't worthwhile:
static <T> String mapAndJoin(Collection<T> c, Function<T,String> f){
return c.stream()
.map(f)
.map(s -> "'" + s + "'")
.collect(joining(","));
}
mapAndJoin(companies, Company::getId);

As shown in this answer, Collectors.joining can be used to produce a comma separator list. But you can also use it to enclose the elements in single quotes in one go, which is more efficient than doing this in a separate String operation per element before joining them.
The basic idea is as follows:
public static <T> String objectPropertyToString(
Collection<? extends T> list, Function<T,String> f) {
return list.stream().map(f).collect(Collectors.joining("', '", "'", "'"));
}
Instead of just separating the elements with just a comma, we separate with a closing single quote, followed by comma and an opening single quote. Further, we use an opening single quote as starter before the first element and a closing single quote after the last element.
This works smoothly, with only one exception: if the list is empty, we get a sole '' as result because the initial opening quote and trailing closing quote is always produced. To solve this, we have to retrace what Collectors.joining does internally, to get hands on the StringJoiner used for the process, so we can configure it in a way not offered by the built-in collector:
public static <T> String objectPropertyToString(
Collection<? extends T> list, Function<T,? extends CharSequence> f) {
return list.stream().map(f).collect(
()->new StringJoiner("', '", "'", "'").setEmptyValue(""),
StringJoiner::add, StringJoiner::merge).toString();
}
This basically does the same as the previous attempt with the notable exception that we now can use setEmptyValue to specify the different result for the case that there were no elements. As a bonus we can now relax the generic type signature, allowing arbitrary CharSequence instances to be joined instead of just Strings.
The usage is as before:
List<Company> companies = getCompaniesList();//not important
String result = objectPropertyToString(companies , Company::getId);
List<Part> parts= getPartsList();//not important
String result = objectPropertyToString(parts, Part::getPartNumber);

Related

replace specific word after specific char in java

I have this String
String tst = " {"id":$.id, "parent_id":200}";
I am trying to extract $.id from this string and replace it by an other word.
For now I tried:
tst = tst.replaceAll("(\\$.).", "other_word");
But this code is replacing all the rest (like "parent_id"...) by this other word
here is the output:
{"id":other_wordd_mag, "parent_id":200}
it's replacing only the "i" from "id_mag" any solution ?
This code seems to be replacing as expected:
String tst = " {\"id\":$.id, \"parent_id\":200}";
System.out.println(tst.replaceAll("\\$\\.id", "other_word"));
Output:
{"id":other_word, "parent_id":200}
Update
If you need to substitute some variables inside JSON, you can use the following regexp:
String tst = "{\"id\":$.id, \"name\":$.name, \"parent_id\":200}";
System.out.println(tst.replaceAll("(\\$\\.\\w+)", "\"other_word\"")); // using shorthand for word characters
output:
{"id":"other_word", "name":"other_word", "parent_id":200}
Or, if you have a map of variables in the form of key-value pairs, you can use this method:
static String replaceVars(String src, Map<String, String> vars) {
for (Map.Entry<String, String> e : vars.entrySet()) {
src = src.replaceAll("(\\$\\." + e.getKey()+ ")", "\"" + e.getValue()+"\"");
}
return src;
}
// -----------
String tstDiff = "{\"id\":$.id, \"name\":$.name, \"parent_id\":200}";
System.out.println(replaceVars(tstDiff, Map.of("id", "my_id", "name", "my_name")));
output:
{"id":"my_id", "name":"my_name", "parent_id":200}
Working with these types of strings can be a little bit easier if you know what JSON is.
And Java has also a really good library for handling Json strings called GSON.
You can use this library and for this specific case use the fromJson method.
But if you want to work with regex and get familiar with Strings:
tst.replaceAll("\\$\\.id", "other_word")
This should work properly.

Insert string keyword values based map keys in main string

In Java, I have to insert strings value based on the key in main string.
For example -
Main String -
sellers(seller: $sellerId, shipment: $shipmentId)
Map of key and value -
{
sellerId: abc
shipmentId: 123
}
So after inserting it will become
sellers(seller: abc, shipment: 123)
I know i can do string replace. But that doesn't seem to be good approach here. So just wondering is there a standard approach or better way of doing things here?
Two approaches you can consider:
1 - loop over map entries, and do a simple string replace (note that this assumes a single occurrence of each var in the strings; if that is not the case, you need to use replaceAll):
String text = "sellers(seller: $sellerId, shipment: $shipmentId)";
Map<String, Object> binding = ...;
String result = text;
for (Entry<String, Object> entry : binding.entrySet()) {
result = result.replace("$" + entry.getKey(), String.valueOf(entry.getValue()));
}
2 - for advanced use cases, you want to use a proper template engine. And here's an example using groovy's simple template engine (use in java by adding the groovy jar):
groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine();
Writable template = engine.createTemplate(text).make(binding);
String result = template.toString();
Just note that groovy replaces variable names prefixed with $, and that's why this works without changes (making this a good choice for your current syntax).
Both produce your expected result, but you have to choose based on what this can turn into.
Depending on values map can hold you may face some problems. For instance if value may contain other key identifier like
{
foo: $bar
bar: 123
}
then using series of replace(mapEntryKey, mapEntryValue) could change string like
abc $foo efg $bar
first into $foo->$bar
abc $bar efg $bar
and then $bar->123
abc 123 efg 123
which is NOT what we wanted.
To avoid such problem we should iterate over template only once, search for each $key and replace it with value stored for it in map. If map doesn't contain such key we can leave it as it (replace it with itself).
We can do it with Matcher#replaceAll​(Function<MatchResult,​String> replacer). BTW if map value can contain $ and \ which are also metacharacters in replacement, we need to escape them. To do it we can use Mather#quoteReplacement method.
Demo:
Map<String, String> map = Map.of("sellerId", "abc",
"shipmentId", "123");
String yourTemplate = "sellers(seller: $sellerId, shipment: $shipmentId)";
Pattern p = Pattern.compile("\\$(\\w+)");
Matcher m = p.matcher(yourTemplate);
String replaced = m.replaceAll(match -> {
if(map.containsKey(match.group(1))){
return Matcher.quoteReplacement(map.get(match.group(1)));
}else{
return Matcher.quoteReplacement(match.group());
}
});
System.out.println(replaced);
Output: sellers(seller: abc, shipment: 123).
String format is an option here
Map<String, Integer> yourMap = new HashMap<>();
yourMap.put("abc", 123);
for (Map.Entry<String, Integer> entry : yourMap.entrySet()) {
String output = String.format("sellers(seller: %s, shipment: %d)", entry.getKey(), entry.getValue());
System.out.println("output = " + output);
}

Output ArrayList to String without [,] (brackets) appearing

OK, so I have an ArrayList that I need to return as a String. Right now I am using this approach:
List<Customer> customers = new ArrayList<>();
List<Account> accounts = new ArrayList<>();
public String customerList()
{
String list = Arrays.toString(customers.toArray());
return list;
}
public String accountList()
{
String account = Arrays.toString(accounts.toArray());
return account;
}
This works fine, but the problem is that I get brackets around the text. I am getting stuff like:
Customer list:
--------------
[Name: Last, First
, Name: Last, First
, Name: Last, First
]
When I want something like this:
Customer list:
--------------
Name: Last, First
Name: Last, First
Name: Last, First
However, unlike similar questions, I don't simply want to output line by line. I need to store the ArrayList in a String without the brackets so I can work with it.
EDIT: It should be noted that the one comma in the version I want it to look like was placed there by a method in a different class:
public final String getName()
{
return getLast() + ", " + getFirst();
}
EDIT 2: I was able to find a complete solution by adapting the two answers I was given. It was difficult deciding which was the "answer" because I found both useful, but went with the one that I could use more selectively.
public String customerList()
{
String list = Arrays.toString(customers.toArray()).replace(", N", "N").replace(", N", "N");
return list.substring(1,list.length()-1);
}
To remove the brackets I used the modified return. Removing the comma required me to focus on the fact that each line will start with an "N", but the flaw in this approach is that the code would break if I forget to change it here if I change it there. Still, it solves my specific problem, and I can always notate to myself in both places as needed.
You could try to replace the '[' and ']' with empty space
String list = Arrays.toString(customers.toArray()).replace("[", "").replace("]", "");
I think the best solution to print list without brackets and without any separator( for java 8 and higher )
String.join("", YOUR_LIST);
You can also add your own delimiter to separate printing elements.
String.join(", \n", YOUR_LIST);
example above separate each list element with comma and new line.
Java 8 version
List<Integer> intList = new ArrayList<Integer>();
intList.add(1);
intList.add(2);
intList.add(4);
System.out.println(intList.stream().map(i -> i.toString()).collect(Collectors.joining(",")));
Output:
1,2,4
Can directly convert list/set to string and perform action on it
customers.toString().replace("[", "").replace("]", "")
A possible solutions is:
String account = Arrays.toString(accounts.toArray());
return account.substring(1,account.length()-1);
Or do you override the toString method to return the string as per you wanted.
You can use the method substring() to remove starting and ending brackets without tampering any entries in the ArrayList.
I used something like this to convert a ArrayList<String> to String
String str = list.toString().substring(1, list.toString().length() - 1);
If you want your output to look like:
item1, item2, item3
Arrays.toString(customers.toArray()).replace('[', ' ').replace(']', ' ').trim()
If you want your output to look like:
item1 item2 item3
Arrays.toString(customers.toArray()).replace('[', ' ').replace(']', ' ').replace(',', ' ').trim()
You can override the toString() method and represent the output in whatever format you
You can try this.
String listAsStr = myList.toString(); // get list as string
listAsStr = listAsStr.substring(1,listAsStr.length()-1); // removing first and last bracket
This will return the string without first and last brackets.
Anyone still stuck on this, try using a for-each loop.
ArrayList<String> list = new ArrayList<>(); // default size of 10
/*add elements to
the ArrayList*/
for(String element: list)
System.out.println(element);

Returning a list of wildcard matches from a HashMap in java

I have a Hashmap which may contain wildcards (*) in the String.
For instance,
HashMap<String, Student> students_;
can have John* as one key. I want to know if JohnSmith matches any elements in students_. There could be several matches for my string (John*, Jo*Smith, etc). Is there any way I can get a list of these matches from my HashMap?
Is there another object I could be using that does not require me to iterate through every element in my collection, or do I have to suck it up and use a List object?
FYI, my collection will have less than 200 elements in it, and ultimately I will want to find the pair that matches with the least amount of wildcards.
It's not possible to achieve with a hasmap, because of the hashing function. It would have to assign the hash of "John*" and the hash of "John Smith" et al. the same value.
You could make it with a TreeMap, if you write your own custom class WildcardString wrapping String, and implement compareTo in such a way that "John*".compareTo("John Smith") returns 0. You could do this with regular expressions like other answers have already pointed out.
Seeing that you want the list of widlcard matchings you could always remove entries as you find them, and iterate TreeMap.get()'s. Remember to put the keys back once finished with a name.
This is just a possible way to achieve it. With less than 200 elements you'll be fine iterating.
UPDATE: To impose order correctly on the TreeSet, you could differentiate the case of comparing two WildcardStrings (meaning it's a comparation between keys) and comparing a WildcardString to a String (comparing a key with a search value).
You can use regex to match, but you must first turn "John*" into the regex equivalent "John.*", although you can do that on-the-fly.
Here's some code that will work:
String name = "John Smith"; // For example
Map<String, Student> students_ = new HashMap<String, Sandbox.Student>();
for (Map.Entry<String, Student> entry : students_.entrySet()) {
// If the entry key is "John*", this code will match if name = "John Smith"
if (name.matches("^.*" + entry.getKey().replace("*", ".*") + ".*$")) {
// do something with the matching map entry
System.out.println("Student " + entry.getValue() + " matched " + entry.getKey());
}
}
You can just iterate your Map without converting it into a list, and use the String matches function, wih uses a regexp.
If you want to avoid the loop, you can use guava like this
#Test
public void hashsetContainsWithWildcards() throws Exception {
Set<String> students = new HashSet<String>();
students.add("John*");
students.add("Jo*Smith");
students.add("Bill");
Set<String> filteredStudents = Sets.filter(students, new Predicate<String>() {
public boolean apply(String string) {
return "JohnSmith".matches(string.replace("*", ".*"));
}
});
assertEquals(2, filteredStudents.size());
assertTrue(filteredStudents.contains("John*"));
assertTrue(filteredStudents.contains("Jo*Smith"));
}

Lang ToStringBuilder request parameters logging

I need to log all the request parameters in some situations for debug purposes...
I tried using ToStringBuilder.reflectionToString(request), but it still showed memory addresses
Is there any easy way to log request parameters in plain text so that I could do something
logger.info(ToStringBuilder.reflectionToString(request)); ?
I also tried logger.info(ToStringBuilder.reflectionToString(request.getParameterMap());
reflectionToString only uses reflection on the object given, to find the attributes to print. The attributes themselves are output using their toString() methods.
Neither the request nor the parameter map have the request parameters you are interested in as direct attributes, so reflectionToString fails for you.
I know of no OOTB way to deeply reflection-print an object, in JDK or commons-lang.
What does the simple call
logger.info(request.getParameterMap());
produce for you?
Ah, I see: The parameter values are String arrays, which only print their hashcode.
You might try a helper function like this (disclaimer: uncompiled and untested)
public static String getParameterToString(ServletRequest request){
StringBuilder sb = new StringBuilder("{");
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()){
sb.append(entry.getKey()).append(":");
sb.append(Arrays.toString(entry.getValue())).append(",");
}
if (sb.length() > 1)
sb.setLength(sb.length() - 1);
return sb.append("}").toString();
}
This function is tested
public static String dumpParams(ServletRequest req) {
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String[]>> entries = req.getParameterMap().entrySet();
for (Map.Entry<String, String[]> entry : entries) {
sb.append(entry.getKey())
.append(" = ")
.append(Arrays.toString(entry.getValue()))
.append(", ");
}
if (sb.length() > 2)
sb.setLength(sb.length() - 2); //Removes the last comma
return sb.toString();
}

Categories