How do I cleanly drill down through nested object in Java? - java

I have the following Java class:
public class MyClass{
private List<Settings> settings;
public static class Settings {
private String name;
private List<Features> features;
}
public static class Features {
private String name;
private Boolean isActive;
}
}
What I want to do is first check that settings is not null or empty. If not, then I want to find the Settings object that has the name "reliability", and then find its Features objects that have the names "logs" and "score" and get the isActive from these two objects.
This is what I've tried:
MyClass myClass = new MyClass ();
Boolean logs = false;
Boolean score = false;
if (myClass.getSettings != null) {
for (Settings setting: myClass.getSettings) {
if (setting.getName().equals("reliability")) {
for (Features features : setting.getFeatures) {
if (features.getName().equals("logs")) {
logs = features.getIsActive;
} else if (features.getName().equals("score")) {
score = features.getIsActive;
}
}
}
}
}
How do I do this in a clean way? I can only do it with countless nested if and for loops, and it is not pretty.

Here is the possible solution with Streams.
I assume that there would be no duplicated Features (i.e. having the same name) objects.
By the way, class names are usually singular nouns. Class Features is meant to represent a single object with a distinct name and a single property isActive. Therefore, the name Feature might` be more suitable.
The method below expects an argument of type MyClass, settings name and varargs of names of target features. The result it produces is Map with feature names as keys and corresponding isActive properties as values.
public static Map<String, Boolean> getFeaturesByName(MyClass myClass,
String settingName,
String... featureNames) {
if (myClass.getSettings() == null) return Collections.emptyMap();
Set<String> featureSet = Set.of(featureNames);
return myClass.getSettings().stream()
.filter(settings -> settings.getName().equals(settingName))
.flatMap(settings -> settings.getFeatures().stream())
.filter(features -> featureSet.contains(features.getName()))
.collect(Collectors.toMap(
MyClass.Features::getName,
MyClass.Features::getActive
));
}

Delegate the "drilling" to your classes:
add to MyClass
public boolean hasSettings() {
return settings != null && !settings.isEmpty();
}
public Settings getSetting(String name) {
return settings.stream()
.filter(s -> s.hasName(name))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No settings with name " + name));
}
add to Settings
public boolean hasName(String name) {
return this.name.equals(name);
}
public Features getFeature(String name) {
return features.stream()
.filter(f -> f.hasName(name))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No feature with name " + name));
}
add to Features
public boolean hasName(String name) {
return this.name.equals(name);
}
Then you can do
if (myClass.hasSettings()) {
Settings reliabilitySetting = myClass.getSetting("reliability");
logs = reliabilitySetting.getFeature("logs").isActive();
score = reliabilitySetting.getFeature("score").isActive();
}
NOTE: You can return Optionals if you don't want to throw exceptions.

Well to check if the object is nullable or not you can use the optional class then regarding of find the setting object you have an option to use Java Stream Filter to filter the result.

You can stream API to reduce the code
private static final String SETTINGS_NAME_FOR_CHECK = "reliability";
private static final List<String> FEATURES_NAME_FOR_CHECK = Arrays.asList("logs", "score");
public static void main(String[] args) {
MyClass myClass = new MyClass();
if(myClass.getSettings() != null) {
Settings correctSettings = myClass.getSettings().stream()
.filter(setting -> SETTINGS_NAME_FOR_CHECK.equals(setting.getName()))
.findFirst().orElse(null);
if(correctSettings.getFeatures() != null) {
List<Features> features = correctSettings.getFeatures().stream()
.filter(feature -> (FEATURES_NAME_FOR_CHECK.contains(feature.getName()))).collect(Collectors.toList());
System.out.println(features);
}
}
}
Once you get the features (which will be only having logs and shares), you can check for the boolean value.

To make your code cleaner you can move logic into support methods. This also makes it more testable. In your code you are inspecting an instance of MyClass to determine if it certain features which are identified by name. You could write a method that does just that. Your original code could be re-written as:
MyClass myClass = ...
boolean hasLogs = hasSettingFeature(myClass, "reliability", "logs");
boolean hasScore = hasSettingFeature(myClass, "reliability", "score");
You can iterate through the given instance within the support model. You can do with this for-loops.
public boolean hasSettingFeature(MyClass myClass, String settingName, String featureName) {
if (null == myClass || null == myClass.getSettings()) {
return false;
}
for (Settings settings : myClass.getSettings()) {
if (settingName.equals(settings.getName()) {
for (Features features : settings.getFeatures()) {
if (featureName.equals(features.getName()) {
return true;
}
}
}
}
return false;
}
You may also use the Stream API to filter to determine the state:
public boolean hasSettingFeature(MyClass myClass, String settingName, String featureName) {
if (null == myClass || null == myClass.getSettings()) {
return false;
}
return myClass.getSettings().stream()
.filter(setting -> settingName.equals(setting.getName()))
.flatMap(setting -> setting.getFeatures())
.filter(features -> featureName.equals(features.getName()))
.findAny()
.isPresent();
}

I think, a more fitting datastructure would be the Map. I presume every Features exists only once?
public class MyClass {
private Map<String, Settings> settings;
public static class Settings {
private String name;
private Map<String, Features> features;
}
public static class Features {
private String name;
private Boolean isActive;
}
public static void main(String[] args) {
MyClass t = new MyClass();
Boolean logs = null, score = null;
Settings s = t.settings.get("reliability");
if (s != null) {
Features f;
if ((f = s.features.get("logs")) != null) // Is "features" null-save?
logs = f.isActive;
else if ((f = s.features.get("score")) != null)
score = f.isActive;
}
System.out.println("Logs: " + logs + ", Score: " + score);
}
}
(Also it is very bad practice to use the nullable wrapperclass Boolean. You might wanna do something about that.)
If you have problems turning your existing lists into maps, use this method:
public static void main(String[] args) {
Map<String, Settings> map = toMap(List.of(new Settings()), s -> s.name);
}
static <K, V> Map<K, V> toMap(Collection<V> c, Function<V, K> func) {
return c.stream().collect(Collectors.toMap(func, v -> v));
}

Related

How to create a composite annotation? [duplicate]

I have used Lombok in my code to automatically generate getter and setter code. I want to add other personal annotations and use it.
For example, I want to add an #Exist method which verifies the existence of a key in a list:
#Getter #Setter
public class User {
private String name;
private List<Integer> keys;
public boolean existKeys(Integer key) {
boolean exist = keys.contains(key);
return exist;
}
}
After creating the annotation, I would do something like:
#Getter #Setter
public class User {
private String name;
#Exist
private List<Integer> keys;
}
General Considerations
If you are already using Lombok, you can add custom Lombok transformation annotation and handler.
Define Exists annotation with #Target(FIELD) and #Retention(SOURCE)
Create a handler
#ProviderFor(JavacAnnotationHandler.class)
public class HandleExists extends JavacAnnotationHandler<Exists>{ ...`
to process your annotation. Handler class package must start with the lombok. prefix. If you need to support Eclipse, etc. in addition to javac, you'll need to write more handlers extending appropriate framework classes.
In the handler override/implement the handle() method to generate the required code through AST manipulation.
You can take as a sample the #Getter implementation:
Annotation:
Getter.java
Handler:
HandleGetter.java
You can also look into sources of other annotations and handlers to see how to generate particular code.
You'll need to add dependencies on lombok, JDK tools.jar.
Some resources:
The lombok-pg project with a source for a bunch of custom lombok annotations, in particular FluentSetter.java, HandleFluentSetter.java / FluentSetterHandler.java
An overview of a custom transformation
Simple annotation example with explanations.
Note, there are some points to consider here
This is a bunch of non-trivial code to write and maintain. If you plan to use annotation 5-6 times it is just not worth it.
You may need to change your annotation processor implementation with lombok upgrades.
The hole in compiler that lombok relies on also may be closed (then the whole Lombok project will change dramatically or cease to exist; in this case you'll have a more serious problem anyway if you use Lombok extensively, even if just for #Getter).
A more complex alternative without Lombok is to use standard annotation processing for code generation but, AFAIK, you can't change original classes and must generate/use classes that extend them (unless you'll exploit the same back-door as Lombok or resort to a code manipulation like CGLib or ASM).
Lombok Example
Below is some working code to create custom Lombok annotation that I've called #Contains.
It is javac implementation only, no Eclipse, etc. I guess it will be not hard to create a similar handler for Eclipse or other IDE.
It will generate fieldNameContains() member method which is delegated to the fieldName.contains().
Note, the code is just quick and dirty (but working) sample. For production grade annotation, you will need to handle many boundary conditions, check correct types, handle Lombok configuration and so on, as it can be observed in lombok or lombok-pg library sources.
Sample usage
SomeEnity.java
#Getter
#Setter
public class SomeEntity {
#NonNull
#Contains
private Collection<String> fieldOne = new ArrayList<>();
#NonNull
#Contains
private Collection<String> fieldTwo = new ArrayList<>();
}
SomeEntityTest.java
public class SomeEntityTest {
#Test
public void test() {
SomeEntity entity = new SomeEntity();
Collection<String> test1 = Arrays.asList(new String[] { "1", "2" });
entity.setFieldOne(test1);
assertSame(test1, entity.getFieldOne());
Collection<String> test2 = new HashSet<String>(Arrays.asList(new String[] { "3", "4" }));
entity.setFieldTwo(test2);
assertSame(test2, entity.getFieldTwo());
assertTrue(entity.fieldOneContains("1"));
assertTrue(entity.fieldOneContains("2"));
assertFalse(entity.fieldOneContains("3"));
assertFalse(entity.fieldOneContains("4"));
assertFalse(entity.fieldTwoContains("1"));
assertFalse(entity.fieldTwoContains("2"));
assertTrue(entity.fieldTwoContains("3"));
assertTrue(entity.fieldTwoContains("4"));
try {
entity.setFieldOne(null);
fail("exception expected");
} catch (Exception ex) {
}
try {
entity.setFieldTwo(null);
fail("exception expected");
} catch (Exception ex) {
}
}
}
Annotation Implementaiton
Contains.java
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.SOURCE)
public #interface Contains {
Class<?>[] types() default {};
Class<?>[] excludes() default {};
}
HandleContains.java
#ProviderFor(JavacAnnotationHandler.class)
#HandlerPriority(65536)
#ResolutionResetNeeded
public class HandleContains extends JavacAnnotationHandler<Contains> {
#Override
public void handle(AnnotationValues<Contains> annotation, JCAnnotation ast, JavacNode annotationNode) {
try {
JavacNode node = annotationNode.up();
if (node.getKind() != Kind.FIELD) {
annotationNode.addError("#Contains is allowed only on fields");
return;
}
Name delegateName = annotationNode.toName(node.getName());
JavacResolution reso = new JavacResolution(annotationNode.getContext());
JCTree member = node.get();
if (member.type == null) {
reso.resolveClassMember(node);
}
Type delegateType = member.type;
if (delegateType instanceof ClassType) {
ClassType ct = (ClassType) delegateType;
//TODO validate that this field is a collection type
// if(!Collection)
// annotationNode.addError("#Contains can only be used on collections");
final String methodName = "contains";
MethodSig methodSig = getMethodBinding(methodName, ct, annotationNode.getTypesUtil());
if (methodSig == null) throw new Exception("no method " + methodName + " in " + ct.tsym.name);
JCMethodDecl methodDecl = createDelegateMethod(methodSig, annotationNode, delegateName);
injectMethod(node.up(), methodDecl);
} else {
annotationNode.addError("#Contains can only use concrete class types");
return;
}
} catch (Exception ex) {
//ex.printStackTrace();
annotationNode.addError("#Contains unexpected error: " + ex.getMessage());
}
}
public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateName) throws TypeNotConvertibleException {
JavacTreeMaker maker = annotation.getTreeMaker();
com.sun.tools.javac.util.List<JCAnnotation> annotations;
if (sig.isDeprecated) {
annotations = com.sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.<JCExpression>nil()));
} else {
annotations = com.sun.tools.javac.util.List.nil();
}
JCModifiers mods = maker.Modifiers(PUBLIC, annotations);
JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);
boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;
ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>();
ListBuffer<JCExpression> args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCExpression> thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCTypeParameter> typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCTypeParameter>();
ListBuffer<JCExpression> typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCExpression>();
Types types = Types.instance(annotation.getContext());
for (TypeMirror param : sig.type.getTypeVariables()) {
Name name = ((TypeVar) param).tsym.name;
ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>();
for (Type type : types.getBounds((TypeVar) param)) {
bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true));
}
typeParams.append(maker.TypeParameter(name, bounds.toList()));
typeArgs.append(maker.Ident(name));
}
for (TypeMirror ex : sig.type.getThrownTypes()) {
thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));
}
int idx = 0;
String[] paramNames = sig.getParameterNames();
boolean varargs = sig.elem.isVarArgs();
for (TypeMirror param : sig.type.getParameterTypes()) {
long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());
JCModifiers paramMods = maker.Modifiers(flags);
Name name = annotation.toName(paramNames[idx++]);
if (varargs && idx == paramNames.length) {
paramMods.flags |= VARARGS;
}
params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));
args.append(maker.Ident(name));
}
JCExpression accessor = maker.Select(maker.Ident(annotation.toName("this")), delegateName);
JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(accessor, sig.name), toList(args));
JCStatement body = useReturn ? maker.Return(delegateCall) : maker.Exec(delegateCall);
JCBlock bodyBlock = maker.Block(0, com.sun.tools.javac.util.List.of(body));
StringBuilder generatedMethodName = new StringBuilder(delegateName);
generatedMethodName.append(sig.name.toString());
generatedMethodName.setCharAt(delegateName.length(), Character.toUpperCase(generatedMethodName.charAt(delegateName.length())));
return recursiveSetGeneratedBy(maker.MethodDef(mods, annotation.toName(generatedMethodName.toString()), returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null), annotation.get(), annotation.getContext());
}
public static <T> com.sun.tools.javac.util.List<T> toList(ListBuffer<T> collection) {
return collection == null ? com.sun.tools.javac.util.List.<T>nil() : collection.toList();
}
public static class MethodSig {
final Name name;
final ExecutableType type;
final boolean isDeprecated;
final ExecutableElement elem;
MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {
this.name = name;
this.type = type;
this.isDeprecated = isDeprecated;
this.elem = elem;
}
String[] getParameterNames() {
List<? extends VariableElement> paramList = elem.getParameters();
String[] paramNames = new String[paramList.size()];
for (int i = 0; i < paramNames.length; i++) {
paramNames[i] = paramList.get(i).getSimpleName().toString();
}
return paramNames;
}
#Override public String toString() {
return (isDeprecated ? "#Deprecated " : "") + name + " " + type;
}
}
public MethodSig getMethodBinding(String name, ClassType ct, JavacTypes types) {
MethodSig result = null;
TypeSymbol tsym = ct.asElement();
if (tsym == null) throw new IllegalArgumentException("no class");
for (Symbol member : tsym.getEnclosedElements()) {
if (member.getKind() != ElementKind.METHOD || !name.equals(member.name.toString())) {
continue;
}
if (member.isStatic()) continue;
if (member.isConstructor()) continue;
ExecutableElement exElem = (ExecutableElement) member;
if (!exElem.getModifiers().contains(Modifier.PUBLIC)) continue;
ExecutableType methodType = (ExecutableType) types.asMemberOf(ct, member);
boolean isDeprecated = (member.flags() & DEPRECATED) != 0;
result = new MethodSig(member.name, methodType, isDeprecated, exElem);
}
if (result == null) {
if (ct.supertype_field instanceof ClassType) {
result = getMethodBinding(name, (ClassType) ct.supertype_field, types);
}
if (result == null) {
if (ct.interfaces_field != null) {
for (Type iface : ct.interfaces_field) {
if (iface instanceof ClassType) {
result = getMethodBinding(name, (ClassType) iface, types);
if (result != null) {
break;
}
}
}
}
}
}
return result;
}
}

