Can't initialise a class instance from JsonNode with reflection - java

As I mentioned in the title. Here is my code below
import java.lang.reflect.Field;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
public class ReflectClass {
public Double getDoubleKey() {
return doubleKey;
}
public void setDoubleKey(Double doubleKey) {
this.doubleKey = doubleKey;
}
public Long getLongKey() {
return longKey;
}
public void setLongKey(Long longKey) {
this.longKey = longKey;
}
private Double doubleKey;
private Long longKey;
public ReflectClass(JsonNode node) throws IllegalAccessException {
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType().equals(Double.class)) {
field.setDouble(this, node.get(field.getName()).asDouble());
} else if (field.getType().equals(Long.class)) {
field.setLong(this, node.get(field.getName()).asLong());
}
}
}
public static void main(String[] args) throws IllegalAccessException {
ObjectNode jNode = new ObjectMapper().createObjectNode();
jNode.put("doubleKey", 1.0);
jNode.put("longKey", 11L);
new ReflectClass(jNode);
}
}
However when I run the code above, the error like below pops up.
java.lang.IllegalArgumentException: Can not set java.lang.Double field models.weibo.ReflectClass.doubleKey to (double)1.0
I can certainly initialise the class instance by the traditional way like this.doubleKey = node.get("doubleKey").asDouble(). However if there are too many fields in this class, I'd prefer to initialise it through a loop.

Related

Jackson, deserialize property based on another property (dependent property)

