Huge if statement, refactor using Enum or Map? - java

I stumbled upon a 400 lines if/else, with about 100 clauses in it. So I feel the urge to refactor that monster.
The method is of this form:
private void updatePaymentField(Payment payment, String fieldName, String value) {
if (Field.FIELD_1.equalsIgnoreCase(fieldName)) {
payment.getField1().setId(value)
} else if (Field.FIELDS_2.equalsIgnoreCase(fieldName)) {
payment.setField2(valueToSet)
} else if (Field.FIELDS_3.equalsIgnoreCase(fieldName)) {
UtilClass::setField3(payment, value)
} else if ... // 100 more of these
}
I changed that into an enum that takes a String (the field name) and a BiConsumer<Payment, String> (the method/lambda that will update the field on the payment)
enum Field {
UNDEFINED("", null),
FIELD1(Fields.FIELD1, (Payment, value) -> payment.getField1().setId(value)),
FIELD2(Fields.FIELD2, Payment::setField2),
FIELD3(Fields.FIELD3, UtilClass::setField3),
// 100 more of these,
private String fieldName;
private BiConsumer<Payment, String> populateFunction;
Field(String fieldName, BiConsumer<Payment, String> populateFunction) {
this.fieldName = fieldName;
this.populateFunction = populateFunction;
}
public static Field getByName(String name) {
return Arrays.stream(values())
.filter(p -> p.getFieldName().equals(name))
.findFirst()
.orElse(UNDEFINED);
}
private String getFieldName() {
return fieldName;
}
public BiConsumer<Payment, String> getPopulateFunction() {
return populateFunction;
}
}
then basically the big if can be replaced by
private void updatePaymentField(Payment payment, String fieldName, String value) {
Field field = Field.getByName(fieldName);
if(field != Field.UNDEFINED) {
field.getPopulateFunction().accept(payment, value);
}
}
That works and is all good, but I am thinking that I might have overdone it can achieve the same thing with a simple Map
Map<String, BiConsumer<Payment, String>> fieldMap = new HashMap<>();
fieldMap.put(Fields.FIELD1, (Payment, value) -> payment.getField1().setId(value))
fieldMap.put(Fields.FIELD2, Payment::setField2)
fieldMap.put(Fields.FIELD3, PaymentUtilClass::setField3)
// 100 more of these,
And use it the same way
private void updatePaymentField(Payment payment, String fieldName, String value) {
BiConsumer<Payment, String> populateFunction = fieldMap.get(fieldName);
if(populateFunction!=null) {
populateFunction.accept(payment, value);
}
}
I feel that accessing the BiConsumer<Payment, String> would be quicker using the Map than using the getByName method on the enum, and the Map notation is also lighter.
So I am thinking the Map might be a better choice.
So the question is: Before I change this to a Map, is there any objective value in using en enum over a Map in that specific use case?

Related

Enforce compile type safety with TypeSafeMap