Java 8 to remove string value from each field where "string" value comes of the Custom Object

I went through link: Is it possible in Java to check if objects fields are null and then add default value to all those attributes? and implemented the same solution as below -
Note: I am using Swagger/Open API Specs (using springdoc-openapi-ui) and while making POST request all string fields are having default value as "string" which I really wanted to set it to null or space.
Any quick pointer ?
public static Object getObject(Object obj) {
for (Field f : obj.getClass().getFields()) {
f.setAccessible(true);
try {
if (f.get(obj) == "string") {
f.set(obj, null);
}
} catch (IllegalArgumentException | IllegalAccessException e) {
log.error("Error While Setting default values for String");
}
}
return obj;
}
REST endpoints
#GetMapping(value = "/employees")
public ResponseEntity<PagedModel<EmployeeModel>> findEmployees(
EmployeeDto geoDto,
#Parameter(hidden=true) String sort,
#Parameter(hidden=true) String order,
#Parameter(hidden=true) Pageable pageRequest) {
EmployeeDto dto = (EmployeeDto) CommonsUtil.getObject(geoDto);
Page<CountryOut> response = countryService..............;
PagedModel<EmployeeModel> model = employeePagedAssembler.toModel(response, countryOutAssembler);
return new ResponseEntity<>(model, HttpStatus.OK);
}
You could do it a bit simpler, I guess. If you control EmployeeDto, for example:
#Accessors(chain = true)
#Getter
#Setter
#ToString
static class EmployeeDto {
private String firstname;
private String lastname;
private int age;
}
You could iterate over fields of the class and using MethodHandles invoke the needed setters, when some getters return the string you are interested in (and Strings are compared using equals, not ==). This can even be made into a tiny library. Here is a start:
private static final Lookup LOOKUP = MethodHandles.lookup();
/**
* this computes all the know fields of some class (EmployeeDTO in your case) and their getter/setter
*/
private static final Map<Class<?>, Map<Entry<String, ? extends Class<?>>, Entry<MethodHandle, MethodHandle>>> ALL_KNOWN =
Map.of(
EmployeeDto.class, metadata(EmployeeDto.class)
);
private Map<String, Entry<MethodHandle, MethodHandle>> MAP;
/**
* For example this will hold : {"firstname", String.class} -> getter/setter to "firstname"
*/
private static Map<Entry<String, ? extends Class<?>>, Entry<MethodHandle, MethodHandle>> metadata(Class<?> cls) {
return Arrays.stream(cls.getDeclaredFields())
.map(x -> new SimpleEntry<>(x.getName(), x.getType()))
.collect(Collectors.toMap(
Function.identity(),
entry -> {
try {
return new SimpleEntry<>(
LOOKUP.findGetter(cls, entry.getKey(), entry.getValue()),
LOOKUP.findSetter(cls, entry.getKey(), entry.getValue()));
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
));
}
With that information you can provide a public method for users to call, So you need to provide the actual instance of your DTO, the DTO class, the Class of the fields you want to "default to", the equality to check against and the actual defaultValue.
public static <T, R> T defaulter(T initial,
Class<T> dtoClass,
Class<R> fieldType,
R equality,
R defaultValue) throws Throwable {
Set<Entry<MethodHandle, MethodHandle>> all =
ALL_KNOWN.get(dtoClass)
.entrySet()
.stream()
.filter(x -> x.getKey().getValue() == fieldType)
.map(Entry::getValue)
.collect(Collectors.toSet());
for (Entry<MethodHandle, MethodHandle> getterAndSetter : all) {
R whatWeGot = (R) getterAndSetter.getKey().invoke(initial);
if (Objects.equals(whatWeGot, equality)) {
getterAndSetter.getValue().invoke(initial, defaultValue);
}
}
return initial;
}
And this is how your callers can call it:
public static void main(String[] args) throws Throwable {
EmployeeDto employeeDto = new EmployeeDto()
.setFirstname("string")
.setLastname("string");
EmployeeDto withDefaults = defaulter(employeeDto, EmployeeDto.class, String.class, "string", "defaultValue");
System.out.println(withDefaults);
}

Calling setter from Top Level of Modelclass hierarchy

Imagine you have a model class hierarchy, like
public class TopLevel {
private MiddleLevel middleLevel = null;
public TopLevel() {
middleLevel = new MiddleLevel();
}
public MiddleLevel getMiddleLevel() { return middleLevel; }
}
public class MiddleLevel {
private LowLevel lowLevel = null;
public MiddleLevel () {
lowLevel = new LowLevel();
}
public LowLevel getLowLevel() { return lowLevel; }
}
public class LowLevel {
private Value value = null;
public LowLevel() {
value = new Value();
}
public Value getValue() { return value; }
}
public class Value {
private String stringValue = "ItsAValue";
private String doubleValue = 1.0d;
private String integerValue = 4321;
public void setStringValue(String value) {
stringValue = value;
}
}
And of course further classes with different attributes. E.g. this hierarchy was created and instantiated by Jaxb.
Now, i want to set a value in the Value-class. Of course i can execute something like:
TopLevel topLevel = new TopLevel();
topLevel.getMiddleLevel().getLowLevel().getValue().setStringValue("NewValue");
Is there a way to simplify or to generalize this, e.g. to be able to call the "path" through all these class-objects to set a value deep inside? Here is some pseudocode, what i mean:
public class Anotherclass {
public static void main(String[] args) {
TopLevel topLevel = new TopLevel();
setStringValueByPath("topLevel/middleLevel/lowLevel/value/stringValue", "newValue");
setDoubleValueByPath("topLevel/middleLevel/lowLevel/value/doubleValue", 5.0d);
setIntegerValueByPath("topLevel/middleLevel/lowLevel/value/integerValue", 1234);
}
}
Thanks a lot
Alex
Ok, if anyone is interested, i think i found a solution, that i was looking for:
A recursive approach based on Java.reflection :
public class ReflectionSetter {
private static List<Field> getFields(Object object) {
List<Field> fields = new ArrayList<>();
fields.addAll(Arrays.asList(object.getClass().getDeclaredFields()));
return fields;
}
private static Field hasField(Object object, String fieldName) {
for (Field f : getFields(object)) {
if (f.getName().equalsIgnoreCase(fieldName)) return f;
}
return null;
}
public static void setValue(Object object, String path, String newValue) throws IllegalArgumentException, IllegalAccessException {
if (path.contains("/")) {
int pos = path.indexOf('/');
String first = path.substring(0, pos);
String rest = path.substring(pos+1);
Field f = ReflectionSetter.hasField(object, first);
if (null == f) throw new IllegalArgumentException("Path not found: " + path);
f.setAccessible(true);
Object obj = f.get(object);
setValue(obj, rest, newValue);
} else {
Field f = ReflectionSetter.hasField(object, path);
if (f == null) throw new IllegalArgumentException("Field not found: " + path);
// if found -> set value
f.setAccessible(true);
f.set(object, newValue);
}
}
}
Now, you can set a value via a path. Usage:
TopLevel topLevel = new TopLevel();
ReflectionSetter.setValue(topLevel, "middleLevel/lowLevel/value/myValue", "NewValue");
An ideal efficient way to do this and by focussing more on reducing code complexity and at the same time improving code readability, you should look at design patterns, may be something like visitor pattern.
One of the most common use cases of visitor pattern is and as a result of separating algorithm and the data structure, comes with ability to add new operations to existing object structures without modifying said structures.
Moving on to a phase where "No, I want to look at string based approached as pointed in question". Apache commons library provides something called JxPath.
Unsure if you tried looking at JxPath ref.apache.jx.path
It offers simple interpreter of an expression language called XPath. JXPath applies XPath expressions to graphs of objects of all kinds
Picking an example from your question :
TopLevel topLevel = new TopLevel();
JXPathContext context = JXPathContext.newContext(topLevel);
context.setValue("middleLevel/lowLevel/value/stringValue", "newStringValue");

Getting enum name based on value java in run time

I need to get the enum name based on value. I am given with enum class and value and need to pick the corresponding name during run time .
I have a class called Information as below.
class Information {
private String value;
private String type;
private String cValue;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getcValue() {
return cValue;
}
public void setcValue(String cValue) {
this.cValue = cValue;
}
public static void main(String args[]) {
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
}
}
class SignalEnum {
RED("1"), GREEN("2"), ORANGE("3");
private String sign;
SignalEnum(String pattern) {
this.sign = pattern;
}
}
class MobileEnum {
SAMSUNG("1"), NOKIA("2"), APPLE("3");
private String mobile;
MobileEnum(String mobile) {
this.mobile = mobile;
}
}
In run time i will come to know the enum name using the attribute type from the Information class and also i am getting the value. I need to figure out the corresponding enum to set the value for cValue attribute of Information class.
Just for example i have provided two enums like SignalEnum and MobileEnum but in my actual case i will get one among 100 enum types. Hence i dont want to check type cast. I am looking for some solution using reflection to se the cValue.
Here is a simple resolver for any enum class.
Since reflection operations are expensive, it's better to prepare all required data once and then just query for it.
class EnumResolver {
private Map<String, Enum> map = new ConcurrentHashMap<>();
public EnumResolver(String className) {
try {
Class enumClass = Class.forName(className);
// look for backing property field, e.g. "sign" in SignalEnum
Field accessor = Arrays.stream(enumClass.getDeclaredFields())
.filter(f -> f.getType().equals(String.class))
.findFirst()
.orElseThrow(() -> new NoSuchFieldException("Not found field to access enum backing value"));
accessor.setAccessible(true);
// populate map with pairs like ["1" => SignalEnum.RED, "2" => SignalEnum.GREEN, etc]
for (Enum e : getEnumValues(enumClass)) {
map.put((String) accessor.get(e), e);
}
accessor.setAccessible(false);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public Enum resolve(String backingValue) {
return map.get(backingValue);
}
private <E extends Enum> E[] getEnumValues(Class<E> enumClass) throws ReflectiveOperationException {
Field f = enumClass.getDeclaredField("$VALUES");
f.setAccessible(true);
Object o = f.get(null);
f.setAccessible(false);
return (E[]) o;
}
}
And here is simple JUnit test
public class EnumResolverTest {
#Test
public void testSignalEnum() {
EnumResolver signalResolver = new EnumResolver("com.abc.SignalEnum");
assertEquals(SignalEnum.RED, signalResolver.resolve("1"));
assertEquals(SignalEnum.GREEN, signalResolver.resolve("2"));
assertEquals(SignalEnum.ORANGE, signalResolver.resolve("3"));
}
#Test
public void testMobileEnum() {
EnumResolver mobileResolver = new EnumResolver("com.abc.MobileEnum");
assertEquals(MobileEnum.SAMSUNG, mobileResolver.resolve("1"));
assertEquals(MobileEnum.NOKIA, mobileResolver.resolve("2"));
assertEquals(MobileEnum.APPLE, mobileResolver.resolve("3"));
}
}
And again for performance sake you can also instantiate these various resolvers once and put them into a separate Map
Map<String, EnumResolver> resolverMap = new ConcurrentHashMap<>();
resolverMap.put("com.abc.MobileEnum", new EnumResolver("com.abc.MobileEnum"));
resolverMap.put("com.abc.SignalEnum", new EnumResolver("com.abc.SignalEnum"));
// etc
Information inf = new Information();
inf.setType("com.abc.SignalsEnum");
inf.setValue("1");
SignalEnum red = (SignalEnum) resolverMap.get(inf.getType()).resolve(inf.getValue());

Multiple #QueryParam keys for a single value in Jersey

Is it possible to allow multiple #QueryParam keys for a single object/variable in Jersey?
Actual:
#POST
public Something getThings(#QueryParam("customer-number") Integer n) {
...
}
so, if I add ?customer-number=3 after the URL it works.
Expected:
I want to get the behavior above if I add any of the following values:
?customer-number=3
?customerNumber=3
?customerNo=3
Obs:
The QueryParam annotation looks like:
...
public #interface QueryParam {
String value();
}
so, it cannot accept multiple String values (like #Produces).
The approach below allows the user to use multiple keys having the same meaning at the same time (and I want to have an "OR" condition between them):
#POST
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) {
...
}
Something like this doesn't work:
#POST
public Something getThings(#QueryParam("customer-number|customerNumber|customerNo") Integer n) {
...
}
How can I do this?
Details:
Jersey 2.22.1
Java 8
To be honest: this is not how webservices are supposed to be designed. You lay down a strict contract that both client and server follow; you define one parameter and that's it.
But of course it would be a perfect world where you have the freedom to dictate what is going to happen. So if you must allow three parameters in, then you'll have to make that the contract. This is one way following approach #2 which I have to provide without being able to test it for goofs:
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) throws YourFailureException {
Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3);
// things with stuff
}
private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException {
Integer value = null;
for(Integer choice : params){
if(choice != null){
if(value != null){
// this means there are at least two query parameters passed with a value
throw new YourFailureException("Ambiguous " + label + " parameters");
}
value = choice;
}
}
if(value == null){
throw new YourFailureException("Missing " + label + " parameter");
}
return value;
}
So basically reject any call that does not pass specifically one of the parameters, and let an exception mapper translate the exception you throw into a HTTP response code in the 4xx range of course.
(I made the getNonNullValue() method static is it strikes me as a reusable utility function).
Maybe the simplest and easiest way would be to use a custom #BeanParam:
First define the custom bean merging all the query parameters as:
class MergedIntegerValue {
private final Integer value;
public MergedIntegerValue(
#QueryParam("n1") Integer n1,
#QueryParam("n2") Integer n2,
#QueryParam("n3") Integer n3) {
this.value = n1 != null ? n1
: n2 != null ? n2
: n3 != null ? n3
: null;
// Throw an exception if value == null ?
}
public Integer getValue() {
return value;
}
}
and then use it with #BeanParam in your resource method:
public Something getThings(
#BeanParam MergedIntegerValue n) {
// Use n.getValue() ...
}
Reference: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403
You can create a custom annotation. I won't go in too much about how to do it, you can see this post, or this post. Basically it relies on a different infrastructure than the usual dependency injection with Jersey. You can see this package from the Jersey project. This is where all the injection providers live that handle the #XxxParam injections. If you examine the source code, you will see the the implementations are fairly the same. The two links I provided above follow the same pattern, as well as the code below.
What I did was created a custom annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface VaryingParam {
String value();
#SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
#Override
public String value() {
return this.value;
}
}
}
It may seem odd that I have a factory to create it, but this was required for the implementation of the below code, where I split the value of the String, and end up creating a new annotation instance for each split value.
Here is the ValueFactoryProvider (which, if you've read either of the above articles, you will see that is required for custom method parameter injection). It a large class, only because I put all the required classes into a single class, following the pattern you see in the Jersey project.
public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
#Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
You will need to register this class' Binder (bottom of class) with Jersey to use it.
What differentiates this class from Jersey QueryParamValueFactoryProvider is that instead of just processing a single String value of the annotation, it splits the value, and tries to extract the values from the query param map. The first value found will be returned. If the parameter is a List or Set, it just continues to keep looking up all the options, and adding them to the list.
For the most part this keeps all the functionality you would expect from an #XxxParam annotation. The only thing that was difficult to implement (so I left out supporting this use case), is multiple parameters, e.g.
#GET
#Path("multiple")
public String getMultipleVariants(#VaryingParam("param-1|param-2|param-3") String value1,
#VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
I actually don't think it should be that hard to implement, if you really need it, it's just a matter of creating a new MultivaluedMap, removing a value if it is found. This would be implemented in the provide() method of the VaryingParamFactory above. If you need this use case, you could just use a List or Set instead.
See this GitHub Gist (it's rather long) for a complete test case, using Jersey Test Framework. You can see all the use cases I tested in the QueryTestResource, and where I register the Binder with the ResourceConfig in the test configure() method.

Categories