Using Jackson, is there a way to deserialize a proprty that depends on the value of another property?
if i have this json {"foo":"a","bar":"b"} i'd like to deserialize it to the Test class below as Test [foo=a, bar=b_a], where bar is the value of the json property "bar" and the value of the property "foo".
Of course this is a trivial example, the real deal would be to deserialize a datamodel entity: {"line":"C12", "machine": {"line":"C12", "code":"A"}} machine.line and line are always the same, and i'd like to express it like this: {"line":"C12", "machine": "A"}
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public abstract class Main{
private static class Test {
#JsonProperty
private String foo;
#JsonProperty
#JsonDeserialize(using = CustomDeserializer.class)
private String bar;
// ...other fields to be deserialized with default behaviour
private Test() {
}
public Test(String a, String bar) {
this.foo = a;
this.bar = bar;
}
#Override
public String toString() {
return "Test [foo=" + foo + ", bar=" + bar + "]";
}
}
private static class CustomDeserializer extends StdDeserializer<String> {
protected CustomDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String foo = //how to get foo property?
String value = p.getValueAsString();
if (!foo.isEmpty()) {
return value + "_" + foo;
} else {
return value;
}
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
System.out.println(foo2); // Test [foo=a, bar=b_a]
}
}
One way to solve your problem is specify a custom deserializer that involves your Test class instead of your string field because the deserialization of your property is based on the value of another property:
public class CustomDeserializer extends JsonDeserializer<Test> {}
#JsonDeserialize(using = CustomDeserializer.class)
public class Test {}
Then you can deserialize your object reading the JsonNode tree built from your input string:
public class CustomDeserializer extends JsonDeserializer<Test> {
#Override
public Test deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String foo = node.get("foo").asText();
String bar = node.get("bar").asText();
if (!foo.isEmpty()) {
bar = (bar + '_' + foo);
}
return new Test(foo, bar);
}
}
//your example
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Test foo2 = mapper.readValue("{\"foo\":\"a\",\"bar\":\"b\"}", Test.class);
System.out.println(foo2); // Test [foo=a, bar=b_a]
}
}
I got a similar problem today and I wanted to share my solution. So instead of using a #JsonDeserialize, I use a #JsonCreator on the parent object with a package private constructor to accept the "raw" properties and then I can process this data and return better objects.
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
class Scratch {
public static void main(String[] args) throws Exception{
final var testData = "{\"foo\":\"a\",\"bar\":\"b\"}";
final var mapper = new ObjectMapper();
final var testObj = mapper.readValue(testData, Test.class);
System.out.println(testObj); // Test[foo=a, bar=a_b]
}
record Test (
String foo,
String bar
){
#JsonCreator Test(
#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar,
#JsonProperty("_dummy") String _dummy // extra param for the constructor overloading
) {
this(foo, deserializeBar(foo, bar));
}
private static String deserializeBar(String foo, String bar) {
if (foo == null || foo.isEmpty()) {
return bar;
}
return "%s_%s".formatted(foo, bar);
}
}
}
In the end, I've resorted using BeanDeserializerModifier
Please notice that the following code is not fully functioning because it relies on code I'm not allowed to share, but it should suffice to get the idea.
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
public class JsonDelegateDeserializerModule extends SimpleModule {
// !! must be registered as guice factory
public interface JsonDelegateDeserializerFactory {
JsonDelegateDeserializerModule create(String packagePath);
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonDelegateDeserializer {
public Class<? extends StdDeserializer<?>> deserializer();
public Class<?> forType();
}
protected interface JsonDeserializerFactory {
// non metto nessun generic in TagHandler o guice non riesce piu a creare la
// factory!
#SuppressWarnings("rawtypes")
public JsonDeserializer create(JsonDeserializer baseDeserializer);
}
private static final Logger LOGGER = LoggerFactory.getLogger(JsonDelegateDeserializerModule.class);
#Inject
private FactoryInjector injector;
private final String packagePath;
#AssistedInject
protected JsonDelegateDeserializerModule(#Assisted String packagePath) {
super();
this.packagePath = packagePath;
}
#Override
public String getModuleName() {
return JsonDelegateDeserializerModule.class.getSimpleName() + "[" + packagePath + "]";
}
#Override
public Object getTypeId() {
return JsonDelegateDeserializerModule.class.getSimpleName() + "[" + packagePath + "]";
}
#Override
public void setupModule(SetupContext context) {
Reflections reflectios = new Reflections(packagePath, new SubTypesScanner(), new TypeAnnotationsScanner());
Map<Class<?>, JsonDeserializerFactory> classToDeserializerFactory = new HashMap<>();
Set<Class<?>> classesWithModifier = reflectios.getTypesAnnotatedWith(JsonDelegateDeserializer.class);
for (Class<?> classWithModifier : classesWithModifier) {
JsonDelegateDeserializer annotation = classWithModifier.getAnnotation(JsonDelegateDeserializer.class);
if (annotation != null) {
Class<? extends StdDeserializer<?>> deserializerType = annotation.deserializer();
Class<?> forType = annotation.forType();
try {
JsonDeserializerFactory factory = injector.getFactory(JsonDeserializerFactory.class,
deserializerType);
classToDeserializerFactory.put(forType, factory);
} catch (Exception e) {
LOGGER.error("Exception was thown while creating deserializer {} for type {}:", deserializerType,
forType, e);
throw new RuntimeException(e);
}
}
}
if (!classToDeserializerFactory.isEmpty()) {
setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
List<Class<?>> possibleTypesList = new LinkedList<>();
if (deserializer instanceof BeanDeserializer) {
for (Entry<Class<?>, JsonDeserializerFactory> entry : classToDeserializerFactory.entrySet()) {
Class<?> type = entry.getKey();
if (type.isAssignableFrom(deserializer.handledType())) {
possibleTypesList.add(type);
}
}
if (possibleTypesList.size() > 1) {
possibleTypesList.sort(new Comparator<Class<?>>() {
#Override
public int compare(Class<?> o1, Class<?> o2) {
if (o1.isAssignableFrom(o2)) {
return 1;
} else {
return -1;
}
}
});
}
Class<?> type = Utils.first(possibleTypesList);
if (type == null) {
return super.modifyDeserializer(config, beanDesc, deserializer);
} else {
JsonDeserializerFactory factory = classToDeserializerFactory.get(type);
JsonDeserializer<?> modifiedDeserializer = factory.create(deserializer);
return super.modifyDeserializer(config, beanDesc, modifiedDeserializer);
}
} else {
// รจ gia stato impostato un deserializzatore piu specifico, non imposato questo
return super.modifyDeserializer(config, beanDesc, deserializer);
}
}
});
}
super.setupModule(context);
}
}
then you can simply annotate the Mixin to add the custom deserializer
#JsonDelegateDeserializer(deserializer = LoadLineDeserializer.class, forType = Line.class)
public interface LineMixIn {
public static class LoadLineDeserializer extends DelegatingDeserializer {
#AssistedInject
public LoadLineDeserializer(#Assisted JsonDeserializer baseDeserializer, LineService lineService) {
super(baseDeserializer);
}
// ...
}
// ...
}

