Avoiding a set of java annotations to be used together - java

I have created different java annotations which shouldn't be used together (something like #SmallTest, #MediumTest and #LargeTest. Is there a way to make the compiler not allow them being used together?
EDIT: more info to make my problem more explicit
Suppose I have:
public #interface SmallTest
{
String description();
}
public #interface MediumTest
{
String description();
ReasonToBeMedium reason(); //enum
int estimatedTimeToRun();
}
public #interface LargeTest
{
String description();
ReasonToBeLarge reason(); //enum
int estimatedTimeToRun();
}

Instead of creating three different annotations, you could create one annotation which takes an enum parameter, like #MyTest(TestSize.SMALL), #MyTest(TestSize.MEDIUM), or#MyTest(TestSize.LARGE).
Something like this (not tested, no guarantees, may cause abdominal distension, yadda yadda):
public #interface MyTest
{
TestSize value() default TestSize.MEDIUM;
}
Edit re: OP's comment "What if the annotation has a content itself, say "description"? And what if the content for each is different (say one has description, the other has estimatedTimeToRun)?"
It's not fantastically elegant, but you could lump all of the annotation elements in as well, with reasonable defaults for the optional elements.
public #interface MyTest
{
String description(); // required
TestSize size() default TestSize.MEDIUM; // optional
long estimatedTimeToRun default 1000; // optional
}
Then use it like:
#MyTest(description="A big test!") or
#MyTest(size=TestSize.SMALL, description="A teeny tiny test", estimatedTimeToRun = 10) or
#MyTest(description="A ho-hum test")

Related

How to create a single add method for different entities?

How can I create a single add method that will insert different entities in the jdbc? I want to use annotations and reflection for that.
I have created 2 annotations:
(...)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
and
(...)
public #interface Table {
String name();
}
Suppose we have 2 entities/models/etc. : Client and Waiter. For both we should make 2 add methods, each with own INSERT.
If we have like 4 entities, we should have 4 add methods for each entity. Instead of having 4 add methods, how can I make just 1 add method? and by using the annotations and the reflection.
Eg:
#Table(name = "Table_Client")
public class Client{
#Column(name = "ID", isPrimaryKey = true)
private long id;
#Column(name = "FULL_NAME")
private String name;
}
#Table(name = "Table_Waiter")
public class Waiter {
#Column(name = "FULL_NAME", isPrimaryKey = true)
private String name;
#Column(name = "AGE")
private int age;
}
case: db.add(Client c1) => add to the database in the table Table_Client the client c1
db.add(Waiter w1) => add to the database in the table Table_Waiter the waiter w1
and so on...
My idea is to take the class of the given object and scan it for the TYPE annotation to get the table's name. Then, take all the field's annotations and make a dynamic INSERT INTO VALUES query, but the problem is that I can't actually do that, because I can't pass the object's arguments.
Another question: if this can be done, update and delete methods can follow the same path?
I cannot refrain from mentioning how many holes you may find in the road ahead. But judging from the comments, that's the path you want to explore.
First of all, regarding your existing code, you need to apply a retention meta-annotation to your annotations, Column and Table. For example:
#Retention(RetentionPolicy.RUNTIME)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
This meta-annotation will ensure that you can read your annotations at runtime through reflection.
Then, you need to inspect the class searching for these annotations at both class and field levels.
The Class class will have everything you need. You should know you can get it from any object by calling the getClass method. It contains a couple of important methods for what you are trying to achieve:
getAnnotation(Class c) will return the annotation if it exists, or null otherwise.
getDeclaredFields will return all declared class fields, even private ones.
At a field level, the Field class provides the following methods:
getAnnotation(Class c), same as above, will return the annotation if it exists, or null otherwise.
getType will return the class associated with the field
Now consider the following piece of code:
public static void inspectClass(Class<?> cls) {
Table t = cls.getAnnotation(Table.class);
if (t != null) {
System.out.print(t.name() + " --> ");
for (Field f: cls.getDeclaredFields()) {
Column c = f.getAnnotation(Column.class);
if (c != null) {
System.out.print(c.name()
+ " "
+ f.getType().getSimpleName()
+ (c.isPrimaryKey() ? " PK" : "") + ", ");
}
}
}
}
Applying this to your Client class, for instance, would return something like:
Table_Client --> ID long PK, FULL_NAME String,
Of course, this needs some work, but the idea is there.
EDIT:
To access values of an instance through reflection at runtime, for creating a dynamic INSERT statement, that could be done by calling get method on the Field class. When dealing with private fields though, it's necessary to tweak the privacy mode first:
f.setAccessible(true);
Object value = f.get(myInstance);

