-
Notifications
You must be signed in to change notification settings - Fork 789
Nullness fixes #4290
Nullness fixes #4290
Conversation
Travis does not seem to like the changes to |
/** | ||
* Interface for classes that instances provide an identifier. | ||
* | ||
* @author Markus Rathgeb - Initial contribution | ||
*/ | ||
@NonNullByDefault |
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.
Don't we still have the issue that Rule.getUID()
is still allowed to return null and thus is @nullable
?
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.
Hm, you are right, the current concept allows rules without an UID.
But does it make sense to state that we have an interface Identifiable
that provides an UID and allow an implementing class to return null on that method (so "I am itentifiable but don't give you an UID)?
Shouldn't we throw an IllegalStateException
on the Rule.getUID()
method if there is no UID assigned? As soon as someone wants to call getUID
because it relies that the reference is an Identifiable
it should also provide an UID. If the Rule
has no UID assigned, the method should not be called at all (the usage of that reference should know about the fact that there is no UID yet assigned and should not call that method).
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.
Note that I brought this topic up here already:
I think we have introduced a bug when adding the Identifiable interface: I would claim that Identifiable.getUID() should return a non-null value. Adding such an annotation gets us into trouble with the Rule 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 on getUID().
Throwing an IllegalStateException
might be a solution, but @danchom should be involved in that refactoring.
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.
Great, I identified that I replied to your comment 😉
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.
Shouldn't we throw an IllegalStateException on the Rule.getUID() method if there is no UID assigned?
For me no, because it will be hard for users to know for already used strings which are used by the RuleEngine as ruleUIDs. At the moment the RuleEngine grantee unique ids for added rules.
Actuality the Rule is Identifiable after the object is added to RuleRegistry. Before that it is just a POJO object which carries rule's information and there is no guarantee that it is a valid rule object.
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.
@kaikreuzer, @maggu2810 we need to agree on a decision about the @NonNull
UID and @NonNullByDefault
for the getUID()
method in the Identifiable interface. The possible solutions are:
Null
to be possible for UID - this is the current situation whenNull
is a valid UID.
- Advantage: no changes :-)
- Disadvantage: all "not added rules" are equal
- System UID that will be recognized and replaced by the Registry after adding the Rule - this is similar to the current situation, but without default constructors, and if the constructors are called with
Null
, the system UID (constant) will be used instead.
- Advantage:
@NonNullByDefault
can be used for thegetUID()
method. - Disadvantage: all "not added rules" are equal
- Add a
generateUID()
method in Registry. It can be added in:
a) Rule Registry, or
b) Abstract Registry
- Advantage:
@NonNullByDefault
can be used for thegetUID()
method. - Disadvantage: The IDs generated by the
generateUID()
will be unique at the time of generation. When you try to add the Rule in the Registry, the ID may have already been taken for another rule, and adding will fail. In the add method, the Registry does not know if the rule UID was generated by thegenerateUID()
method, or set explicitly by the user.
- The user must provide a correct UID for the rule
- Advantage: the Rule registry acts as all other Registries.
- Disadvantage: we make it more difficult for the user
All resolutions have negatives and we should discuss which is the most effective one.
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.
Didn't we already agree that the getUID()
of an Identifiable
should return an identifiable ID and so not null already?
About your point 3: Couldn't this (the UID generation) be make clever that a collision is such a rare case that it will "never" be happen (see e.g. https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions)?
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.
Didn't we already agree that the getUID() of an Identifiable should return an identifiable ID and so not null already?
@maggu2810 I agree with this, but I can't get this decision alone, I should convince our architects. I am actually asking by their request.
About your point 3: Couldn't this (the UID generation) be make clever that a collision is such a rare case that it will "never" be happen?
It is not about "when the same UUID is generated more than once", it is about that the same UUID could be generated form more than one generators, each of them with its own way to generate a UUID for its rules:
- scripting
- file provider
- bundle provider
- by the user it self
- third party app
I am actually voting for point 4 but let me say again I can't get this decision by my self
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 agree that option 4 is probably the best. This would at least also make it consistent with how we deal with it for Things, where a UID is also generated (by the UI) if a thing is manually created.
To reduce the impact, we could imho simply generate a java.util.UUID
in the Rule constructor, if no UID is passed from the client. This way we would ensure to always have a UID while all client code can stay as it is. Note that the ScriptedRuleSupport already uses with UUID
s, so it would be in sync with this part as well. The only downside would be that the UIDs are not "nice" compared to the current "rule_x", but as they are an internal identifier not really meant for any end user, this is imho acceptable.
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 #4438 to confirm is it what we agreed here
Crazy 😉 Will have a look at. |
Ah, it is about the missing external annotations. |
throws ConfigurationException, MqttException { | ||
// Extract mandatory fields | ||
String brokerID = brokerConnectionConfig.get(NAME_PROPERTY); | ||
if (StringUtils.isBlank(brokerID)) { | ||
if (brokerID == null || brokerID.isEmpty()) { |
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.
Imho it really is a pity that we have to "work around" the annotations now just because the compiler is not able to recognise StringUtils.isBlank
as an implicit null check. I just had a similar situation in a builder pattern (see https://github.com/eclipse/smarthome/pull/4301/files#diff-8f975ae88094089585192875908ba08bR79) which I could solve by doing some API changes. In the end it turned out these changes where a turn for the better.
Just to clarify: I thought @NonNullByDefault
should guarantee the call to brokerConnectionConfig.get(NAME_PROPERTY)
to always return a non null value? So why is the null check (or StringUtils.isBlank) required 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.
Imho it really is a pity that we have to "work around" the annotations now just because the compiler is not able to recognise
StringUtils.isBlank
as an implicit null check.
You are right, the Eclipse Nullness Checker should be improved and the EEAs.
The Checker Framework can handle such situations already.
There I could use astub files (similar to EEA) and state that if the method returns true, the reference used in the argument is non-null.
class StringUtils {
static @EnsuresNonNull("#1") boolean isBlank(@Nullable String str);
}
throws ConfigurationException, MqttException { | ||
// Extract mandatory fields | ||
String brokerID = brokerConnectionConfig.get(NAME_PROPERTY); | ||
if (StringUtils.isBlank(brokerID)) { | ||
if (brokerID == null || brokerID.isEmpty()) { | ||
throw new ConfigurationException("MQTT Broker property 'name' is not provided"); | ||
} | ||
brokerID = brokerID.toLowerCase(); | ||
|
||
final String brokerURL = brokerConnectionConfig.get("url"); |
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.
another question about the non null behaviour of maps: With @NonNullByDefault
we force the compiler to interpret a call to brokerConnectionConfig.get("url")
to always return a non null value. How can this be guaranteed at all? I get the impression that any call to a map is interpreted as non null value, making additional null checks obsolete. Thats a dangerous behaviour:
Object value = myMap.get("key");
if (value == null) {
throw ....
}
in this example the body of the if clause is reported as dead code. I guess for Maps the default should be Map<@NoNull KEY_TYPE, @Nullable VALUE_TYPE>
.
wdyt?
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.
another question about the non null behaviour of maps: With
@NonNullByDefault
we force the compiler to interpret a call tobrokerConnectionConfig.get("url")
to always return a non null value.
No, you define the default for your classes or packages but not for the map itself.
A map's get method could always return null e.g. your ask for a value of a non-existing key.
I guess for Maps the default should be
Map<@NoNull KEY_TYPE, @Nullable VALUE_TYPE>
.
No, this is not correct. If you look at the Map API null is allowed for the key if the implementation allows null keys (stated in the JavaDoc).
Also @NonNull K
is not correct at all, because of the upper / lower bounds of generic types. In the generic type declaration you need to use K extends @NonNull Object
.
The checker framework has a good documentation about that. I realized it a few days ago.
This PR contains three commits and it seems at least the
|
extends eclipse-archived#4290 Signed-off-by: Ana Dimova <a.dimova@prosyst.bg>
extends eclipse-archived#4290 Signed-off-by: Ana Dimova <a.dimova@prosyst.bg>
extends eclipse-archived#4290 Signed-off-by: Ana Dimova <a.dimova@prosyst.bg>
extends #4290 Signed-off-by: Ana Dimova <a.dimova@prosyst.bg>
Signed-off-by: Markus Rathgeb <maggu2810@gmail.com>
Signed-off-by: Markus Rathgeb <maggu2810@gmail.com>
Rebased |
extends eclipse-archived#4290 Signed-off-by: Ana Dimova <a.dimova@prosyst.bg>
The commits are separated, so rebase without squash should be okay.