ConcurrentSkipListMap firstKey() throws NoSuchElementException even though it contains data

I wrote a small application that receives data from a web socket, which I store in static ConcurrentSkipListMap.
The application initially creates a new thread where it runs infinitely while loop calling ConcurrentSkipListMap.firstKey(). After a while, this call throws a NoSuchElementException, even though the ConcurrentSkipListMap contains data.
break point in catch block
Example of my application:
I have cacher class that contains websocket implementation and NavigableMap init:
package solvethat.net.triobot.Example;
import com.binance.api.client.BinanceApiCallback;
import com.binance.api.client.BinanceApiClientFactory;
import com.binance.api.client.domain.event.DepthEvent;
import com.binance.api.client.domain.market.OrderBook;
import com.binance.api.client.domain.market.OrderBookEntry;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
public class AskCacher {
private long updateId;
private final BinanceApiClientFactory factory;
public AskCacher() {
factory = BinanceApiClientFactory.newInstance();
initAsks();
runWebsocket();
}
/**
* Init data getting order book snapshot
*/
private void initAsks() {
try {
OrderBook orderBook = factory.newRestClient().getOrderBook("btcusdt".toUpperCase(), 10);
updateId = orderBook.getLastUpdateId();
NavigableMap<Double, Double> asks = new ConcurrentSkipListMap<>(Comparator.naturalOrder());
for (OrderBookEntry ask : orderBook.getAsks()) {
asks.put(Double.parseDouble(ask.getPrice()), Double.parseDouble(ask.getQty()));
}
StaticData.ask = asks;
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
private void runWebsocket() {
factory.newWebSocketClient().onDepthEvent("btcusdt", new BinanceApiCallback<>() {
/**
* Set ask price and call analysis method
*/
#Override
public void onResponse(DepthEvent depthEvent) {
if (depthEvent.getFinalUpdateId() > updateId) {
updateId = depthEvent.getFinalUpdateId();
updateOrderBook(depthEvent.getAsks());
}
}
/**
* Just print err message
*/
#Override
public void onFailure(final Throwable cause) {
System.err.println(cause.getMessage());
}
});
}
/**
* Updates an order book (asks) with a delta received from the server.
* Whenever the qty specified is ZERO, it means the price should was removed from the order book.
*/
private void updateOrderBook(List<OrderBookEntry> orderBookDeltas) {
for (OrderBookEntry orderBookDelta : orderBookDeltas) {
Double price = Double.parseDouble(orderBookDelta.getPrice());
BigDecimal qty = new BigDecimal(orderBookDelta.getQty());
if (qty.compareTo(BigDecimal.ZERO) == 0) {
// qty=0 means remove this level
StaticData.ask.remove(price);
} else {
StaticData.ask.put(price, Double.parseDouble(orderBookDelta.getQty()));
}
}
// Print best ask to see if cacher is alive
System.out.println("btc-usdt best ask: " + StaticData.ask.firstKey());
// Edit map length
if (StaticData.ask.size() > 10) {
StaticData.ask.tailMap((Double) StaticData.ask.keySet().toArray()[10], true).clear();
}
}}
Then infinite loop:
package solvethat.net.triobot.Example;
public class InfiniteLoop {
public void loopProcess() {
Analyzer analyzer = new Analyzer();
while (true) {
analyzer.analyze(StaticData.ask.firstEntry());
}
}}
And analyzer class:
package solvethat.net.triobot.Example;
import java.util.Map;
public class Analyzer {
public void analyze(Map.Entry<Double, Double> entry) {
StaticData.AnalyzeObject analyzeObject = new StaticData.AnalyzeObject();
analyzeObject.setBestAsk(entry.getKey());
if (analyzeObject.getBestAsk() > 50000) {
System.out.println("It is a good price!!");
}
}
}
Static data model:
package solvethat.net.triobot.Example;
import java.util.NavigableMap;
public class StaticData {
public static NavigableMap<Double, Double> ask;
public static class AnalyzeObject {
double bestAsk;
public double getBestAsk() {
return bestAsk;
}
public void setBestAsk(double bestAsk) {
this.bestAsk = bestAsk;
}
}
}
Main class for example run:
package solvethat.net.triobot.Example;
public class Main {
public static void main(String[] arguments) {
new AskCacher();
new Thread(new InfiniteLoop()::loopProcess).start();
}
}
The example only shows how the application is composed, but I was not able to use it to raise an error but I opened my repo as public:
https://github.com/Sick-E/TrioBot
Can anyone please help me?
Thank you.
Tomas
You can replace your code with something like that (no exception handling is required)
Optional.ofNullable(trio.getThirdPair().getBids().firstEntry())
.map(Map.Entry::getKey)
.ifPresent(trio.getTrioAnalysis()::setBidThird);

Getting NoSuchMethodException from getMethod using Java reflection

I'm testing with Java reflection and trying to apply overloaded method to parameters according to their type..
However, I have NoSuchMethodException even though the method I tried to get is public. This exception still appears when I used getDeclaredMethod.
Here's the main program
public class Test {
public static void main(Object... inputs){
InputManipulation test = new InputManipulation();
for (Object input: inputs){
Class ownerClass = test.getClass();
Class inputClass = input.getClass();
Method method = ownerClass.getDeclaredMethod("add", inputClass);
method.invoke(test, "Testing reflection");
}
}
}
And here's the self-defined InputManipulation class
public class InputManipulation {
Integer total;
public InputManipulation(){this.total = 0;}
public void add(Integer num){this.total += num;}
public void add(String str){System.out.println(str);}
}
Thanks in advance!
I now changed the Test class as follows.. but the problem still exists.
public class Test {
public static void main(String[] args){
Test testExample = new Test();
testExample.testMethod("String1", 1, "String2");
}
public void testMethod(Object... inputs){
InputManipulation test = new InputManipulation();
for (Object input: inputs){
Class ownerClass = test.getClass();
Class inputClass = input.getClass();
Method method = ownerClass.getDeclaredMethod("add", inputClass);
method.invoke(test, "Testing reflection");
}
}
}
I also tried putting the inputClass into an array of Class, as suggested by another post, but it didn't help..
There seems to be a few issues with the initial code you provided and as others have suggested using an IDE would have pointed some of the issues out pretty quickly. However, I have taken your update and fixed the code to call the proper method in the loop you provided of input types.
First change your InputManipulation class like so:
public class InputManipulation {
Integer total;
public InputManipulation() {
this.total = 0;
}
public void add(Integer num) {
this.total += num;
System.out.println(this.total);
}
public void add(String str) {
System.out.println(str);
}
}
Now alter your Test class like so:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Test testExample = new Test();
testExample.testMethod("String1", 1, "String2");
}
public void testMethod(Object... inputs){
InputManipulation test = new InputManipulation();
for (Object input: inputs){
Class<? extends Object> ownerClass = test.getClass();
Class<? extends Object> inputClass = input.getClass();
//Method method; //not needed
try {
ownerClass.getDeclaredMethod("add", inputClass).invoke(test, input);
} catch (NoSuchMethodException | SecurityException |
IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
I used these readings to help guide my answer, but altered the way I invoked the method:
http://tutorials.jenkov.com/java-reflection/methods.html
public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Test testExample = new Test();
testExample.testMethod("String1", 1, "String2");
}
public void testMethod(Object... inputs) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
InputManipulation test = new InputManipulation();
for (Object input: inputs){
Class ownerClass = test.getClass();
Class inputClass = input.getClass();
Method method = ownerClass.getDeclaredMethod("add", inputClass);
method.invoke(test, input);
}
}
}
Your problem was caused by this method.invoke(test, "Testing reflection");
You iterate through 2 types of arguments and depends of this argument you invoke method 'add'. When you tried to invoke method with argument Integer you pass to method String parameter that causes error

Jackson serialization to report list of fields that could not be serialized

I am wrapping legacy code with some REST/jackson capabilities. In particular let's say I have an interface called LegacyObject
interface LegacyObject {
Integer getAge(); //could throw UnsupportedOperationException
String getDesc();
String getName(); //May throw RuntimeException
//about 200+ other methods.
}
The implementation is a legacy class and assume cannot be changed. My REST service has an endpoint which converts LegacyObject to JSON. The only problem being that this conversion fails fully whenever one of the getters throws an exception. What I need is a json like the below (assuming getAge(), getDesc() worked okay but getName() threw runtimeexception)
{"age": 40, "desc": "some description", "unsupportedFields": ["name"]}
Basically a way to capture all fields that failed serialization and then report at the end.
An interceptor like thing might work for me but if anyone has some code examples that would be great!
Since there are 200+ methods in the interface, below a solution with Proxies.
This code does not guarantee that the "getUnsupportedFields" method is called last (and thus still some exceptions may occur after)
public interface LegacyObject {
Integer getAge(); //could throw UnsupportedOperationException
String getDesc();
String getName(); //May throw RuntimeException
//about 200+ other methods.
}
import java.util.List;
public interface ExtendedLegacyObject extends LegacyObject {
List<String> getUnsupportedFields();
}
public class ExceptionLegacyObject implements LegacyObject {
#Override
public Integer getAge() {
return 40;
}
#Override
public String getDesc() {
return "some description";
}
#Override
public String getName() {
throw new RuntimeException();
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
public class LegacyObjectHandler implements InvocationHandler {
private static final Logger LOG = Logger.getLogger(LegacyObjectHandler.class);
private final List<String> unsupportedFields = new ArrayList<>();
private final LegacyObject legacyObject;
public LegacyObjectHandler(LegacyObject legacyObject) {
this.legacyObject = legacyObject;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getUnsupportedFields".equals(method.getName())) {
return unsupportedFields;
} else {
try {
return method.invoke(legacyObject, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
LOG.error(cause.getMessage(), cause);
unsupportedFields.add(method.getName());
Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
if (returnType.isAssignableFrom(boolean.class)) {
return false;
} else if (returnType.isAssignableFrom(byte.class)) {
return (byte) 0;
} else if (returnType.isAssignableFrom(short.class)) {
return (short) 0;
} else if (returnType.isAssignableFrom(int.class)) {
return 0;
} else if (returnType.isAssignableFrom(long.class)) {
return 0L;
} else if (returnType.isAssignableFrom(float.class)) {
return 0F;
} else if (returnType.isAssignableFrom(double.class)) {
return 0D;
} else if (returnType.isAssignableFrom(char.class)) {
return (char) 0;
} else {
return null;
}
} else {
return null;
}
}
}
}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Proxy;
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ExceptionLegacyObject exceptionLegacyObject = new ExceptionLegacyObject();
ExtendedLegacyObject proxy = (ExtendedLegacyObject) Proxy.newProxyInstance(
LegacyObject.class.getClassLoader(),
new Class[] { ExtendedLegacyObject.class },
new LegacyObjectHandler(exceptionLegacyObject)
);
System.out.println(mapper.writeValueAsString(proxy));
}
}
I used a variation of what #toongeorges suggested above. Here is a utility class which will do a "exception safe" convert to JSON. There will be an extra element in the returned JSON called "exceptionMessages" which contains the properties that failed json serialisation (or the method name if it is NOT a Java bean property). This can be changed to return a Pair of JsonNode one for the object and one for exceptionMessages if that style suits you better
import static java.util.stream.Collectors.toMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.lang3.exception.ExceptionUtils;
public abstract class JsonUtils {
private static ObjectMapper mapper = new ObjectMapper();
/**
* This is only useful in the context of converting a object whose methods could throw exceptions
* into JSON. By default a "getName" method that throws an exception will fail the whole
* serialization however with this method such exceptions will be swallowed and there will be a
* "exceptionMessages" element in the returned JSON which contains all failures
*
* To be used only when working with legacy code.
*/
#SuppressWarnings("unchecked")
public static <U> ObjectNode exceptionSafeWrite(Class<U> sourceClazz, U obj, boolean prettyPrint) {
GuardedInvocationHandler handler = new GuardedInvocationHandler(obj);
U proxiedObject = (U) Proxy
.newProxyInstance(sourceClazz.getClassLoader(), new Class<?>[]{sourceClazz}, handler);
ObjectNode originalNode = mapper.convertValue(proxiedObject, ObjectNode.class);
ObjectNode exceptionMessages = mapper.convertValue(handler.getExceptionMessagesForJson(), ObjectNode.class);
originalNode.put("exceptionMessages", exceptionMessages);
return originalNode;
}
private static class GuardedInvocationHandler implements InvocationHandler {
private final Object target;
private Map<Method, Throwable> exceptionMap = new LinkedHashMap<>();
private Map<Method, String> methodToPropertyNameMap;
private GuardedInvocationHandler(Object target) {
this.target = target;
this.methodToPropertyNameMap = methodToPropertyNameMap(target.getClass());
}
private static Map<Method, String> methodToPropertyNameMap(Class<?> clazz) {
try {
return Stream.of(Introspector.getBeanInfo(clazz).getPropertyDescriptors())
.collect(toMap(d -> d.getReadMethod(), d -> d.getName()));
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
exceptionMap.put(method, e.getTargetException());
return null;
} catch (Exception e) {
exceptionMap.put(method, e);
return null;
}
}
public Map<String, String> getExceptionMessagesForJson() {
return exceptionMap.entrySet().stream().collect(
toMap(e -> methodToPropertyNameMap.getOrDefault(e.getKey(), e.getKey().getName()),
e -> ExceptionUtils.getMessage(e.getValue())));
}
}
}

How can we write an annotation

I am very new to usage of annotation.
can anyone please tell me how can we declare an annotation and also call all the methods / variables that are declared with that annotation
am using java to implement this annotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
public #interface isAnnotatedVariable {
String varName();
}
and used the annotation in
public class Example {
#isAnnotatedVariable(varName = "S")
public String var;
#isAnnotatedVariable(varName = "S")
public String var1;
}
and tried to get the variable names using
public class BuildStepClassDetector {
public static void main(String[] args) throws IOException {
BuildStepClassDetector build = new BuildStepClassDetector();
final Logger4J logger = new Logger4J(build.getClass().getName());
final HashMap<String, Class<?>> isAnnotatedVariables = new HashMap<String, Class<?>>();
final TypeReporter reporter = new TypeReporter() {
#SuppressWarnings("unchecked")
#Override
public Class<? extends Annotation>[] annotations() {
return new Class[] { isAnnotatedVariable.class };
}
#SuppressWarnings("unchecked")
#Override
public void reportTypeAnnotation(Class<? extends Annotation> arg0, String arg1) {
Class<? extends isAnnotatedVariable> isAnnotatedVariableClass;
try {
isAnnotatedVariableClass = (Class<? extends isAnnotatedVariable>) Class.forName(arg1);
isAnnotatedVariables.put(
isAnnotatedVariableClass.getAnnotation(isAnnotatedVariable.class).varName(),
isAnnotatedVariableClass);
} catch (ClassNotFoundException e) {
logger.getStackTraceString(e);
}
}
};
final AnnotationDetector cf = new AnnotationDetector(reporter);
cf.detect();
System.out.println(isAnnotatedVariables.keySet());
}
}
Here is a simple example for declaring annotation and retrieving a annotated field using Reflection.
package asif.hossain;
import java.lang.annotation.*;
import java.lang.reflect.Field;
/**
*
* Created by sadasidha on 21-Aug-14.
*/
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface MyAnnotation {
public String value();
}
class TestClass
{
#MyAnnotation("This is a name field")
public String name;
}
public class Main {
public static void main(String ... args) throws IllegalAccessException {
TestClass testObject = new TestClass();
Field[] fields = testObject.getClass().getFields();
for (Field field : fields)
{
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation)
{
System.out.println(field.getName());
// get field value
String value = (String)field.get(testObject);
System.out.println("Field Value = "+ value);
//Set field value
field.set(testObject,"Your Name");
System.out.println(testObject.name);
}
}
}
}
You can follow this tutorial http://tutorials.jenkov.com/java-reflection/index.html to learn more about annotation and reflection.

Categories

Resources