Avoiding many if statements - different datatypes - java

I have many if statements and I want to avoid them.
I tried with HashMap, but it did not work with datatypes. Please see the code below that I have. This is just the first if statement but I have more...
field is a DataType
if (field.equals(DataTypes.IntegerType)) {
x = Integer.valueOf();
}
[...]
else if (field instanceof org.apache.spark.sql.types.TimestampType) {
try {
x = Timestamp.valueOf();
} catch (Exception) {
try {
columns[i] = Timestamp.valueOf(...).toLocalDateTime());
} catch (Exception) {
throw new ParseException("...");
}
}
[...]
else {
x = null
}
Can I somehow avoid so many else if statements?

You can keep a static Map of the conversions:
private static final Map<DataType, Function<String, ?>> stringConverters;
static {
Map<DataType, Function<String, ?>> map = new EnumMap<>(DataType.class);
map.put(DataType.IntegerType, Integer::valueOf);
map.put(DataType.LongType, Long::valueOf);
map.put(DataType.DoubleType, Double::valueOf);
map.put(DataType.FloatType, Float::valueOf);
map.put(DataType.StringType, Function.identity());
map.put(DataType.BinaryType, Binary::fromString);
if (!map.keySet().containsAll(EnumSet.allOf(DataType.class))) {
throw new RuntimeException(
"Programming error: Not all DataType values accounted for.");
}
stringConverters = Collections.unmodifiableMap(map);
}
// ...
columns[i] = stringConverters.get(field).apply(s);
The containsAll check is useful for making sure you don’t overlook any of the DataType values.

I assume DataTypes is an enum.
The simplest method would require access to the DataTypes enum to add a method that is implemented by each type.
enum DataTypes {
IntegerType() {
public Object valueFrom(String s) {
return Integer.parseInt(s);
}
},
LongType() {
return Long.parseLong(s);
}; // CONTINUE WITH ALL TYPES
public Object valueFrom(String s) {
return s;
}
}
Then you would simplify that code to:
columns[i] = field.valueFrom(s);
You could also keep the convert methods separated and just have a field in each enum type that holds a reference to the converter method for that type:
enum DataTypes {
IntegerType(Integer::parseInt),
LongType(Long::parseLong); // CONTINUE WITH ALL TYPES
private Function<String,Object> converter;
DataTypes(Function<String,Object> converter) {
this.converter = converter;
}
public Object valueFrom(String s) {
return converter.apply(s);
}
}

you can use switch but still if/else is preferred over any other method.

Related

Java calling method based on type

I'm making a Java class where I can apply a filter (searchterms) on a List with DTO's. The filter looks like:
[{ field: "price", value: "10.0" }, { field: "name", value: "%phone%" }]
In my class I have the following method, to apply all the filters to the list:
public List<T> applyFilters(List<T> input, ArrayList<LinkedHashMap<String, String>> searchTerms) {
for (LinkedHashMap<String, String> searchTerm : searchTerms) {
input = input.stream()
.filter(row -> {
try {
return applySingleFilter(row, searchTerm);
} catch (Exception e) {
throw new CustomGraphQLException(400, "The filter field is not a valid field in this type");
}
})
.collect(Collectors.toList());
}
return input;
}
But the applySingleFilter has different implementations based on the type of a field. Like for Strings I create a Regex:
private boolean applySingleStringFilter (T category, LinkedHashMap<String, String> searchTerm) throws Exception {
String patternString = createCompareRegex(searchTerm.get("value"));
String propertyValue = (String) PropertyUtils.getProperty(category, searchTerm.get("field"));
return propertyValue.matches(patternString);
}
But for like a Float I want another comparison, I don't want to apply the Regex to a float. What is the best way to make sure the correct method is called based on the type of the field?
Well, you'd first need to know the type of the field and once you have that information you could maintain a repository of filters that apply to the type (some casting might be required though).
We had a similar system in place which in essence looked like this:
interface Filter<T> {
Class<T> getHandledType();
boolean apply(T element);
}
class DoubleFilter implements Filter<Double> {
public Class<Double> getHandledType() { return Double.class; }
boolean apply(Double element) {
//filter here
}
}
The repository was basically a Map<Class<?>, Filter<?>> and using it was like:
Object fieldValue = //get the field value;
Class<?> fieldType = fieldValue.getClass(); //ofc, check for null first
Filter<?> filter = repo.get(fieldType);
if( filter != null ) {
//nasty cast to a raw type to tell the compiler to allow the call
((Filter)filter).apply(fieldValue);
}

