I can generate annotation class using JCodeModel exept one thing.
I can't understand how to add this to annotation:
#Target(value={METHOD,TYPE,CONSTRUCTOR})
How to set array of defined values as value of value method?
I can use param() method of JAnnotationUse class for simple annotations, but how to set array as value I can't find.
Make something similar to this:
JAnnotationUse annotation = (JClass/JMethod/JFieldVar).annotate(Class<? extends java.lang.annotation.Annotation> MyAnnotation.class);
// Check if the value of the parameter of the annotation is an Array.
if (paramAnnotationValue.getClass().isArray()) {
Object[] paramAnnotationValueArray = (Object[]) paramAnnotationValue;
JAnnotationArrayMember annotationArrayMember = annotation.paramArray("parameter");
for (Object paramValue : paramAnnotationValueArray) {
annotationArrayMember.param((String/Boolean/Class/JType) paramValue);
}
}
// No Array is normal.
else {
(...)
}
Something like this is generated:
#MyAnnotation(parameter = {
"value_a",
"value_b"
})
Related
How can I inject a map into an object using only Core Java?
I have a map with 4 key-value(String, Object) pairs and a class with 3 fields, I want to invoke the setter method based on the key name and set them.
{
"variableA": "A",
"variableB": true,
"variableC": 1,
"variableD": "DONT USE"
}
public Class Example {
public void setVaraibleA(String variableA);
public void setVaraibleB(Boolean variableB);
public void setVaraibleC(Integer variableC);
}
Example example = new Example();
// Do something to map it
assert(example.getVariableA.equals("A"));
assert(example.getVariableB.equals(true));
assert(example.getVariableC.equals(1));
you can use Java Reflection to get a method (given its name) and invoke it with a given parameter.
Example example = new Example();
Method method = Example.class.getMethod("setVariableA", String.class);
method.invoke(example, "parameter-value1");
Alternatively to #BeppeC's answer, if you can't easily determine the type of the object that you're injecting at runtime, and assuming that you don't have duplicate property names, I would use Class's getMethods() method and Method's getName() method.
Basically, I would write some code like the following:
Method[] exampleMethods = Example.class.getMethods();
Map<String, Method> setterMethodsByPropertyName = new HashMap<>(exampleMethods.length);
for (Method exampleMethod : exampleMethods) {
String methodName = exampleMethod.getName();
if (!methodName.startsWith("set")) {
continue;
}
// substring starting right after "set"
String variableName = methodName.substring(3);
// use lowercase here because:
// 1. JSON property starts with lower case but setter name after "set" starts with upper case
// 2. property names should all be different so no name conflict (assumption)
String lcVariableNmae = variableName.toLowerCase();
setterMethodsByPropertyName.put(lcVariableName, exampleMethod);
}
// later in the code, and assuming that your JSON map is accessible via a Java Map
for (Map.Entry<String, ?> entry : jsonMap.entrySet()) {
String propertyName = entry.getKey();
String lcPropertyName = propertyName.toLowerCase();
if(!setterMethodsByPropertyName.containsKey(lcPropertyName)) {
// do something for this error condition where the property setter can't be found
}
Object propertyValue = entry.getValue();
Method setter = setterMethodsByPropertyName.get(lcPropertyName);
setter.invoke(myExampleInstance, propertyValue);
}
I have the following class that uses internally a HashMap:
open class I18n<T> {
var i18n: MutableMap<LanguageEnum, T?> = mutableMapOf()
#JsonAnyGetter get
fun add(lang: LanguageEnum, value: T?) {
i18n[lang] = value
}
// ...
}
Thanks to the #JsonAnyGetter annotation, when I serialize this into Json I have the following format:
{
"pt": "Texto exemplo",
"en": "Example text"
}
instead of
{
i18n: {
"pt": "Texto exemplo",
"en": "Example text"
}
}
Now I need to make the way back. I have a HashMap containing the languages key and I need to desserialize it into my I18n object.
The caveat here is that I'm doing this among a lot of reflections and abstractions and it would be really nice if it could work like this:
// Here I am going through the fields of a given POJO.
// One of those fields is a I18n type.
// My model variable is a Map containing the same keys as my POJO field's name, so I'm basically trying to map them all
for (f in fields) {
if (model.containsKey(f.name)) {
// when f.type is I18n, value is a HashMap<string, string>
val value = model[f.name]
f.isAccessible = true
// when f.type is I18n.class, the field is set to an empty instance of I18n because it could not desserialize
f.set(dto, mapper.convertValue(value, f.type))
f.isAccessible = false
}
}
I would not like to do things like:
if (f.type === I18n.class) {
// Special treatment
}
Any ideas? Thanks in advance.
I have a custom annotation as follows.
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#Documented
#Conditional(OnApiVersionConditional.class)
public #interface ConditionalOnApiVersion {
int[] value() default 5;
String property();
}
OnApiVersionConditional is,
public class OnApiVersionConditional implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(ConditionalOnApiVersion.class.getName());
attributes.get("value");
final String inputVersion = context.getEnvironment().getProperty("userInputVersion");
}
In my Bean annotation,
#Bean
#ConditionalOnApiVersion(value = {6, 7}, property = "userInputVersion")
There are beans with single version matching also, like
#Bean
#ConditionalOnApiVersion(value = 8, property = "userInputVersion")
I would like to validate the userInput version from the property file to the available Beans supported versions. Not sure, how can i get the value, iterate and compare with userInoutVersion. The value could be 8 or {6,7} as an int array. Not sure, how can i iterate the value to check if any of the value is matching with the input version.
final List apiVersions = attributes.get("value").stream().collect(Collectors.toList());
Question:
How to iterate attributes.get("value") and compare with userInputVersion?
attributes.get("value") returns a List of Object.
I tried the below code,
final List<Object> apiVersions = attributes.get("value").stream().collect(Collectors.toList());
boolean result = apiVersions.stream().anyMatch(version -> (int)version == Integer.parseInt(userInputVersion));
But getting the below error int eh 2nd line anyMatch,
java.lang.ClassCastException: [I cannot be cast to java.lang.Integer
Thanks
You have defined a good custom annotation which uses Spring's #Conditional and this process will only create beans if the property named 'property' is present in the array value.
The annotation defines these two variables as follows:
value: int[] (One or more supported versions)
property: String (The name of the property to compare to)
When you use the metadata to retrieve these values, Spring returns them as Objects. Casting these objects into the types you defined them as is a key step. There is no need for streams as iterating an int[] array is trivial.
I tested various forms of your annotation (value = 5, value = {6,7}, etc) and found the following code to work well (#Conditional beans were only created if the version was correct).
public class OnApiVersionConditional implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
final MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(ConditionalOnApiVersion.class.getName());
// 1. Retrieve the property name from the annotation (i.e. userInputVersion)
List<Object> propertyObject = attributes.get("property");
// 2. Cast it to a String
String propertyName = (String)propertyObject.get(0);
// 3. Retrieve the value
List<Object> list = attributes.get("value");
// 4. Cast it to int[]
int[] values = (int[])list.get(0);
// 5. Look up the actual version (based on the property name in the annotation)
final String inputVersion = context.getEnvironment().getProperty(propertyName);
// Assume it is an integer?
int version = Integer.valueOf(inputVersion).intValue();
// 6. Scan the supported version; look to match against actual version
for (int i : values)
if (i == version)
return true;
// The version is not supported
return false;
}
}
This method could be improved by validating both property and value; returning false if either of these contains bad/empty values, etc.
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
Summarizing excellent answers by Programmer Bruce and StaxMan:
Missing properties referenced by the constructor are assigned a default value as defined by Java.
You can use setter methods to differentiate between properties that are implicitly or explicitly set. Setter methods are only invoked for properties with explicit values. Setter methods can keep track of whether a property was explicitly set using a boolean flag (e.g. isValueSet).
What happens if I annotate a constructor parameter using #JsonProperty but the Json doesn't specify that property. What value does the constructor get?
For questions such as this, I like to just write a sample program and see what happens.
Following is such a sample program.
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// {"name":"Fred","id":42}
String jsonInput1 = "{\"name\":\"Fred\",\"id\":42}";
Bar bar1 = mapper.readValue(jsonInput1, Bar.class);
System.out.println(bar1);
// output:
// Bar: name=Fred, id=42
// {"name":"James"}
String jsonInput2 = "{\"name\":\"James\"}";
Bar bar2 = mapper.readValue(jsonInput2, Bar.class);
System.out.println(bar2);
// output:
// Bar: name=James, id=0
// {"id":7}
String jsonInput3 = "{\"id\":7}";
Bar bar3 = mapper.readValue(jsonInput3, Bar.class);
System.out.println(bar3);
// output:
// Bar: name=null, id=7
}
}
class Bar
{
private String name = "BLANK";
private int id = -1;
Bar(#JsonProperty("name") String n, #JsonProperty("id") int i)
{
name = n;
id = i;
}
#Override
public String toString()
{
return String.format("Bar: name=%s, id=%d", name, id);
}
}
The result is that the constructor is passed the default value for the data type.
How do I differentiate between a property having a null value versus a property that is not present in the JSON?
One simple approach would be to check for a default value post deserialization processing, since if the element were present in the JSON but had a null value, then the null value would be used to replace any default value given the corresponding Java field. For example:
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFooToo
{
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
// {"name":null,"id":99}
String jsonInput1 = "{\"name\":null,\"id\":99}";
BarToo barToo1 = mapper.readValue(jsonInput1, BarToo.class);
System.out.println(barToo1);
// output:
// BarToo: name=null, id=99
// {"id":99}
String jsonInput2 = "{\"id\":99}";
BarToo barToo2 = mapper.readValue(jsonInput2, BarToo.class);
System.out.println(barToo2);
// output:
// BarToo: name=BLANK, id=99
// Interrogate barToo1 and barToo2 for
// the current value of the name field.
// If it's null, then it was null in the JSON.
// If it's BLANK, then it was missing in the JSON.
}
}
class BarToo
{
String name = "BLANK";
int id = -1;
#Override
public String toString()
{
return String.format("BarToo: name=%s, id=%d", name, id);
}
}
Another approach would be to implement a custom deserializer that checks for the required JSON elements. And yet another approach would be to log an enhancement request with the Jackson project at http://jira.codehaus.org/browse/JACKSON
In addition to constructor behavior explained in #Programmer_Bruce's answer, one way to differentiate between null value and missing value is to define a setter: setter is only called with explicit null value.
Custom setter can then set a private boolean flag ("isValueSet" or whatever) if you want to keep track of values set.
Setters have precedence over fields, in case both field and setter exist, so you can "override" behavior this way as well.
I'm thinking of using something in the style of an Option class, where a Nothing object would tell me if there is such a value or not. Has anyone done something like this with Jackson (in Java, not Scala, et al)?
(My answer might be useful to some people finding this thread via google, even if it doesn't answer OPs question)
If you are dealing with primitive types which are omittable, and you do not want to use a setter like described in the other answers (for example if you want your field to be final), you can use box objects:
public class Foo {
private final int number;
public Foo(#JsonProperty Integer number) {
if (number == null) {
this.number = 42; // some default value
} else {
this.number = number;
}
}
}
this doesn't work if the JSON actually contains null, but it can be sufficient if you know it will only contain primitives or be absent
another option is to validate the object after deserialization either manually or via frameworks such java bean validation or, if you are using spring, the spring validation support.
I am using custom annotation with aspectj.
#TestLoggingAnnotation(setMessage = "I want to set value here")
public void get() {
String retString = null;
String message = "DEFAULT";
if (message == "DEFAULT") {
retString = "Default Logging";
} else {
retString = "Custom Logging";
}
}
The above is just simple and sample code. My requirement is that I want to pass the parameter value after resulting from method.
In my case I want set retString value to setMessage in custom parameter.
As of now, annotations can only take compile constants and cant be assigned values at runtime, though their value can be used at runtime using #Retention.
discussion follows here
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TestLoggingAnnotation{
String setMessage ();
}
now use reflection to extract and set method param i doubt we can do this with local varibles.