-
Notifications
You must be signed in to change notification settings - Fork 782
added some null annotations to core APIs #3933
Conversation
@@ -1087,7 +1089,7 @@ protected synchronized RuleStatusInfo getRuleStatusInfo(String rUID) { | |||
return statusMap.get(rUID); | |||
} | |||
|
|||
protected synchronized String getUniqueId() { | |||
protected synchronized @Nonnull String getUniqueId() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this annotation is needed here? This method is not part of the public api and it always returns not null value. If it returns a null this is bug in implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it always returns a non-null value, why not state it here? It prevents warnings at places where this method is called (as you won't need to do any null checks there).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll not add any null checks because I know that it does not return null. The problem with the annotation is it adds unnecessary checks on each method call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"because I know"
Now that is really helpful, is it? If somebody refactors the code so that it suddenly returns null, all your code will break without noticing. If somebody adds a functionality that uses your method, he will add null checks, because he does not know that it will always be non-null. For both situations, the annotation is very helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I agree.
String rUID = rule.getUID(); | ||
if (rUID == null) { | ||
rUID = ruleEngine.getUniqueId(); | ||
super.add(initRuleId(rUID, rule)); | ||
} else { | ||
super.add(rule); | ||
} | ||
return get(rUID); | ||
Rule ruleCopy = get(rUID); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible the AbstractRegistry to return a null value for already added element? If so it is a bug in the AbstractRegistry implementation for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it is a bug in your train of thoughts - which proves that the annotations are helpful :-)
We are requesting a rule of a certain UID here. You cannot assume that it exists here. Although you have added it previously, somebody else might have deleted it again or the initRuleId
method might have decided to store it under a different UID - how could you tell?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initRuleId won't change id because it is my method, but you are right that someone can meanwhile remove the added element. Ok this approach is better then synchronization for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because it is my method
There is no such thing as "my method" in an open source project. Make sure to implement robust code that does not do too many implicit assumptions that only exist in your mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"my method"
I mean that this is an implementation detail which is not exposed by the API. We have to find a limit where to stop with annotations. Do we have to set annotation on the private methods too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are helpful as they remove a warning at another place as in this case, what speaks against it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a worning in places where I use this method. Btw these warnings are related with missing null checks. Isn't be better to have handle these situations (as we saw that @nonnull does not defends us) and provider a suitable exception instead of just a NullPointerException at runtime.
Anyway I can live with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we saw that @nonnull does not defends us
It actually defends us especially on internal methods, because we have checks in place in our IDE and the Maven build. So we can be sure that all ESH code itself respects the annotations - imho definitely helpful for any kind of future refactorings.
@@ -251,7 +255,7 @@ public Rule add(Rule rule) { | |||
* @param rule candidate for unique ID | |||
* @return a rule with UID | |||
*/ | |||
protected Rule initRuleId(String rUID, Rule rule) { | |||
protected @Nonnull Rule initRuleId(String rUID, Rule rule) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of this method will never return a null element. I think the annotation is an overweight of the method. IMHO it is better to update java doc to said the method returns not null value instead of adding a runtime check on every method call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that is the whole intention of the annotations (please read all the discussion on #3624): Nobody reads JavaDoc and if you can express the very same in a machine readable way, it is WAY BETTER.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I agree that it is more readable way, but it adds additional checks at runtime, which are not needed for me. Actually this is not public method used only by the RuleRegistryImpl and RuleEngine. Anyway I can live with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it adds additional checks at runtime
I didn't get the point why the annotation itself will add checks at runtime.
Could you please explain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About the JavaDoc and annotation: The JavaDoc is for the developer. If a developer does not read the documentation he does it wrong. But: The nullness annotations are for the tooling on the development and compilation time. Tools (e.g. IDE or something that is used at compile time) could check if your written code is safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it adds additional checks at runtime
I didn't get the point why the annotation itself will add checks at runtime.
Could you please explain.
Because the @nonnull is annotated with @retention(RetentionPolicy.RUNTIME)
Can you please explain me about the relationship between the retention policy runtime and additional checks at runtime?
Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
So, a point could be "the file size of the classes increases by a few bytes".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right, but are you sure that this annotation is not used at runtime by the VM to ensure that the passed parameter is not null? For example if we have this method:
public void foo(@nonnull String a)
the VM has to check on each call if the param a != null otherwise we can't remove the null check from the method. If so I'm not sure if there is a need from it at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JVM does not care about the annotation at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is than suitable to remove null pointer checks in all places in this commit? For example in the AbstractManagedProvider.add(E elemetn) was removed
if (element == null) {
throw new IllegalArgumentException("Cannot add null element");
}
which will lead to the NPE instead of exception with describable message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, see #3933 (comment).
@@ -49,15 +54,15 @@ | |||
* key of the element | |||
* @return element or null if no element was found | |||
*/ | |||
public E get(K key); | |||
public @Nullable E get(K key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see what information gives this annotation here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says that the return value might be null (and by adding it, we state that this is deliberate and not that we merely didn't bother adding a @Nonnull
annotation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concerns was that exactly this was said in the javadoc (but in case if it is not read ok) and it is obvious for me that it will return a null for any key without value (as a Map).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is obvious for me that it will return a null for any key without value
Well, there are also other examples, so making it explicit is a good thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can change the return value from @Nullable
of a parent class to @Nonnull
in a child but you can't change from @Nonnull
to @Nullable
.
It is the same logic used for more specialized returned classes used by the JVM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maggu2810 these are two different methods and the getItem() does not overwrite the get(), My proposal was: Registry.get() to be not annotated, because it is obvious that it returns null for missing elements (as any Map), and to annotated @nonnull ItemRegistry.getItem() because it never return a null.
But if you think current approach is better I'm ok with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some @SuppressWarnings("null")
I would like to check. I can do it after this one is merged or before.
I know you already address that issue in your initial comment. But I would like to give it a try (some time).
All the other stuff looks good to me.
@@ -139,6 +139,7 @@ public void addRegistryChangeListener(RegistryChangeListener<E> listener) { | |||
listeners.add(listener); | |||
} | |||
|
|||
@SuppressWarnings("null") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to check this one (some time)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be happy if you'd find a better solution - my issue is that the whole Java8 Stream API is pretty much incompatible with those annotations. Which might be a hint that for all new stream based APIs that we introduce, we might indeed want to consider the use of Optional
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See below...
Me, too.
Sometimes yes, sometimes no. It depend on the use case. The annotation itself is no safe-guard, it is equal to a comment in the JavaDoc. We cannot know if a binding developer is using the annotation checks etc. So, if the framework has to protect itself to prevent a NPE some time later, he still needs it.
Hm, IIRC this was my concern about using the chosen annotation and you told me it doesn't care.
The Checker Framework supports an "annotation stub" for the whole JDK classes.
I agree that |
Ok, agreed; but my suggestion would be to only have additional null checks at places where it could really have negative impact on the framework, if the NPE happens at a later stage in the code. If it "just" leads to an NPE being thrown, that's something in the responsibility of the caller then.
I didn't "not care", but I meant better have something than nothing. But I admit that I underestimated the widespread use of that case in our API, so point for you ;-)
There is also a similar feature in the Eclipse IDE (external annotations), maybe we should have a closer look at it in future. I have rebased the branch, so imho it should be ready to be merged. Note that Travis is likely to fail for some other reason. |
Exactly what I was thinking about. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't "not care", but I meant better have something than nothing. But I admit that I underestimated the widespread use of that case in our API, so point for you ;-)
Thanks for the point, but be sure, its not about "I told you".
I don't understand your comment "better have something than nothing".
The related discussion was about "which" annotation is used as there are several ones.
The used annotation could be configured in the Eclipse IDE, so I don't think it was about "something or nothing" but "javax" namespace vs. another specific one.
There is also a similar feature in the Eclipse IDE (external annotations), maybe we should have a closer look at it in future.
Didn't see it yet. Will try to find it 😉
Then I would prefer (with respect to your comment above about the stream API etc.).
Adding @SupressWarnings("null")
is IMHO contradictionar to introducing nullness annotations.
@@ -139,6 +139,7 @@ public void addRegistryChangeListener(RegistryChangeListener<E> listener) { | |||
listeners.add(listener); | |||
} | |||
|
|||
@SuppressWarnings("null") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See below...
Ok, you are right (again) ;-)
Here's something to read for the weekend: http://help.eclipse.org/oxygen/index.jsp?topic=/org.eclipse.jdt.doc.user/tasks/task-using_external_null_annotations.htm |
Would it make sense to give the Eclipse JDT Nullness annotations a try? |
... can be argued, yes... |
I did a quick test with the eclipse annotations and this is the result: my example
First observation: I can easily replace the package names of The two examples from the Eclipse JDT documentation do work as stated in the comments:
So I think the check works only intra-procedural? @maggu2810 What are your observations in this regard? |
Ok, compared to the Findbugs annotations we can get rid of a warning on
Though I am not sure if the warning disappears because there is a real check, OR there is no warning/check mechanism implemented for items within a collection. Because in the |
Signed-off-by: Kai Kreuzer <kai@openhab.org>
I have updated my PR to use the JDT annotations instead - a use of generic type annotations can be found here. This annotations prevents a warning in this line, since we have to make sure to pass a non-null value, which is now ensured through the new annotation that wasn't possible before. It does not seem to solve ALL out collection issues though. For example, I wasn't able to get rid off this check, since So if this version is fine to everyone: @triller-telekom, could you then help to adapt the Maven build accordingly? I have already removed the javax.annotation from the target platform with this PR. |
If this is a step forward compared to the @maggu2810 Since you are using annotations on your local IDE since quite a while. WDYT? |
Sorry, very busy... I am using nullness annotations and checks several month now (approximately about a year). I integrate the checks in the build system only. I didn't try to configure the IDE, yet. IMHO we should use "something" that support the generic type annotations e.g. the JDT ones. If the tools that checks the sources (in our case the Eclipse tooling) does not detect all that stuff at the moment, this is another story (the tooling could be improved).
Let's give it a try. |
So feel free to merge! |
Since we are starting to use quite a lot of annotations now and more often run into situations where we would need annotations on the JDK or other external libraries, how shall we proceed with that? Eclipse offers the "Eclipse External Annotations": @kaikreuzer mentioned them here: https://github.com/openhab/openhab2-addons/pull/2556#issuecomment-323672827. I had a brief look at them and the repository doesn't look that full so far. But how about integrating these external annotations in our setup and in case they are lacking some annotation we can contribute them to the repository? |
I had started to put some null annotations to our Registry interface. I learned that adding such annotations quickly spreads throughout the code, so I ended up adding them at a lot of places.
I tried to limit it for the start and have a decent balance between the number of changed classes and the number of new warnings being added to the code at all unchecked places.
A few observations:
throw new IllegalArgumentException()
superfluous.Collections.emptyList()
(and similar methods) are not recognised to be non-null, so we have to add asuppressWarnings
when we use them (not ideal). The same applies to other 3rd party libs or JVM classes as we do not have any null information about them.Identifiable
interface: I would claim thatIdentifiable.getUID()
should return a non-null value. Adding such an annotation gets us into trouble with theRule
class, though as we allow the creation of rules without any UID (which is added only by the rule registry upon add). I think we should have a closer look at how this can best be resolved - for the moment, I didn't add the annotation ongetUID()
.Signed-off-by: Kai Kreuzer kai@openhab.org