Java calling method based on type - java

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);
}

Related

Avoiding many if statements - different datatypes

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.

Sorting with Java 8 by Field given as Input

I have a REST endpoint and I want the UI to pass the field name that they want to sort their result by "id", "name", etc. I came up with below, but was really trying to use Reflection / Generics so this could be expanded to encompass every object in my project.
I feel like this solution isn't easily maintainable if I want to have the same functionality for 100 different classes.
public static void sort(List<MovieDTO> collection, String field) {
if(collection == null || collection.size() < 1 || field == null || field.isEmpty()) {
return;
}
switch(field.trim().toLowerCase()) {
case "id":
collection.sort(Comparator.comparing(MovieDTO::getId));
break;
case "name":
collection.sort(Comparator.comparing(MovieDTO::getName));
break;
case "year":
collection.sort(Comparator.comparing(MovieDTO::getYear));
break;
case "rating":
collection.sort(Comparator.comparing(MovieDTO::getRating));
break;
default:
collection.sort(Comparator.comparing(MovieDTO::getId));
break;
}
}
Any ideas on how I could implement this better so that it can be expanded to work for an enterprise application with little maintenance?
Original post
I won't repeat everything said in the comments. There are good thoughts there. I hope you understand that reflection is not an optimal choice here.
I would suggest keeping a Map<String, Function<MovieDTO, String>>, where the key is a field name, the value is a mapper movie -> field:
Map<String, Function<MovieDTO, String>> extractors = ImmutableMap.of(
"id", MovieDTO::getId,
"name", MovieDTO::getName
);
Then, the collection can be sorted like:
Function<MovieDTO, String> extractor = extractors.getOrDefault(
field.trim().toLowerCase(),
MovieDTO::getId
);
collection.sort(Comparator.comparing(extractor));
Playing with reflection
As I promised, I am adding my vision of annotation processing to help you out. Note, it's not a version you have to stick firmly. It's rather a good point to start with.
I declared 2 annotations.
To clarify a getter name ( if not specified, <get + FieldName> is the pattern):
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD})
#interface FieldExtractor {
String getterName();
}
To define all possible sorting keys for a class:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
#interface SortingFields {
String[] fields();
}
The class MovieDTO has been given the following look:
#SortingFields(fields = {"id", "name"})
class MovieDTO implements Comparable<MovieDTO> {
#FieldExtractor(getterName = "getIdentifier")
private Long id;
private String name;
public Long getIdentifier() {
return id;
}
public String getName() {
return name;
}
...
}
I didn't change the sort method signature (though, it would simplify the task):
public static <T> void sort(List<T> collection, String field) throws NoSuchMethodException, NoSuchFieldException {
if (collection == null || collection.isEmpty() || field == null || field.isEmpty()) {
return;
}
// get a generic type of the collection
Class<?> genericType = ActualGenericTypeExtractor.extractFromType(collection.getClass().getGenericSuperclass());
// get a key-extractor function
Function<T, Comparable<? super Object>> extractor = SortingKeyExtractor.extractFromClassByFieldName(genericType, field);
// sort
collection.sort(Comparator.comparing(extractor));
}
As you may see, I needed to introduce 2 classes to accomplish:
class ActualGenericTypeExtractor {
public static Class<?> extractFromType(Type type) {
// check if it is a waw type
if (!(type instanceof ParameterizedType)) {
throw new IllegalArgumentException("Raw type has been found! Specify a generic type for further scanning.");
}
// return the first generic type
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
}
}
class SortingKeyExtractor {
#SuppressWarnings("unchecked")
public static <T> Function<T, Comparable<? super Object>> extractFromClassByFieldName(Class<?> type, String fieldName) throws NoSuchFieldException, NoSuchMethodException {
// check if the fieldName is in allowed fields
validateFieldName(type, fieldName);
// fetch a key-extractor method
Method method = findExtractorForField(type, type.getDeclaredField(fieldName));
// form a Function with a method invocation inside
return (T instance) -> {
try {
return (Comparable<? super Object>) method.invoke(instance);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
};
}
private static Method findExtractorForField(Class<?> type, Field field) throws NoSuchMethodException {
// generate the default name for a getter
String fieldName = "get" + StringUtil.capitalize(field.getName());
// override it if specified by the annotation
if (field.isAnnotationPresent(FieldExtractor.class)) {
fieldName = field.getAnnotation(FieldExtractor.class).getterName();
}
System.out.println("> Fetching a method with the name [" + fieldName + "]...");
return type.getDeclaredMethod(fieldName);
}
private static void validateFieldName(Class<?> type, String fieldName) {
if (!type.isAnnotationPresent(SortingFields.class)) {
throw new IllegalArgumentException("A list of sorting fields hasn't been specified!");
}
SortingFields annotation = type.getAnnotation(SortingFields.class);
for (String field : annotation.fields()) {
if (field.equals(fieldName)) {
System.out.println("> The given field name [" + fieldName + "] is allowed!");
return;
}
}
throw new IllegalArgumentException("The given field is not allowed to be a sorting key!");
}
}
It looks a bit complicated, but it's the price for generalisation. Of course, there is room for improvements, and if you pointed them out, I would be glad to look over.
Well, you could create a Function that would be generic for your types:
private static <T, R> Function<T, R> findFunction(Class<T> clazz, String fieldName, Class<R> fieldType) throws Throwable {
MethodHandles.Lookup caller = MethodHandles.lookup();
MethodType getter = MethodType.methodType(fieldType);
MethodHandle target = caller.findVirtual(clazz, "get" + fieldName, getter);
MethodType func = target.type();
CallSite site = LambdaMetafactory.metafactory(caller,
"apply",
MethodType.methodType(Function.class),
func.erase(),
target,
func);
MethodHandle factory = site.getTarget();
Function<T, R> function = (Function<T, R>) factory.invoke();
return function;
}
The only problem is that you need to know the types, via the last parameter fieldType
I'd use jOOR library and the following snippet:
public static <T, U extends Comparable<U>> void sort(final List<T> collection, final String fieldName) {
collection.sort(comparing(ob -> (U) on(ob).get(fieldName)));
}
Consider using ComparatorChain from apache commons.
Take a look to this answer https://stackoverflow.com/a/20093642/3790546 to see how to use it.

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);

