Check at compile time if the string parameter passed to the method has @deprecated annotation

I would like to check if the strings passed to the methods are obsolete or not. eg:.

public class MyRepo @Deprecated private static final String OLD_PATH = "old_path"; private static final String NEW_PATH = "new_path"; //... public load(Node node){ migrateProperty(node, OLD_PATH , NEW_PATH ); //load the properties loadProperty(node, NEW_PATH); } //I want to validate that the String oldPath has the @Deprecated annotation public void migrateProperty(Node node, String oldPath, String newPath) { if(node.hasProperty(oldPath)){ Property property = node.getProperty(oldPath); node.setProperty(newPath, (Value) property); property.remove(); } } //I want to validate that the String path does not have the @Deprecated annotation public void loadProperty(Node node, String path) { //load the property from the node } } 

The closest I can find is checking the annotations of the parameters themselves .

+4
source share
7 answers

Your annotation marks the OLD_PATH field as deprecated, not the string "old_path" . In a migrateProperty call migrateProperty you pass a string, not a field. Thus, the method does not know the field where the value comes from, and cannot check it for annotations.

In the annotation, you indicate something about Java elements such as classes, fields, variables, methods. You cannot annotate objects, such as strings.

The article you are referencing talks about annotating formal parameters. Again, this is a parameter that is annotated, not an argument (value passed). If you put @Something in a method parameter, that parameter will always be annotated, regardless of the value that the calling object passes to this method.

What you can do, but I'm not sure if this is what you want - this is the following:

 @Deprecated private static final String OLD_PATH = "old_path"; private static final String NEW_PATH = "new_path"; public load(Node node){ migrateProperty(node, getClass().getDeclaredField("OLD_PATH"), getClass().getDeclaredField("NEW_PATH") ); // ... } //I want to validate that the String oldPath has the @Deprecated annotation public void migrateProperty(Node node, Field<String> oldPath, Field<String> newPath) { if ( oldPath.getAnnotation(Deprecated.class) == null ) { // ... invalid } // ... } 

In this case, you are really passing a field, not a value.

+1
source

First of all, your @deprecated token is just a JavaDoc tag, not an annotation, so it has nothing to do with the compiler.

If you use the @Deprecated annotation, you will receive a rejection warning for lines where you use an obsolete field:

 @Deprecated private static final String OLD_PATH = "old_path"; private static final String NEW_PATH = "new_path"; 

You can also store the JavaDoc @Deprecated tag, but that would be helpful if you provided some explanation for it. Of course, the javadoc tag must be inside /** ... */ .


However, if you want to check at runtime inside the migrateProperty() method that the passed string came from an obsolete variable, this is not possible. What you get with the method call is a reference to String on the heap. Deviation refers only to a field that can only be checked before a method is called.

0
source

"obsolete" (pre-Java 5, based on JavaDoc) obsolete annotation is stored in the compiled class file, but, unfortunately, is not accessible through reflection.

If you can use the "real" annotation (@ java.lang.Deprecated) instead, you can, of course, use reflection to get all the declared fields of your class, check if they are static strings with @Deprecated annotations and compare them with argument of the method passed.

This sounds pretty ugly, but I would advise you to find another way to check for unwanted arguments.

0
source

I don’t know what exactly you are using, but I don’t think you can do what you want with @Deprecated. When you mark something as deprecated, you mark the field, method or class NOT value. All you get access to in loadProperty is the value.

So, taking your example, I can easily name

 new MyRepo().loadProperty("old_path"); 

without reference to OLD_PATH or NEW_PATH. The solution is simple, you need to explicitly check your methods for consistency. (added date added method). You can leave @Deprecated annotation if you like, as an indication.

 public class MyRepo { @Deprecated private static final String OLD_PATH = "old_path"; private static final String NEW_PATH = "new_path"; private boolean isDeprecated(String path) { return OLD_PATH.equals("old_path"); } //... public load(Node node){ migrateProperty(node, OLD_PATH , NEW_PATH ); //load the properties loadProperty(node, NEW_PATH); } //I want to validate that the String oldPath has the @Deprecated annotation public void migrateProperty(Node node, String oldPath, String newPath) { if (!isDeprecated(oldPath)) { throw new Exception(oldPath + " is not deprecated"); } if(node.hasProperty(oldPath)){ Property property = node.getProperty(oldPath); node.setProperty(newPath, (Value) property); property.remove(); } } //I want to validate that the String path does not have the @Deprecated annotation public void loadProperty(Node node, String path) { if (isDeprecated(path)) { throw new Exception(path + " is deprecated, please use " + NEW_PATH); } //load the property from the node } } 

If this pattern needs to be applied to several classes, you can of course make it more rigorous.

0
source

Is the ".class" file checked in accordance with the compilation time requirements? FindBug allows a lot of checks in the .class file. You can write your own plugin to check fields, methods and arguments (and much more). Here is an old tutorial

If you manage to write one, I will be very interested to use this code :)

0
source

My approach is to change this to something that is already a good compiler: type checking.

Based on the use of constants in your example, I'm going to suggest that you have a known set of potential values ​​that enum s offers.

 public class MyRepo private enum Preferred { PATH("new_path"), OTHER_THING("bar"); private String value; Preferred(String value) { this.value = value; } @Override public String toString() { return value; } } private enum Legacy { PATH("old_path"), OTHER_THING("foo"); private String value; Legacy(String value) { this.value = value; } @Override public String toString() { return value; } } public load(Node node){ migrateProperty(node, Legacy.PATH, Preferred.PATH); //load the properties loadProperty(node, Preferred.PATH); } public void migrateProperty(Node node, Legacy oldBusted, Preferred newHotness) { if (node.hasProperty(oldBusted)) { Property property = node.getProperty(oldBusted); node.setProperty(newHotness, (Value) property); property.remove(); } } public void loadProperty(Node node, Preferred path) { //load the property from the node } } 

If this does not meet your needs, add more information about the use case and that the main problem is what you are trying to solve.


If you really set yourself up for doing this through annotations, there seems to be a way. Java 6 implements annotation APIs built into javac , which seem to be efficient compiler plugins. They can do what you need, plus much more, but they seem pretty esoteric, at least at first glance. This seems like a good introduction: http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html

0
source

This is simply impossible to do at compile time.

First, the @Depreciated note may refer to a String field, but in any case does not bind a string value.

Secondly, even if you can somehow mark the string value with an annotation, nothing in a system like Java allows you to declare that only values ​​with a specific annotation can be transmitted - the annotation information will not even be available at compile time.

Annotation processing will not work due to point 1. All other schemes will work only at runtime due to point 2.

To perform compile-time checking, the most natural option would be to create an enumeration containing all the valid values ​​of your string.

0
source