Problem statement: We are building a library which has a TypeSafeMap as response. TypeSafeMap is a map which can hold any type of objects.
Now, the client is going to access the typesafemap. We are trying to enforce some level of compile type safety. Below is the code and more explanation.
Response structure:
public Class Response {
private TypeSafeMap t;
public TypeSafeMap getMap() { return t; }
}
//Type safety Map
public class TypeSafeMap
{
private final static Map<String, Object> map = new HashMap<>();
public static <T> T put(String key, T value) {
if (null != key) {
return (T) map.put(key, value);
}
return (T) map;
}
#SuppressWarnings("unchecked")
public static <T> T get(PartyEnums partyEnum)
{
return (T) map.get(partyEnum.PARTY.name());
}
}
//Enum that we expose to client to get the property and the corresponding field type
public enum PartyEnums
{
PARTY("party", new ArrayList<Party>().getClass());
private final String name;
private final Class<?> clzz; //this is the type client should access as field type
PartyEnums(String name,Class<?> clzz)
{
this.name = name;
this.clzz=clzz;
}
public Class<?> getClzz()
{
return clzz;
}
#SuppressWarnings("unchecked")
public <T> T getInstance()
{
T ins = null;
try {
ins = (T) getClzz().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return ins;
}
}
//Client call
public class ClientCall {
Object obj = TypeSafeMap.get(PartyEnums.PARTY); //No error.
String str = TypeSafeMap.get(PartyEnums.PARTY); //No error.
But we want enforce some level of compile type safety as the field type "str" and TypeSafeMap.get() type do not match.
How can we enforce compile type safety?
List<Party> party = TypeSafeMap.get(PartyEnums.PARTY);// OK.
}
TL;DR
This cannot be done using an enum in its current form.
There was a JEP 301: Enhanced Enums that would address the very same problem, but it's been withdrawn.
Your only choice to enforce type-safety (currently) is to simulate an enum by using a final class with predefined typed constants.
I would imagine it to be something like this:
public final class Key<V> {
public static final Key<List<Party>> PARTY_LIST = new Key<>("party list");
public static final Key<Map<Integer, String>> PARTY_MAP = new Key<>("party map");
public static final Key<String> SOME_STRING = new Key<>("party string");
private final String name;
private Key(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
And now we are ready to make your TypeSafeMap really type-safe by modifying both <T> T put(String key, T value) (which would not obviously be safe even if the "enum approach" was theoretically possible) and <T> T get(PartyEnums partyEnum) this way:
public static class TypeSafeMap {
private final static Map<String, Object> MAP = new HashMap<>();
#SuppressWarnings("unchecked")
public static <T> T put(Key<T> key, T value) {
if (null != key) {
return (T) MAP.put(key.getName(), value);
}
return value;
}
#SuppressWarnings("unchecked")
public static <T> T get(Key<T> key) {
return (T) MAP.get(key.getName());
}
}
I still see at least 2 possible issues that:
Multiple constants may share the same name hence effectively overwriting each other once used (can be tackled for instance using an enum instead of String)
put(Key<T> key, T value) passes through the input value back even for the null keys which is (in my opinion) fail-safe but misleading for the caller (who might think the "store" operation was somehow succeeded.
However, given the weak points above this implementation still fits your original intention, so that:
These cases would pass:
map.put(Key.JUST_STRING, "just string");
map.put(Key.PARTY_LIST, Arrays.asList(new Party()));
...
Object resultAsObject = map.get(Key.PARTY_LIST);
List<Party> resultAsList = map.get(Key.PARTY_LIST);
String resultString = map.get(Key.JUST_STRING);
But these fail with compile-time error:
map.put(Key.PARTY_LIST, Arrays.asList(new String()));
...
String stringFromList = map.get(Key.PARTY_LIST);
Note that Object resultAsObject = map.get(<ANYTHING>) will always succeed as any returned value (including nulls) can we represented as an Object variable

Best way to avoid multiple parallel if else loop in java 8

What is the best way to avoid multiple parallel if-else loop. I tried with switch statement as well, but again that doesn't look readable. I have hundreds of such statements:
public static Map getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
if(map.containsKey(Constants.NAME_KQV)) {
map.put(Constants.NAME_KQV, secureNodeData.getNodename());
}
if(map.containsKey(Constants.SPOV)) {
map.put(Constants.SPOV, secureNodeData.getOverride());
}
if(map.containsKey(Constants.SPEP)) {
map.put(Constants.SPEP, secureNodeData.getEnabledProtocol());
}
if(map.containsKey(Constants.SPTO)) {
map.put(Constants.SPTO, secureNodeData.getAuthTimeout());
}
if(map.containsKey(Constants.TLCN)) {
map.put(Constants.TLCN, secureNodeData.getCommonName());
}
if(map.containsKey(Constants.SEDT)) {
map.put(Constants.SEDT, secureNodeData.getEncryptData());
}
if(map.containsKey(Constants.TLCF)) {
map.put(Constants.TLCF, secureNodeData.getKeyCertLabel());
}
if(map.containsKey(Constants.TLCL)) {
map.put(Constants.TLCL, secureNodeData.getCipherSuites());
}
return map;
}
Please note that I have to invoke different getter of secureNodeData for every check.
For each Constants value (e.g. Constants.NAME_KQV), you can provide a Function<Sample, Object> (e.g. sample -> sample.getNodename()).
If you organised it in a structure like Map or enum (here, I used a enum), you could end up with a simple loop:
public static Map<String, Object> getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
for (Constant constant : Constant.values()) {
final String name = constant.getName();
if (map.containsKey(name)) {
map.put(name, constant.getFunction().apply(secureNodeData));
}
}
return map;
}
The enum was defined as:
enum Constant {
NAME_KQV(Constants.NAME_KQV, Sample::getNodename);
// other definitions
final String name;
final Function<Sample, Object> function;
Constant(String name, Function<Sample, Object> function) {
this.name = name;
this.function = function;
}
public String getName() {
return name;
}
public Function<Sample, Object> getFunction() {
return function;
}
}
It seems like this method does a lot. (1) It's unclear why it overrides existing values. (2) The method name is obscure. (3) You are using a raw Map, replace it with Map<String, Object> at least, and figure out how to substitute the Object part. (4)
I feel rethinking the design would help much more than the above approach and these small corrections.
You can try to take advantage of method references:
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
applyParam(Constants.NAME_KQV, map, node::getNodename);
applyParam(Constants.SPOV, map, node::getOverride);
// ...
}
public static void applyParam(String key, Map<String, Object> data, Supplier<Object> getter) {
if (data.containsKey(key)) {
data.put(key, getter.get());
}
}
Alternatively you can use Function references that are instance independent:
private static final Map<String, Function<Sample, Object>> MAPPING;
static {
MAPPING = new LinkedHashMap<>();
MAPPING.put(Constants.NAME_KQV, Sample::getNodename);
MAPPING.put(Constants.SPOV, Sample::getOverride);
}
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
for (String key : MAPPING.keySet()) {
if (map.containsKey(key)) {
map.put(key, MAPPING.get(key).apply(node));
}
}
}
There are many ways how you can approach your specific use case, but method references in general makes developer's life much much easier.

