From external system I receive String representation of some abbreviations and I have to make transformation(conversion) to another String for example:
"O" -> Open
"C" -> Closed
"E" -> Exit
For Object to Object conversion I was using Spring Custom COnverter
import org.springframework.core.convert.converter.Converter;
public class Converter<Source, Target> implements Converter<Source, Target>
public final Target convert(#Nonnull Source source) {
...
}
But I can't create String to String converter. I do not want to use external mapping library only Spring capabilities. But I can't do this. The simplest thing that I can do is switch
String input = "O";
String result = null;
switch(input){
case "O": result ="Open"
break;
case "C": result ="Close"
break;
....
In matter of fact I have to do over 100 mapings. Can Spring offer better solution?
When you don't have any logic to execute in switch-case, you can use a static HashMap<String,String>
static HashMap<String,String> map = new HashMap<>();
static
{
map.put("O","Open");
map.put("C","Close");
.....................
}
Instead of switch-case just use
map.get(input);
If you are suing Java 8, you can even use
map.getOrDefault(input,"");
Related
TestDTO testDTO = new TestDTO();
for (Object attribute : row.getAttributes()) {
switch (attribute) {
case "CATEGORY":
testDTO.setCategory((String) attribute);
break;
case "DESCRIPTION":
testDTO.setDescription((String) attribute);
break;
case "NOTE":
testDTO.setNote((String) attribute);
break;
case "FEATURES":
testDTO.setFeatures((String) attribute);
break;
case "INDICATOR":
testDTO.setIndicator((String) attribute);
break;
case "LABEL":
testDTO.setLabel((String) attribute);
break;
case "TYPE":
testDTO.setType((String) attribute);
break;
default:
}
}
As you can see in above code, we are using multiple case for setting data. Code is working fine.
Is there any way for reducing multiple cases for setting those data.
In the above code, the problem is maintainability. Because suppose if we have 30 fields, then we need to put 30 cases for that.
Is there any other way to achieve the same?
Without refactoring you cannot do anything really helping the situation. Also you will need to add specific code for every field anyway - this is obvious
In abstract situation what you could do would be to implement factory or strategy pattern and e.g. register proper handlers for every type of attribute - something like
Map<Object, BiConsumer<TestoDTO, Object>> handlers; // then you can add for example handlers.put("TYPE", (d, a) -> d.setType(a))
And just iterate over attributes
row.getAttributes().forEach(a -> handlers.get(attribute).accept(dto, a)); // ofc you need to handle all situation like NPE, no key etc
In scope of mapping objects you could use some existing tool like ObjectMapper or ModelMapper because it's quite possible that these tools will resolve your issue out of the box
Last and least (:)) solution is to use some reflection, map attribute to field name, extract setter... Don't do this :) it's filthy, insecure, hard to write and understand - will cause many issues you will regret but because it's an option I'm mentioning this
For a robust solution you can also build your association using enumerated types and method references, and conveniently encapsulate the map into a single type. Plus, it's pretty obvious how to add new fields:
enum DTOMap
{
CATEGORY(TestDTO::setCategory),
DESCRIPTION(TestDTO::setDescription);
private final BiConsumer<TestDTO, String> attributeConsumer;
private DTOMap(BiConsumer<TestDTO, String> attributeConsumer) {
this.attributeConsumer = attributeConsumer;
}
public static void execute(TestDTO testDTO, Object attribute) {
String attributeAsString = (String) attribute;
DTOMap.valueOf(attributeAsString.toUpperCase()).attributeConsumer.accept(testDTO, attributeAsString);
}
}
With this your switch statement can be reduced to a single line:
for (Object attribute : row.getAttributes()) {
DTOMap.execute(testDTO, attribute);
}
You can use reflection to refactor it like below:
TestDTO testDTO = new TestDTO();
for (Object attribute : row.getAttributes()) {
Method method = testDTO.getClass().getMethod("set" + capitalize((String) attribute), String.class);
method.invoke(testDTO, (String) attribute);
}
The capitalize func:
public String capitalize(String string) {
return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
}
I am trying to refactor some legacy code. The task here is to construct lengthy messages/strings based on some pre-defined template that looks like this:
field1,8,String
filed2,5,Integer
field3,12,String
......
Then I am handed a java object that has all those fields. What needs to be done here is simply get data from the object fields and use them to construct a long message/string based on the template. Some of these fields also may be converted based on some simple rules. For example:
abc => a
def => d
ghi => g
As a result we need to check the values of these fields from time to time. Also there are rules about padding (mostly adding empty space to the right). So a conostructed message/string may look like this:
uater 4751 enterprise ......
Currently we are just using brutal force to do this job. First we feed the template into an ArrayList, each element is a line, eg, "field1,8,String". During the actual message construction, we loop through this ArrayList, and then fill the data into a StringBuffer. Here is some sample snippet
StringBuffer message = new StringBuffer(1000);
for (String field : templateFields) {
String[] fieldArray = field.split(Constants.SEPARATOR);
if (fieldArray[0].equalsIgnoreCase(Constants.WORKFLOW)) {
message.append(rightPad(object.getFieldOne(), Integer.parseInt(fieldArray[1])));
} else if (fieldArray[0].equalsIgnoreCase(Constants.WORKVOLUME)) {
message.append(rightPad(object.getFieldTwo(), Integer.parseInt(fieldArray[1]));
} else if (fieldArray[0].equalsIgnoreCase(Constants.WORKTYPE)) {
if (object.getFieldThree().equalsIgnoreCase("abc")) {
message.append(rightPad("a", Integer.parseInt(fieldArray[1]));
} else if (object.getFieldThree().equalsIgnoreCase("def")) {
message.append(rightPad("d", Integer.parseInt(fieldArray[1]));
} else {
message.append(rightPad("g", Integer.parseInt(fieldArray[1]));
}
} else if ......
}
As you can see, as hidious as it is, it gets the job done. But such code is error-prone, and is hard to maintain. I wonder if you guys have any tools or libraries or some elegant solutions to recommend. Thanks so much! Hua
If I understand your question right, you have an approach where you are looping over possible templateFields. That's not necessary.
Since every fieldArray[0] is compared to some Constants values and in case of a match is processed further, we can replace the for-loop by a Map. Its keys are the possible Constants values and its values are mappers. A mapper is a BiFunction which takes the object and the value of fieldArray[1] and returns for these a message of type String.
Let's start with the mappers:
public class FieldToMessageMapper {
private static final Map<String, Function<String, String>> WORKINGTYPE_MESSAGE_MAPPER = new HashMap<>();
static {
WORKINGTYPE_MESSAGE_MAPPER.put("abc", fieldArray1 -> rightPad("a", Integer.parseInt(fieldArray1)));
WORKINGTYPE_MESSAGE_MAPPER.put("def", fieldArray1 -> rightPad("d", Integer.parseInt(fieldArray1)));
WORKINGTYPE_MESSAGE_MAPPER.put("DEFAULT", fieldArray1 -> rightPad("g", Integer.parseInt(fieldArray1)));
}
private static Map<String, BiFunction<MyObject, String, String>> MESSAGE_MAPPER = new HashMap<>();
static {
MESSAGE_MAPPER.put(Constants.WORKFLOW, (o, fieldArray1) -> rightPad(o.getFieldOne(), Integer.parseInt(fieldArray1)));
MESSAGE_MAPPER.put(Constants.WORKVOLUME, (o, fieldArray1) -> rightPad(o.getFieldTwo(), Integer.parseInt(fieldArray1)));
MESSAGE_MAPPER.put(Constants.WORKTYPE,
(o, fieldArray1) -> WORKINGTYPE_MESSAGE_MAPPER.getOrDefault(o.getFieldThree().toLowerCase(), WORKINGTYPE_MESSAGE_MAPPER.get("DEFAULT")).apply(fieldArray1));
}
public static Optional<String> map(MyObject o, String fieldArray0, String fieldArray1) {
return Optional.ofNullable(MESSAGE_MAPPER.get(fieldArray0.toLowerCase()))
.map(mapper -> mapper.apply(o, fieldArray1));
}
private static String rightPad(String string, int pad) {
// TODO right pad
return string;
}
}
We do not return a mapper itself. FieldToMessageMapper offers the method map which does the mapping. It returns an Optional<String> which shows the result might be empty if there is no mapping for the input.
To ensure to get a mapper independent of the characters case, all keys are String..toLowerCase().
Let's go on with the overall processing:
protected StringBuffer process(Collection<String> templateFields, MyObject object) {
StringBuffer message = new StringBuffer(1000);
for (String field : templateFields) {
String[] fieldArray = field.split(Constants.SEPARATOR);
String msg = FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
.orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", field)));
message.append(msg);
}
return message;
}
I don't know how you need to handle missing mappings. I choose fail fast by throwing an exception.
Please note: StringBuffer is
A thread-safe, mutable sequence of characters. A string buffer is like
a String, but can be modified.
If your processing isn't multithreaded you could use StringBuilder. If the result isn't modified further, you could use String.
Let me show a further alternative using Stream which returns a String:
protected String process(Collection<String> templateFields, MyObject object) {
return templateFields.stream()
.map(field -> field.split(Constants.SEPARATOR))
.map(fieldArray -> FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
.orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", Arrays.toString(fieldArray)))))
.collect(Collectors.joining());
}
If I got the code from the question right, there should be the following implementation of Constants:
public class Constants {
public static final String SEPARATOR = ",";
public static final String WORKFLOW = "field1";
public static final String WORKVOLUME = "filed2";
public static final String WORKTYPE = "field3";
}
EDIT:
If you want to have a configuration approach you can elaborate this code further to use Spring configuration:
Define an interface MessageMapper which has two methods: String getKey() and String map(MyObject o, String fieldArray1). getKey() returns the Constants value for which the mapper provides the mapping.
Implement each of above MESSAGE_MAPPER using this interface.
Add a CommonMessageMapper which has a constructor CommonMessageMapper(MessageMapper... messageMappers). The messageMappers has to be put in a Map<String, BiFunction<MyObject, String, String>> mappers like: mappers.put(messageMapper.getKey(), messageMapper). Define a method String map(MyObject o, String fieldArray0, String fieldArray1) which will lookup the appropriate MessageMapper mm using fieldArray0: MessageMapper mm = mappers.get(fieldArray0). Invoke then mm.map(o, feldArray1). (You may use here also an Optional to handle the case when no appropriate mapper is present.)
To use Spring configuration all MessageMapper and the CommonMessageMapper have to be annotated as Bean or Component. The constructor of CommonMessageMapper has to be annotated with #Autowired.
Define a Spring configuration (either using XML or as #Configuration) which will inject the desired MessageMapper into a CommonMessageMapper and has a factory method for such a CommonMessageMapper.
Use CommonMessageMapper instead of FieldToMessageMapper above.
I couldn't find a better title (feel free to edit it if you find a better one), but the use case is the following. I have two lists of constants. One of those contains the constants I use in my application, the other contains the different constants that are sent to me via a CSV file (along with data).
To give a rough exemple : in the CSV file, there is a field called "id of the client". In my application, I want to use a field called "clientId". So I basically need to create a static link between the two constants, so that I can easily switch from one to the other depending on what I need to achieve.
I've thought about creating a static Map(String, String) of values, but I figured there might be better solutions.
Thanks !
EDIT : changed title to "N" constants instead of 2, because Hashmap doesn't seem to be an option any longer in that case.
you can use the double bracket innitializer idiom to keep map initialization close to the map declaration, so it would be not so "ugly" eg:
static Map<String, String> someMap = new HashMap<String, String>() {{
put("one", "two");
put("three", "four");
}};
Beware that without the static modifier each anonymous class (there is one created in this example) holds a refernce to the enclosing object and if you'll give a reference to this map to some other class it will prevent the enclosing class from being garbage collect.
Fortunatelly, there is a hope for us with java update, in java 9 there will be very handy Map.of() to help us do it more safely.
The best way to separate the mapping from your application code is to use a properties file where in which you define your mapping.
For example, you could have a csv-mapping.properties in the root of your resources and load them with the following code:
final Properties properties = new Properties();
properties.load( this.getClass().getResourceAsStream( "/csv-mapping.properties" ) );
This will work just like a Map, with the added separation of code from configuration.
There are many methods that you can use to easily solve these types of problem.
One way is to use a Properties file, or file containing the key value pair.
Here is the code for Properties.
import java.util.ResourceBundle;
public class ReadingPropertiesFile {
public static void main(String[] args) {
ResourceBundle messages;
messages = ResourceBundle.getBundle("msg");
System.out.println(messages.getString("ID"));
}
}
msg.properties file contains values::
ID = ClientID.
PRODUCT_ID = prod_ID
The output of the program is ClientID.
You can also read from a simple text file. Or you could use the map as you are using. But I would suggest you to use the properties file.
One good option would be to use an enum to create such mappings beetween multiple constants to a single common sense value, eg:
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public enum MappingEnum {
CLIENT_ID("clientId", "id of the client", "clientId", "IdOfTheClient"),
CLIENT_NAME("clientName", "name of the client", "clientName");
private Set<String> aliases;
private String commonSenseName;
private MappingEnum(String commonSenseName, String... aliases) {
this.commonSenseName = commonSenseName;
this.aliases = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(aliases)));
}
public static MappingEnum fromAlias(String alias) {
for (MappingEnum mappingEnum : values()) {
if (mappingEnum.getAliases().contains(alias)) {
return mappingEnum;
}
}
throw new RuntimeException("No MappingEnum for mapping: " + alias);
}
public String getCommonSenseName() {
return commonSenseName;
}
}
and then you can use it like:
String columnName = "id of the client";
String targetFieldName = MappingEnum.fromAlias(columnName).getCommonSenseName();
Is there any String replacement mechanism in Java, where I can pass objects with a text, and it replaces the string as it occurs?
For example, the text is:
Hello ${user.name},
Welcome to ${site.name}.
The objects I have are user and site. I want to replace the strings given inside ${} with its equivalent values from the objects. This is same as we replace objects in a velocity template.
Use StringSubstitutor from Apache Commons Text.
Dependency import
Import the Apache commons text dependency using maven as bellow:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
Example
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}.";
StringSubstitutor sub = new StringSubstitutor(valuesMap);
String resolvedString = sub.replace(templateString);
Take a look at the java.text.MessageFormat class, MessageFormat takes a set of objects, formats them, then inserts the formatted strings into the pattern at the appropriate places.
Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
My preferred way is String.format() because its a oneliner and doesn't require third party libraries:
String message = String.format("Hello! My name is %s, I'm %s.", name, age);
I use this regularly, e.g. in exception messages like:
throw new Exception(String.format("Unable to login with email: %s", email));
Hint: You can put in as many variables as you like because format() uses Varargs
I threw together a small test implementation of this. The basic idea is to call format and pass in the format string, and a map of objects, and the names that they have locally.
The output of the following is:
My dog is named fido, and Jane Doe owns him.
public class StringFormatter {
private static final String fieldStart = "\\$\\{";
private static final String fieldEnd = "\\}";
private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
private static final Pattern pattern = Pattern.compile(regex);
public static String format(String format, Map<String, Object> objects) {
Matcher m = pattern.matcher(format);
String result = format;
while (m.find()) {
String[] found = m.group(1).split("\\.");
Object o = objects.get(found[0]);
Field f = o.getClass().getField(found[1]);
String newVal = f.get(o).toString();
result = result.replaceFirst(regex, newVal);
}
return result;
}
static class Dog {
public String name;
public String owner;
public String gender;
}
public static void main(String[] args) {
Dog d = new Dog();
d.name = "fido";
d.owner = "Jane Doe";
d.gender = "him";
Map<String, Object> map = new HashMap<String, Object>();
map.put("d", d);
System.out.println(
StringFormatter.format(
"My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.",
map));
}
}
Note: This doesn't compile due to unhandled exceptions. But it makes the code much easier to read.
Also, I don't like that you have to construct the map yourself in the code, but I don't know how to get the names of the local variables programatically. The best way to do it, is to remember to put the object in the map as soon as you create it.
The following example produces the results that you want from your example:
public static void main(String[] args) {
Map<String, Object> map = new HashMap<String, Object>();
Site site = new Site();
map.put("site", site);
site.name = "StackOverflow.com";
User user = new User();
map.put("user", user);
user.name = "jjnguy";
System.out.println(
format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}
I should also mention that I have no idea what Velocity is, so I hope this answer is relevant.
Here's an outline of how you could go about doing this. It should be relatively straightforward to implement it as actual code.
Create a map of all the objects that will be referenced in the template.
Use a regular expression to find variable references in the template and replace them with their values (see step 3). The Matcher class will come in handy for find-and-replace.
Split the variable name at the dot. user.name would become user and name. Look up user in your map to get the object and use reflection to obtain the value of name from the object. Assuming your objects have standard getters, you will look for a method getName and invoke it.
There are a couple of Expression Language implementations out there that does this for you, could be preferable to using your own implementation as or if your requirments grow, see for example JUEL and MVEL
I like and have successfully used MVEL in at least one project.
Also see the Stackflow post JSTL/JSP EL (Expression Language) in a non JSP (standalone) context
Handlebars.java might be a better option in terms of a Velocity-like syntax with other server-side templating features.
http://jknack.github.io/handlebars.java/
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hello {{this}}!");
System.out.println(template.apply("Handlebars.java"));
I use GroovyShell in java to parse template with Groovy GString:
Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
I created this utility that uses vanilla Java. It combines two formats... {} and %s style from String.format.... into one method call. Please note it only replaces empty {} brackets, not {someWord}.
public class LogUtils {
public static String populate(String log, Object... objects) {
log = log.replaceAll("\\{\\}", "%s");
return String.format(log, objects);
}
public static void main(String[] args) {
System.out.println(populate("x = %s, y ={}", 5, 4));;
}
}
Since Java 15 you have the method String.formatted() (see documentation).
str.formatted(args) is the equivalent of String.format(str, args) with less ceremony.
For the example mentioned in the question, the method could be used as follows:
"Hello %s, Welcome to %s.".formatted(user.getName(), site.getName())
Good news. Java is most likely going to have string templates (probably from version 21).
See the string templates proposal (JEP 430) here.
It will be something along the lines of this:
String name = "John";
String info = STR."I am \{name}";
System.out.println(info); // I am John
P.S. Kotlin is 100% interoperable with Java. It supports cleaner string templates out of the box:
val name = "John"
val info = "I am $name"
println(info) // I am John
Combined with extension functions, you can achieve the same thing the Java template processors (e.g. STR) will do.
There is nothing out of the box that is comparable to velocity since velocity was written to solve exactly that problem. The closest thing you can try is looking into the Formatter
http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html
However the formatter as far as I know was created to provide C like formatting options in Java so it may not scratch exactly your itch but you are welcome to try :).
So I have a json input, something like this:
{
"a":"something",
"b":"something2"
}
This input can come in 2 other forms:
{
"a":"something",
"b":""
}
And
{
"a":"something"
}
I have a input class like this that I store it in:
public class Foo {
private String a;
private Optional<String> b;
}
In the latter 2 cases I want to store b as Optional.empty(). I have figured out how to do that in the 3rd case with this line of code:
Optional.ofNullable(inputNode.get("b")).map(node -> node.asText())
This works well for the 3rd case. It returns Optional.empty. But not so much for the 2nd case because b ends up just being an empty string. I tried to use .reduce before and after the .map but for some reason it won't let me use that anywhere.
And idea how to achieve what I want?
You can filter the Optional to check that the value is not the empty String:
Optional<String> b = Optional.ofNullable(inputNode.get("b"))
.map(JsonNode::asText)
.filter(s -> !s.isEmpty());
This code also helps:
Optional.ofNullable(jsonNode.findValue("XYZ")).map(JsonNode::asText).orElse(null);