Map null values to default using builder with MapStruct

I want to map field from Source to Target class, and if the source value is null, I would like to convert it to default value based on the data type ("" for strings, 0 for numeric types etc.). For setting the values, I am not using regular setters, but builder (with protobuf, so the names of the methods is newBuilder() and build()).
class Source {
private final String value; // getter
}
class Target {
private final String value;
public static Builder newBuilder() {return new Builder()}
public static class Builder {
public static setValue() {/*Set the field*/}
public static Target build() {/*Return the constructed instance*/}
}
My mapper looks like this:
#Mapper(
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface TargetMapper {
Target map(Source source);
}
The generated mapper implementation with this code calls target.setValue(source.getValue()), instead of performing the null check and setting default value if source returns null. The interesting part is when I add the following annotation to the map method, the null check is present in the implementation.
#Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
Is this a bug in MapStruct with builders, or am I missing some configuration to be ably to set the null mapping as a default policy, instead of duplicating it on all field mappings?
EDIT: For some reason, adding nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS to the class level #Mapper annotation adds the null check, but does not explicitly set the value, just skips the call to setValue. For protobuf, this is okay, since this functionality is in the library, but for other implementations the field would remain null.
#Mapping(source="value", target="value", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
applies to update method (so methods that have the #MappingTarget annotated parameter
There's no real counterpart for regular methods:
1. NullValueMappingStragegy applies to the bean argument itself.
2. NullValueCheckStragegy does perform a check on bean properties, but does not return a default.
Naming is not really brilliant and it has a long history. We still have the intention to align this one day.
A solution would be to use an Object factory creating the builder target object and pre-populate it with default values and then let MapStuct override these one day.
Perhaps you could do something like this:
#Mapper(
// to perform a null check
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface TargetMapper {
Target map(Source source);
}
// to create a pre-defined object (defaults set a-priori). Not sure
// whether this works with builders.. just try
#ObjectFactory
default Target.Builder create() {
Target.Builder builder = Target.newBuilder();
builder.setValueX( "someDefaultValue" );
return builder;
}

How to mass annotate constructor arguments?

I've got a problem where I want to make a lot of classes in our project de-serializable via jackson. The problem is that most of classes look like this:
public class FinalFieds{
private final String field;
private final String secondField;
public FinalFieds(String field, String secondField)
{
this.field = field;
this.secondField = secondField;
}
public String getField()
{
return field;
}
public String getSecondField()
{
return secondField;
}
}
So what I found is that in jackson you can do something like this:
public FinalFieds(#JsonProperty("field") String field, #JsonProperty("secondField") String secondField)
And that works nice. The problem is that I cannot make structural replace in intellij to work for me. When I try:
All my matches are in "Unclassified matches" section.
Furthermore when I try to replace, Intellij just removes a constructor from the class.
Any idea on what I'm doing wrong or is it a known bug in intellij?
Even an overcomplicated regex that will help me replace this (for single argument constructors I can create it myself; the problem is that our constructors in those classes have multi-argument constructors).
It's a bug or a missing feature depending on how you look at it.
https://youtrack.jetbrains.com/issue/IDEA-141143
However, it is possible to do it in two steps. First search for the constructor parameters you want to annotate:
class $Class$ implements OurCommonInterface {
$Class$($Type$ $parameter$);
}
where $parameter$ min: 1 max: unlimited, This variable is target of the search checked.
Then replace the parameter with an annotated parameter in scope Previous Search Results:
$Type$ $parameter$
Replacement template:
#JsonProperty("$parameter$") $Type$ $parameter$

Augment jackson's JsonParser

Currently my applications can do as follows. It can read a JSON configuration such as:
{
"a": 5,
"b": 3
}
Into a POJO that looks like:
public class AddConf {
private Number a;
private Number b;
// constructor, getters and setters
public int add() {
return a.intValue() + b.intValue();
}
}
Then we can call the add function a return a result of 8 in this example. I would like to augment com.fasterxml.jackson.databind somehow to allow my JSON to have placeholders. So let's say when my configuration I don't know what value b will be until runtime, then I might make a configuration like this:
{
"a": 5,
"b": $b_placeholder$
}
To signify that b value will be provided at runtime. Obviously the above is not a valid JSON, and by default jackson throws an Exception (as it should) when it attempts to parse this with ObjectMapper's readValue. Ideally, I would like to read the above "JSON" (or something equivalent) into a POJO that looks something like this:
public class AddConf {
private Map<String,String> usedPlaceholders;
private Number a;
private Number b;
// constructor, getters and setters
public int add(Map<String,String> runtimeConf) {
if (usedPlaceholders.contains("a")) { // if "a" was a placeholder
a = runtimeConf.get(usedPlaceholder.get("a"));
}
if (usedPlaceholders.contains("b")) { // if "b" was a placeholder
b = runtimeConf.get(usedPlaceholder.get("b"));
}
return a.intValue() + b.intValue();
}
public void setPlaceholder(String key, String value) {
usedPlaceholder.put(key, value);
}
}
The idea is when deserializing the augmented JSON above, it would call setPlaceholder("b", "b_placeholder") instead of setting the value b and therefore when add is called, it will use values in the passed in runtimeConf Map instead of values from the JSON to do its configuration.
Given this, I have 2 questions:
Is there an easier way to accomplish my goal of having "placeholders" in my JSON configuration? It seems if I was to implement my idea here, I would have to override some of the Jackson classes. I would have to override the com.fasterxml.jackson.core.JsonParser to allow $ as a valid token in some scenarios, I would also have to write custom derserializers for all my configuration POJO (such as AddConf). This would likely casade into me having to override much of the jackson code base, which I would rather not do.
If I were to take this approach to override some of the default jackson classes, how might I go about doing that?
I am currently using jackson 2.6.0
You may be looking for annotation #JsonRawValue, use of which allows specifying EXACT contents to include as value, while still taking care of adding necessary separators.

Getting annotated variables from a class

This question is a follow up to a question I found before
java: get all variable names in a class
What I want is to get variables from a class, but instead of getting them all, I want only the variables that have the annotation #isSearchable .
So basically I have 2 questions :
How do I create an annotation ?
How to filter my fields by only this annotation ?
And one more thing , if it is something I'm using frequently is it advisable (I'm guessing reflections should be slow).
Thank you
/** Annotation declaration */
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface isSearchable{
//...
}
#isSearchable
public String anyField = "any value";
checking like:
//use MyClass.class.getDeclaredFields() if you want the fields only for this class.
//.getFields() returns the fields for all the class hierarchy
for(Field field : MyClass.class.getFields()){
isSearchable s = field.getAnnotation(isSearchable.class);
if (s != null) {
//field has the annotation isSearchable
} else {
//field has not the annotation
}
}
Here is an example
class Test {
#IsSearchable
String str1;
String str2;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#interface IsSearchable {
}
public static void main(String[] args) throws Exception {
for (Field f : Test.class.getDeclaredFields()) {
if (f.getAnnotation(IsSearchable.class) != null) {
System.out.println(f);
}
}
}
}
prints
java.lang.String Test.str1
How to filter my fields by only this annotation ?
You can get by this simple snippet
Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof IsSearchable){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
In the above snippet you are basically filtering only IsSearchable annotations.
Regarding your one more thing query
Yes reflection will be slow as discussed here, if its possible to avoid, would advise you to avoid.
Field.getDeclaredAnnotations() gives you the annotations for each field.
To answer your supplementary question, I would normally expect reflection to be slow. Having said that, I perhaps wouldn't worry about optimising until this becomes a problem for you.
Hint: Make sure you're checking the up-to-date Javadoc. Google tends to give me Java 1.4 Javadocs, and annotations didn't exist prior to Java 5.

Categories

Resources