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");
Related
I am going to use method addPresetToTaskView that will send text in selenium.
This method should use String presetValue or overwrite it if map scenarioContext contains key exactly like presetValue
I am not sure if this is the best practice.
Could you share with me your opinion please ?
#And("Added preset by {string} text {string} for permission {string} to data view")
public void addPresetToTaskView(String option, String presetValue, String permissionName) {
if(scenarioContext.contextContainsKey(presetValue)) {
presetValue = (String) scenarioContext.getContext(presetValue);
}
setSingleSelectformItem(presetValue, visibleTabsSelect);
}
Scenario context with HashMap class
public class ScenarioContext {
public Map<String, Object> scenarioContext = new HashMap<>();
public void setContext(String key, Object value) {
scenarioContext.put(key, value);
}
public Object getContext(String key){
return scenarioContext.get(key);
}
public Boolean contextContainsKey(String key){
return scenarioContext.containsKey(key);
}
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?
I have a Switch that contains 13 case, each case executes a different sql request. I got the result in an ArrayList<HashMap<String, String>>. This result is supposed to be displayed with angular , for now i'm using this this.respTest = JSON.stringify(response); so it displays a list of "key":"value" .
My problem is since each request gets me different database fields and values ,so I want to merge some fields .
I created this class :
public class DataCollect {
private String type ;
private String entity ;
private String modPar ;
private String dateModif ;
private String numVersion ;
public DataCollect(String type, String entity, String modPar, String dateModif, String numVersion) {
this.type = type;
this.entity = entity;
this.modPar = modPar;
this.dateModif = dateModif;
this.numVersion = numVersion;
}
public DataCollect() {
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public String getModPar() {
return modPar;
}
public void setModPar(String modPar) {
this.modPar = modPar;
}
public String getDateModif() {
return dateModif;
}
public void setDateModif(String dateModif) {
this.dateModif = dateModif;
}
public String getNumVersion() {
return numVersion;
}
public void setNumVersion(String numVersion) {
this.numVersion = numVersion;
} }
In this class I'm supposed to affect the fields' names to the variables that I created and as a return an arraylist of hashmap with the data I extracted from the data base.
I mean I used to return for example "field-name":"value" , I want to return "type":"value","entity":"value" ..etc
I'm using springboot for the backend and angular 5 for the front.
Any help would be appreciated.
What you essentially want is a way to map keys in [each of] your hashmap to the corresponding member variable in the "DataCollect" POJO.
If there is a one to one mapping between the key present and corresponding member variable, you can expose a public constructor in "DataCollect" that takes in the hash map and constructs the corresponding object.
public DataCollect(Map<String, String> result) {
this.type = result.get("type");
this.entity = result.get("db_entity_key");
...
}
If there is no one on one mapping, you'd have to create a factory class, which takes your Map as an input and some context, and returns you the constructed DataCollect object.
Once you have the constructor or the factory class, you only need to iterate over your results list and do the needful to convert each Map into the DataCollect object.
Your controller should automatically serialise the DataCollect objects to corresponding JSON, or you can even use Jackson's ObjectMapper to achieve the same.
I would like to define a method and by passing the enum, returns the mapped type based on the enum. So far I only work out this way:
public class Person {
HashMap<String, Object> mData;
void int getDetail(DetailInt detail){
Object data = mData.get(detail.name());
if(data instanceof Integer)
return (int)data;
return 0;
}
void String getDetail(DetailStr detail){
Object data = mData.get(detail.name());
if(data instanceof String)
return (String)data;
return "";
}
}
public enum DetailInt {
Age("age"), Weight("weight"), Height("height");
String columnName;
DetailInt(String columnName){
this.columnName= columnName;
}
}
public enum DetailStr {
FirstName("first_name"), LastName("last_name");
String columnName;
DetailStr (String columnName){
this.columnName= columnName;
}
}
So I can use the same method, but passing different enums to get the data with the type.
int age = person.getDetail(DetailInt.Age);
String firstName = person.getDetail(DetailStr.FirstName);
Now, what I would like to achieve is to merge both enums together, so I can call as below:
int age = person.getDetail(Detail.Age);
String firstName = person.getDetail(Detail.FirstName);
It is neater. However, I have tried generic type and interface, still cannot find the way to do it. Use below way is similar to what I want but this is not enum type.
abstract class Detail {
}
class DetailStr extend Detail {
}
interface Details {
DetailStr firstName = new DetailStr("first_name");
DetailStr lastName = new DetailStr("las_name");
DetailInt age = new DetailInt("age");
DetailInt weight = new DetailInt("weight");
DetailInt height = new DetailInt("height");
}
public class Person {
void int getDetail(DetailInt detail){
....
}
void String getDetail(DetailStr detail){
....
}
}
You can't do this in Java.
This is because a particular value of an enumerator has the same type as any other value of that enumerator. It's therefore not possible to construct an overloaded function since there's no type difference to act as a descriminator. (You cannot overload a function by return type difference alone.)
The obvious solution is to have two methods getDetailAsInt and getDetailAsString.
I'll share this approach that does not use enums, but it might be of some use to you:
public class Key<T> {
private String key;
...
}
public class Keys {
public static final Key FIRST_NAME = new Key<String>("first_name");
public static final Key AGE = new Key<Integer>("age");
}
public class Person {
public <T> T getDetail(Key<T> key) {
Object detail = mData.get(key.getKey());
return (T) detail;
}
}
I'm afraid it might not be possible to convert it to use enums, so you'd have to ensure no unwanted keys are created in some other way (package-private constructor etc.)
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.