Java Map with variable generics as values

So here's a slightly tricky question (for me).
I have a generic object. Call it MyObject. This object has a method which returns something of the type T:
public class MyObject<T>
{
private T _t;
public MyObject(T t)
{
_t = t;
}
//...
public T get()
{
return _t;
}
}
(Obviously my "MyObject" does a bit more but that's the gist).
Now, I want to have a map of this type:
Map<String, MyObject<?>> m = new HashMap<>();
I want to be able to fetch maps using some predefined string name, and these maps can be of any MyObject. For example, I could call:
m.put("map_1", new MyObject<String>("String"));
m.put("map_2", new MyObject<Integer>(new Integer(3));
m.put("map_3", new MyObject<Long>(new Long(5));
etc.
But - and here's the tricky part - I want the map to "remember" the parameterized type of MyObject when I fetch some value from the map. Using
m.get("map_1");
would return a
MyObject<Object>
type, since the map was defined as containing
MyObject<?>
values. Thus:
m.get("map_1").get() // <-- This is an Object, not a String!
What modification (if any) is possible, in order to be able to get the correct - full - information regarding the MyObject fetched object, such that invoking the last line (m.get("map_1")) would return a
MyObject<String>
Thanks :)
Amir.
Typesafe Heterogeneous Containers from Joshua Bloch's Effective Java might work here. Basically you add a Class object to represent the type.
public class MyObject<T>
{
private T _t;
private Class<T> type;
public MyObject( Class<T> type, T t)
{
_t = t;
this.type = type;
}
//...
public T get()
{
return _t;
}
public Class<T> getType() { return type; }
}
Then you could do something like this:
public <T> T get( Map<String, MyObject<?>> map, String key, Class<T> type ) {
return type.cast( m.get( key ).get() );
}
Which is safe and will compile, but will throw a runtime error if you get the type wrong.
(Note I didn't actually compile that, so I might have syntax errors floating around. But most folks don't know how to use Class to cast objects.)
You can get the class.
Class c = m.get("map_1").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
Here is a full test.
public class GenericsTest {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Map<String, MyObject<?>> map = new HashMap<>();
MyObject<String> obj = new MyObject<>("hello");
map.put("greeting", obj);
Class c = map.get("greeting").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
}
static class MyObject<T> {
T t;
public MyObject(T t) {
this.t = t;
}
T get() {
return t;
}
}
}
The type system only knows about types, not objects, and therefore can not distinguish "key1" from "key2", because both are of type String.
If keys have different types, the easiest way is to encapsulate a weakly typed map, and use reflective casts to prove to the compiler the types are correct:
class Favorites {
private Map<Class<?>,?> map = new HashMap<>();
<V> V get(Class<V> clazz) {
return clazz.cast(map.get(clazz));
}
<V> void put(Class<V> clazz, V value) {
map.put(clazz, value);
}
}
Favorites favs = new Favorites();
favs.put(String.class, "hello");
favs.put(Integer.class, 42);
favs.get(String.class).charAt(1);

Writing generic POJO to CSV transformer

My use case was to write a generic CSV transformer, which should be able to convert any Java POJO to CSV string.
My Implementation :
public <T> List<String> convertToString(List<T> objectList) {
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(!HOW_TO!);
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
I was using Jackson-dataformat-csv library, but I'm stuck with !HOW_TO! part, ie How to extract the .class of the object from the objectList. I was studying and came across Type Erasure, So I think it is somehow not possible other than giving the .class as parameter to my function. But I'm also extracting this object list from generic entity using Java Reflection, so I can't have the option to provide the .class params.
Is there a workaround for this?
OR
Any other approaches/libraries where I can convert a generic List<T> objectList to List<String> csvList with functionality of adding delimiters, quote characters, line separators etc.
Thanks!
I have created a CSVUtil Class similar to below which uses java reflection.
Example to use below CSVUtil
Assuming POJO Student ,
List<Student> StudentList = new ArrayList<Student>();
String StudentCSV = CSVUtil.toCSV(StudentList,' ',false);
import java.lang.reflect.Field;
import java.util.List;
import java.util.logging.Logger;
CSVUtil class
public class CSVUtil {
private static final Logger LOGGER = Logger.getLogger(CSVUtil.class .getName());
private final static char DEFAULT_SEPARATOR = ' ';
public static String toCSV(List<?> objectList, char separator, boolean displayHeader) {
StringBuilder result =new StringBuilder();
if (objectList.size() == 0) {
return result.toString();
}
if(displayHeader){
result.append(getHeaders(objectList.get(0),separator));
result.append("\n");
}
for (Object obj : objectList) {
result.append(addObjectRow(obj, separator)).append("\n");
}
return result.toString();
}
public static String getHeaders(Object obj,char separator) {
StringBuilder resultHeader = new StringBuilder();
boolean firstField = true;
Field fields[] = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String value;
try {
value = field.getName();
if(firstField){
resultHeader.append(value);
firstField = false;
}
else{
resultHeader.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException e) {
LOGGER.severe(e.toString());
}
}
return resultHeader.toString();
}
public static String addObjectRow(Object obj, char separator) {
StringBuilder csvRow =new StringBuilder();
Field fields[] = obj.getClass().getDeclaredFields();
boolean firstField = true;
for (Field field : fields) {
field.setAccessible(true);
Object value;
try {
value = field.get(obj);
if(value == null)
value = "";
if(firstField){
csvRow.append(value);
firstField = false;
}
else{
csvRow.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.severe(e.toString());
}
}
return csvRow.toString();
}
}
There is a simple option. I've added some lines to your code to show it :
public <T> List<String> convertToString(List<T> objectList) {
if(objectList.isEmpty())
return Collections.emptyList();
T entry = objectList.get(0);
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(entry.getClass());
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
stringList.add(csv);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
The trick is to get one of the elements of the list. In order to avoid crashs I've added a little data integrity test at the beginning that return an unmodifiable empty list in the case there are no items in the input list.
Then you retrieve an instance of your Object and use that to get the class.
Alternatively if the convertToString method is in a parametrized class you can do that in a slightly different way
public class GenericClass<T> {
private final Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public Class<T> getMyType() {
return this.type;
}
}
This solution allow you to get the class of T. I don't think you'll need it for this question but it might comes in handy.
It seems this problem is just harder than most people would like it to be as a result of how Java does generics. Bruno's answer shows options that might work if you can make certain assumptions or can structure your code a certain way.
Another option that should work for your case can be found by way of the answers to this other question: How to get a class instance of generics type T
In there you'll find a link to an article: http://blog.xebia.com/acessing-generic-types-at-runtime-in-java/
This describes how to use the ParameterizedType of an object's superclass. You can apply that to your List object and hopefully it will work for you. This only may luckily work in this case, because you're taking as a parameter an object with a superclass whose type parameters match what you need.
Truly in general, we can't rely on knowing the type parameters at runtime. We can at best maybe use type tokens (parameter of type Class<T>)

ModelMapper handling java 8 Optional<MyObjectDto> fields to Optional<MyObject>

I've been using modelmapper and java 8 Optionals all around the application which was working fine because they were primitive types; until I changed one of my model objects' field to Optional type. Then all hell broke loose. Turns out many libraries cannot handle generics very well.
Here is the structure
public class MyObjectDto
{
private Optional<MySubObjectDto> mySubObject;
}
public MyObject
{
privae Optional<MySubjObject> mySubObject;
}
When I attempt to map MyObjectDto to MyObject, modelmapper calls
public void setMySubObject(Optional<MySubObject> mySubObject){
this.mySubObject = mySubObject;
}
with Optional<MySubObjectDto>, which I don't understand how that's even possible (there is no inheritance between them). Of course that crashes fast. For now I've changed my setters to accept Dto type just to survive the day but that's not going to work on the long run. Is there a better way to get around this, or shall I create an issue?
So I digged into the modelmapper code and have done this looking at some generic implementations:
modelMapper.createTypeMap(Optional.class, Optional.class).setConverter(new OptionalConverter());
public class OptionalConverter implements ConditionalConverter<Optional, Optional> {
public MatchResult match(Class<?> sourceType, Class<?> destinationType) {
if (Optional.class.isAssignableFrom(destinationType)) {
return MatchResult.FULL;
} else {
return MatchResult.NONE;
}
}
private Class<?> getElementType(MappingContext<Optional, Optional> context) {
Mapping mapping = context.getMapping();
if (mapping instanceof PropertyMapping) {
PropertyInfo destInfo = ((PropertyMapping) mapping).getLastDestinationProperty();
Class<?> elementType = TypeResolver.resolveArgument(destInfo.getGenericType(),
destInfo.getInitialType());
return elementType == TypeResolver.Unknown.class ? Object.class : elementType;
} else if (context.getGenericDestinationType() instanceof ParameterizedType) {
return Types.rawTypeFor(((ParameterizedType) context.getGenericDestinationType()).getActualTypeArguments()[0]);
}
return Object.class;
}
public Optional<?> convert(MappingContext<Optional, Optional> context) {
Class<?> optionalType = getElementType(context);
Optional source = context.getSource();
Object dest = null;
if (source != null && source.isPresent()) {
MappingContext<?, ?> optionalContext = context.create(source.get(), optionalType);
dest = context.getMappingEngine().map(optionalContext);
}
return Optional.ofNullable(dest);
}
}

Applying a list of values as predicate using Collection Utils by the use of pedicates

I want to implement Database systems in functionality by using the predicate.
This is as like if SQL filter a recordset by in it cumbersome the results.
But if i pass the List as in predicate it takes only one value i.e. if i am passing 53 and 54 it filter the results for 53 only.
public class classNamePredicate implements Predicate<className> {
private Object expected1;
private String property;
private List<Object> listOfValues = new ArrayList<Object>();
public SalesOrderPredicate(Object expected1, String property) {
super();
this.expected1 = expected1;
this.property = property;
}
public SalesOrderPredicate(List<Object> listValues, String property) {
this.listOfValues = listValues;
this.property = property;
}
#Override
public boolean evaluate(SalesOrder object) {
try {
if (property.equals("volume")) {
return ((Integer) expected1 < object.getVolume());
}
if (property.equals("startDateId")) {
return (expected1.equals(object.getStartDateId()));
}
if (property.equals("endDateId")) {
return (expected1.equals(object.getEndDateId()));
}
if (property.equals("productIds")) {
for (Object value : listOfValues) {
return (object.getProductId() == (Integer) value);
}
}
if (property.equals("sourceIds")) {
for (Object value : listOfValues) {
return (object.getSourceId() == (Integer) value);
}
}
return false;
} catch (Exception e) {
return false;
}
}
}
I am trying to use this as per the following way:
List<Object> productIds = new ArrayList<Object>();
productIds.add(53);
productIds.add(54);
List<Object> sourceIds = new ArrayList<Object>();
sourceIds.add(122);
Predicate[] classnameOrderPredicate = { (Predicate) new classnamePredicate(4415, "startDateId"),
(Predicate) new classnamePredicate(4443, "endDateId"), (Predicate) new classnamePredicate(100000, "volume"),
(Predicate) new classnamePredicate(productIds, "productIds"), (Predicate) new classnamePredicate(sourceIds, "sourceIds") };
Predicate classnameallPredicateGeneric = (Predicate) PredicateUtils
.allPredicate((org.apache.commons.collections4.Predicate<? super classname>[]) classnamePredicate);
Collection<classname> classnamefilteredCollectionGeneric = GenericCollectionUtils.select(classname, classnameallPredicateGeneric);
Please suggest in design perspective too.
Thanks in advance
You're only evaluating the first item in the collection:
for (Object value : listOfValues) {
return (object.getProductId() == (Integer) value);
}
You want to evaluate all of them, and Java conveniently provides a contains() method for that:
return listOfValues.contains(object.getProductId());
Other than that, the code looks pretty awful, you should create smaller, targeted Predicates, instead of writing a generic one with lots of different cases. You could get rid of those casts at the same time.
You also failed at your obfuscation by failing to replace a few SalesOrder by className (which doesn't respect the Java coding standard and is distracting).

Categories

Resources