check at compile time if string parameter passed to method has @deprecated annotation
I'd like to validate whether Strings passed to methods are deprecated or not. e.g.:
publ开发者_开发技巧ic 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 nearest I can find is validating annotations on the parameters themselves.
Your annotation marks the field OLD_PATH
as deprecated, not the string "old_path"
. In the call to migrateProperty
you pass the string, not the field. So the method doesn't know the field the value comes from and can't check it for annotations.
With annotations you state something about the Java elements, like classes, fields, variables, methods. You cannot annotate objects, like strings.
The article you link to talks about annotating formal parameters. Again, it's the parameter that is annotated, not the argument (the value passed). If you put @Something to a method parameter, this parameter will always be annotated independently from the value that a caller to this method passes.
What you can do - but I'm not sure if that's what you want - 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 really pass the field, not it's value.
The "old" (pre Java 5, JavaDoc-based) deprecated annotation is stored in the compiled class file, but is unfortunately not accessible through reflection.
If it is an option for you to use the "real" annotation instead (@java.lang.Deprecated), you could of course use reflection to get all declared fields of your class, check if they are static Strings with a @Deprecated annotation and compare these with the passed method argument.
This sounds rather ugly however and I would encourage you to find a different way to check for unwanted arguments.
First of all, your "@deprecated" marker is simply a JavaDoc tag, not an annotation, so it has nothing to do with the compiler.
If you use the @Deprecated
annotation, you'll get the deprecation warning for the lines where you're using the deprecated field:
@Deprecated
private static final String OLD_PATH = "old_path";
private static final String NEW_PATH = "new_path";
You can keep the JavaDoc @deprecated
tag too, but it would only be useful if you'd provide some explanation for it. The javadoc tag must be inside /** ... */
, of course.
However, if you want to check at runtime inside the migrateProperty()
method that the string passed came from a deprecated variable, that's impossible. What you get with the method call is a reference to a String on the heap. The deprecation only refers to the field, which could only be checked prior to calling the method, perhaps.
I don't know what exactly your use case is here, but I don't think you can do what you want with @Deprecated. When you mark something as deprecated, you're marking the field, method or class NOT the value. All you're getting access to in loadProperty is the value.
So taking your example, I can quite easily call
new MyRepo().loadProperty("old_path");
without referencing OLD_PATH or NEW_PATH at all. The solution is simple, you need to check explicitly in your methods for a match. (isDeprecated method added). You can leave the @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 multiple classes, you can, of course, make this more rigourous.
Does checking of ".class" file fits into your "compile" time requirement? FindBug allows many inspections on the .class file. You can write custom plugin to check fields, method and arguments (and many other things). Here is a old tutorial
If you manage to write one, I will be very much interested in using that code :)
My approach would be to change this into something the compiler is already good at: type checking.
Based on the use of constants in your example I'm going to assume you have a known set of potential values, which suggests enum
s.
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 doesn't fit your needs, add some more info about your usage scenario and what the underlying problem is that you're trying to solve.
If you're really set on accomplishing this via annotations, it appears there is a way. Java 6 has annotation processing APIs built into javac
, which seem to effectively be plugins for the compiler. They can do what you're after plus a whole lot more, but they seem pretty esoteric, at least on first glance. This looks like a good intro: http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html
It is simply not possible to do this at compile time.
Firstly, the @Depreciated annotation can refer to a String field, but is not in anyway attached the string value.
Secondly, even if you somehow could tag a string value with an annotation, nothing in the Java type system lets you declare that only values with a certain annotation can be passed - the annotation information wouldn't necessarily even be available at compile time.
Annotation processing won't work due to point 1. All other schemes will only work at runtime due to point 2.
To achieve a compile time check the most natural fit would be to create a enum that contains all valid values of your string.
精彩评论