Generic BiDiMap

I have a BiDiMap class. How can I make it generic, by accepting not only String but also Object type of objects as input parameters, with keeping all the original functions working. For example I'd like to be able to use function put() with Object, Object as input parameters instead of String, String. I'd like to change all the input parameters and returning values of String type to Object types.
package MyBiDiMap;
import java.util.HashMap;
import java.util.Map;
public class BiDiMap {
private Map<String, String> keyValue;
private Map<String, String> valueKey;
public BiDiMap() {
this.keyValue = new HashMap<>();
this.valueKey = new HashMap<>();
}
private BiDiMap(Map<String, String> keyValue,
Map<String, String> valueKey) {
this.keyValue = keyValue;
this.valueKey = valueKey;
}
public void put(String key, String value) {
if (this.keyValue.containsKey(key)
|| this.valueKey.containsKey(value)) {
this.remove(key);
this.removeInverse(value);
}
this.keyValue.put(key, value);
this.valueKey.put(value, key);
}
public String get(String key) {
return this.keyValue.get(key);
}
public String getInverse(String value) {
return this.valueKey.get(value);
}
public void remove(String key) {
String value = this.keyValue.remove(key);
this.valueKey.remove(value);
}
public void removeInverse(String value) {
String key = this.valueKey.remove(value);
this.keyValue.remove(key);
}
public int size() {
return this.keyValue.size();
}
public BiDiMap getInverse() {
return new BiDiMap(this.valueKey, this.keyValue);
}
}
The answer is pretty simple: by introducing two generic types, named K and V on your class and by then vigorously replacing all occurance of String with K (where your key type should be used), and similarly with V where values are required.
In other words: don't use specific types when declaring the two maps, but in all places, use the new generic types you added on class level.

Flattening Java Bean to a Map