Get attribute using annotation value

I want to use the method execute() of the following class:
public class Parser {
#Header("header1")
private String attribute1;
#Header("header2")
private String attribute2;
#Header("header3")
private String attribute3;
#Header("header4")
private String attribute4;
public String execute(String headerValue) {
//Execute
}
}
What I want this method to achieve is matching the headerValue parameter with one in the list of #Header annotations, and returning the value of the respective attribute. For example, if I call execute("header3"), it should return the value of attribute3
How can I achieve this? Or is it a better way to code this requirement?
Why don't you just use a map for this? You'd need one anyways in order to store the mapping of the annotation parameter value to the field but if you can do this without reflection it should be easier to code and to maintain.
What I mean is:
Map<String, String> attributes; //initialized
attributes.put("header1", value1);
...
In execute() you then just access the map.
You could improve this using an enum, e.g. in order to restrict the number of possible values.
Something like this:
enum HeaderType {
HEADER1,
HEADER2,
...
}
private Map<HeaderType, String> headerAttribs = ...;
void setAttrib( HeaderType type, String value ) {
headerAttribs.put(type, value);
}
String getAttrib( HeaderType type ) {
return headerAttribs.get(type);
}
public String execute(HeaderType type ) {
//Execute
}
If you need to use a string for the header type you could consider employing an additional map string->header type to look up the correct type first.
Alternatively you could use a switch statement which since Java 7 should work with strings as well.
Try this:
public String execute(String headerValue) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
for(Field field:this.getClass().getFields()) {
if (field.isAnnotationPresent(Header.class)) {
Header annotation = field.getAnnotation(Header.class);
String name = annotation.value();
if(name.equals(headerValue)) {
Object val = this.getClass().getField(name).get(this);
return (String) val;
}
}
}
return null;
}
There are a couple of exception to handle in line:
Object val = this.getClass().getField(name).get(this);
You can return null for that exception if you don't want to throw it from this method.
This may help you
Field f[]= Parser.class.getDeclaredFields();
for (int i = 0; i < f.length; i++) {
Annotation annotation[]= f[i].getAnnotations();
for (int j=0;j<annotation.length;j++){
Class<Annotation> type = (Class<Annotation>) annotation[j].annotationType();
for (Method method : type.getDeclaredMethods()) {
if(method.getName() .equals(headerValue))
{
String name=f[i].getName();
return name;
}
}
}
}
Parser.class.getDeclaredFields() will include private fields also.

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