I am stuck at converting Java Bean to Map. There are many resources on the internet, but unfortunately they all treat converting simple beans to Maps. My ones are a little bit more extensive.
There's simplified example:
public class MyBean {
private String firstName;
private String lastName;
private MyHomeAddress homeAddress;
private int age;
// getters & setters
}
My point is to produce Map<String, Object> which, in this case, is true for following conditions:
map.containsKey("firstName")
map.containsKey("lastName")
map.containsKey("homeAddress.street") // street is String
map.containsKey("homeAddress.number") // number is int
map.containsKey("homeAddress.city") // city is String
map.containsKey("homeAddress.zipcode") // zipcode is String
map.containsKey("age")
I have tried using Apache Commons BeanUtils. Both approaches BeanUtils#describe(Object) and BeanMap(Object) produce a Map which "deep level" is 1 (I mean that there's only "homeAddress" key, holding MyHomeAddress object as a value). My method should enter the objects deeper and deeper until it meets a primitive type (or String), then it should stop digging and insert key i.e. "order.customer.contactInfo.home".
So, my question is: how can it be easliy done (or is there already existing project which would allow me to do that)?
update
I have expanded Radiodef answer to include also Collections, Maps Arrays and Enums:
private static boolean isValue(Object value) {
final Class<?> clazz = value.getClass();
if (value == null ||
valueClasses.contains(clazz) ||
Collection.class.isAssignableFrom(clazz) ||
Map.class.isAssignableFrom(clazz) ||
value.getClass().isArray() ||
value.getClass().isEnum()) {
return true;
}
return false;
}
Here's a simple reflective/recursive example.
You should be aware that there are some issues with doing a conversion the way you've asked:
Map keys must be unique.
Java allows classes to name their private fields the same name as a private field owned by an inherited class.
This example doesn't address those because I'm not sure how you want to account for them (if you do). If your beans inherit from something other than Object, you will need to change your idea a little bit. This example only considers the fields of the subclass.
In other words, if you have
public class SubBean extends Bean {
this example will only return fields from SubBean.
Java lets us do this:
package com.acme.util;
public class Bean {
private int value;
}
package com.acme.misc;
public class Bean extends com.acme.util.Bean {
private int value;
}
Not that anybody should be doing that, but it's a problem if you want to use String as the keys, because there would be two keys named "value".
import java.lang.reflect.*;
import java.util.*;
public final class BeanFlattener {
private BeanFlattener() {}
public static Map<String, Object> deepToMap(Object bean) {
Map<String, Object> map = new LinkedHashMap<>();
try {
putValues(bean, map, null);
} catch (IllegalAccessException x) {
throw new IllegalArgumentException(x);
}
return map;
}
private static void putValues(Object bean,
Map<String, Object> map,
String prefix)
throws IllegalAccessException {
Class<?> cls = bean.getClass();
for (Field field : cls.getDeclaredFields()) {
if (field.isSynthetic() || Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
Object value = field.get(bean);
String key;
if (prefix == null) {
key = field.getName();
} else {
key = prefix + "." + field.getName();
}
if (isValue(value)) {
map.put(key, value);
} else {
putValues(value, map, key);
}
}
}
private static final Set<Class<?>> VALUE_CLASSES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Object.class, String.class, Boolean.class,
Character.class, Byte.class, Short.class,
Integer.class, Long.class, Float.class,
Double.class
// etc.
)));
private static boolean isValue(Object value) {
return value == null
|| value instanceof Enum<?>
|| VALUE_CLASSES.contains(value.getClass());
}
}
You could always use the Jackson Json Processor. Like this:
import com.fasterxml.jackson.databind.ObjectMapper;
//...
ObjectMapper objectMapper = new ObjectMapper();
//...
#SuppressWarnings("unchecked")
Map<String, Object> map = objectMapper.convertValue(pojo, Map.class);
where pojo is some Java bean. You can use some nice annotations on the bean to control the serialization.
You can re-use the ObjectMapper.

JavaBean set attributes. Multiple if-statements alternative

Again I would like to get your opinion about a design issue.
I have a JavaBean with 15 attributes. For feeding the attributes I have a for loop that iterates over a collection of key-value pairs (concretely SAML attributes, I am mapping the attributes response to principals attributes). I am invoking the appropriate setter method basis on the key value, this is:
.../...
for (SAML2AttributeInfo attr : attrs) {
if (attr.getAttributeName().equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")) {
customPrincipal.setUpn(attr.getAttributeValues().iterator().next());
}
.../... so on and so forth
}
It works, ok, but I have an ugly piece of code, 15 if-statements like above do not look very elegant.
I am thinking on using Reflection, this is, develop a unique set method and pass it the name of the attribute and his value.
Another option could be store the attributes in a Map, but I am not sure...
Any ideas?
Thanks in advance,
Luis
I would use a Map and declare static variables with the attribute keys:
public class Bean {
public static final String ATTRIBUTE_1 = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn";
public static final String ATTRIBUTE_2 = "...";
...
public static final String ATTRIBUTE_N = "...";
private Map<String, Object> map = new HashMap<String, Object>();
public void put(String key, Object value) {
map.put(key, value);
}
public Object get(String key) {
map.get(key);
}
}
Then, you coud store / retrieve values using the static variables:
Bean bean = new Bean();
bean.set(Bean.ATTRIBUTE_1, someValue);
Object value = bean.get(Bean.ATTRIBUTE_1);
Polymorphism for the rescue
enum Attribute {
UPN("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn") {
void setValue(Principal principal, String value) {
principal.setUpn(value);
}
},
...
;
private final String name;
private Attribute(String name) {
this.name = name;
}
public abstract setValue(Principal principal, String name);
public static Attribute getByName(String name) {
for (Attribute attribute : values())
if (attribute.name.equals(name))
return attribute;
return null;
}
public static void setByName(Principal principal, String name, String value) {
Attribute attribute = getByName(name);
if (attribute == null)
throw new IllegalArgumentException("No such attribute");
attribute.setValue(principal, value);
}
}
If you know the attribute you want to set there is no reason to go go via the name:
Attribute.UPN.setValue(principal, "something");

Categories