in which case that is returned if no search result is found, or [2] there is no such marked value, in which case null is returned. I can foresee folks wanting an exception instead, but to those I say: Create an 'UNKNOWN' sentinel value and use that via @FindBy.NotFound.
I can even foresee folks wanting the method to return Optional<ET>, but we'd veto any PR that includes that functionality; we're not particularly big fans of optional, you see.
Maybe one day the feature is expanded and you can ask lombok to implement this via a map-based lookup instead; possibly if you don't explicitly specify which variant you want, lombok looks at the # of enum constants inside the enum, and picks the map mode if it's large, and the loop if it is small, with the boundary value decided by analysis of when the map will outperform the loop. But let's not start making it that complicated.
Small chance that hotspot can eliminate the inherent array copy inside the implementation lf EnumType.values(), which would suggest we should NOT make that copy. In that case, we need some sort of proof (by way of 'this bit of code in hotspot solves precisely this problem', or a JMH report). That research would be an awesome contribution, too :)Custom search mappings might be a concern. Imagine wanting to do a case insensitive search, or a search by string, but the idea is to take the string, parse it into a localdate, and then search with that. I don't see a particularly clean way to support any of this, but perhaps it can be added on later. If someone can come up with a nice design to do it, by all means, let's hear it:)
--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/project-lombok/81f1fa85-7114-4f40-8eab-4a36a1356ce4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombok+unsubscribe@googlegroups.com.
Well hey if you don't want the credit for proposing that, I'll take it :)
This is perfect; that's also a way to get to null:@FindBy.NotFound private static EnumType notFound() {return null;}The method must be static, must return the enumtype, and can optionally take 1 parameter.One annoyance is how to deal with multiply @FindBy annotations, for example one that's on String and another that's on YourCustomType, and then you have a NotFound on a method whose first param type is CharSequence. Lombok doesn't know if a YourCustomType is a subtype of CharSequence. I guess the rule is: We'll call your method, and pass in the variable. If it doesn't fit, well, a compiler error will ensue. On you to fix that / to make the first param's type 'Object' so everything works (autoboxing if needed).
I try to sum up:
@FindBy
Generates a finder method for on enum using the annotated field as search parameter.
Lombok
------------------------------------------------
@RequiredArgsConstructor
public enum Colour {
RED(255, 0, 0, "red"),
BLUE(0, 255, 0, "blue"),
GREEN(0, 0, 255, "green")
int red, blue, green;
@FindBy
String name;
}
================================================
VanillaJava
------------------------------------------------
public enum Colour {
RED(255, 0, 0, "red"),
BLUE(0, 255, 0, "blue"),
GREEN(0, 0, 255, "green")
int red, blue, green;
String name;
// private constructor generated by @RequiredArgsConstructor
// Generated by @FindBy
public static ??? findByName(String search) {
// search for enums with field 'name' is the same as parameter 'search'
}
}
------------------------------------------------
Discussion topics
===============================
How to compare the field with the search parameter?
---------------------------------------------------
Compare two objects with equals(): enumfield.equals(search)
Compare two primitives with equal: enumfield==search
Compare two floating values with equal and delta: Math.abs(enumfield-search) <= delta
What delta should be used?
Or should the search by floating values be forbidden (= compile error)
What type of return value should be used?
-----------------------------------------
When using a search we could find 0, 1 or multiple elements.
We have
InstanceType: for returning 0 or 1 element XXX
Optional<T> : for returning 0 or 1 element
List<T> : for returning any number of elements
How to specify that multiple elements could be found?
-----------------------------------------------------
We have to return a List<T>. But how tell Lombok to use a List<T> instead of T?
We could specify that on the annotation: @FindBy(LIST|SINGLE)
How should the finder behave if nothing is found?
-------------------------------------------------
A: return null
B: throw an Exception (which one?)
C: specify a default value by annotating a constant with @FindBy.Default
D: specify a default method (private static parameterless) which returns the default
D1: specify the method name in the @FindBy annotation: @FindBy(defaultMethod="nameDefault")
D2: specify the method name by name pattern: @FindBy name -> findByName() -> defaultName()
How should the finder behave if multiple elements found but only one returns?
-----------------------------------------------------------------------------
If the method can return only 1 element (T or Optional<T>) but found multiple elements, this
can be because
a) the search field is not unique
b) there is an error in the enum data
BLUE(0,0,255,"blue"),
LIGHTBLUE(0,0,128,"blue") // should be "lightblue"
The finder could
- return just the first hit or
- throw an exception (which one)
- maybe create a compiler error?
Performance hints
-----------------
- Cache the return of values() in a private static array instead of using values() in each generated finder.
Future concerns
---------------
- Maybe one day the feature is expanded and you can ask lombok to implement this via a map-based lookup instead;
possibly if you don't explicitly specify which variant you want, lombok looks at the # of enum constants inside the enum,
and picks the map mode if it's large, and the loop if it is small, with the boundary value decided by analysis of when
the map will outperform the loop. But let's not start making it that complicated.
- Small chance that hotspot can eliminate the inherent array copy inside the implementation of EnumType.values(),
which would suggest we should NOT make that copy. In that case, we need some sort of proof
(by way of 'this bit of code in hotspot solves precisely this problem', or a JMH report).
That research would be an awesome contribution, too :)
- Custom search mappings might be a concern. Imagine wanting to do a case insensitive search, or a search by string,
but the idea is to take the string, parse it into a localdate, and then search with that.
I don't see a particularly clean way to support any of this, but perhaps it can be added on later.
If someone can come up with a nice design to do it, by all means, let's hear it:)
"Management Requirement"
------------------------
1. The API is such that it'll search directly (loop over all enum values until a match is found),
using ==, for all primitives except double and float.
2. @FindBy on a float or double value is an error
(equality for these things is just too hairy to deal with).
3. For non-primitives, .equals() is used, and null is dealt with correctly (not NPE, but == instead).
There is either no way to specify alternate equalities (notably, no way to search case insensitively),
or you have a _REALLY_ good idea as to how to try to tackle it.
4. the code will make a local (private static final EnumType[] VALUES = values();) copy of the values() invocation.
(values() is an expensive method, you don't want to call it all that often). Alternatively prove that hotspot will optimize it.
5. PR includes the implementation for both eclipse as well as javac, AND the docs page + a bunch of tests.
6. As the feature will start in experimental, for now no need to coordinate with the intellij plugin team on adding it there.
7. The behaviour is such that if the result is not found, one of two things happens:
Either [1] one of the enum values is explicitly marked as the not found default (with the @FindBy.NotFound annotation),
in which case that is returned if no search result is found, or
[2] there is no such marked value, in which case null is returned.
I can foresee folks wanting an exception instead, but to those I say: Create an 'UNKNOWN' sentinel value and use that via @FindBy.NotFound.
I can even foresee folks wanting the method to return Optional<ET>, but we'd veto any PR that includes that functionality;
we're not particularly big fans of optional, you see.
Proposal "keep it easy"
=================================================
* Comparison: dont allow @FindBy on floating values.
Create a compiler error.
User could still write their own finder methods directly in the enum.
* Don't generate code if there is the method already exists.
* You could specify the return type as SINGLE or LIST
@FindBy(SINGLE): (default) return in instance of the enum type: T
If nothing is found, null is returned.
Users could wrap that into an Optional by their own:
Optional.ofNullable(Colour.findByName("unknown")).orElse(RED)
@FindBy(LIST): return a list of instances of the enum type: List<T>
If nothing is found, the list is just empty.
* If the return type is SINGLE and there are multiple hits throw a runtime exception.
Better would be to create a compiler error instead. Is this possible?
* Support default return values by providing a method by name pattern.
If a method default{FieldName} exists use its return value:
@FindBy(LIST) : private static List<T> defaultForName() { ... }
@FindBy(SINGLE): private static T defaultForName() { ... }
Step 1: Agree on Proposal
Step 2: Specify examples
Step 3: 'Translate' examples to tests
Step 4: Implement for one compiler
Step 5: Discuss ;)
Step 6: Implement for other compiler
Step 7: Write doc
Lombok
------------------------------------------------
@RequiredArgsConstructor
public enum Colour {
RED(255, 0, 0, "red", "rot", false),
BLUE(0, 255, 0, "blue", "blau", true),
GREEN(0, 0, 255, "green", "grün", false)
int red, blue, green;
@FindBy
String englishName;
@FindBy
String germanName;
@FindBy(LIST)
boolean safeForRedGreenColourBlindness;
private static Colour defaultForGermanName() {
return RED;
}
}
================================================
VanillaJava
------------------------------------------------
public enum Colour {
RED(255, 0, 0, "red", "rot", false),
BLUE(0, 255, 0, "blue", "blau", true),
GREEN(0, 0, 255, "green", "grün", false)
int red, blue, green;
String englishName;
String germanName;
boolean safeForRedGreenColourBlindness;
private static Colour defaultForGermanName() {
return RED;
}
// Generated by @RequiredArgsConstructor
private Colour(int red, int blue, int green, String englishName, String germanName, boolean safeForRedGreenColourBlindness) {
this.red = red;
this.blue = blue;
this.green = green;
this.englishName = englishName;
this.germanName = germanName;
this.safeForRedGreenColourBlindness = safeForRedGreenColourBlindness;
}
// Generated by @FindBy
private static Colour[] VALUES = values();
public static Colour findByEnglishName(String search) {
List<Colour> found = new ArrayList<>();
for(Colour actual : VALUES) {
if (actual.englishName == null) {
if (search == null) {
found.add(actual);
}
} else {
if (actual.englishName.equals(search)) {
found.add(actual);
}
}
}
if (found.isEmpty) {
return defaultForEnglishName();
}
if (found.size()==1) {
return found.get(0);
}
throw new RuntimeException();
}
private static Colour defaultForEnglishName() {
return null;
}
public static Colour findByGermanName(String search) {
// same as in findByEnglishName()
// ...
if (actual.germanName == null) {
// ...
} else {
if (actual.germanName.equals(search)) {
// ...
if (found.isEmpty) {
return defaultForGermanName();
}
// ...
}
public static List<Colour> findBySafeForRedGreenColourBlindness(boolean search) {
List<Colour> found = new ArrayList<>();
for(Colour actual : VALUES) {
// no null check required for primitives
if (actual.safeForRedGreenColourBlindness == search) {
found.add(actual);
}
}
return found;
// no defaults for lists
}
}
------------------------------------------------
What do you think?
Jan
- Maybe one day the feature is expanded and you can ask lombok to implement this via a map-based lookup instead;
possibly if you don't explicitly specify which variant you want, lombok looks at the # of enum constants inside the enum,
and picks the map mode if it's large, and the loop if it is small, with the boundary value decided by analysis of when
the map will outperform the loop. But let's not start making it that complicated.
- Small chance that hotspot can eliminate the inherent array copy inside the implementation of EnumType.values(),
which would suggest we should NOT make that copy. In that case, we need some sort of proof
(by way of 'this bit of code in hotspot solves precisely this problem', or a JMH report).
That research would be an awesome contribution, too :)
- Custom search mappings might be a concern. Imagine wanting to do a case insensitive search, or a search by string,
but the idea is to take the string, parse it into a localdate, and then search with that.
I don't see a particularly clean way to support any of this, but perhaps it can be added on later.
If someone can come up with a nice design to do it, by all means, let's hear it:)
"Management Requirement"
------------------------
1. The API is such that it'll search directly (loop over all enum values until a match is found),
using ==, for all primitives except double and float.
2. @FindBy on a float or double value is an error
(equality for these things is just too hairy to deal with).
3. For non-primitives, .equals() is used, and null is dealt with correctly (not NPE, but == instead).
There is either no way to specify alternate equalities (notably, no way to search case insensitively),
or you have a _REALLY_ good idea as to how to try to tackle it.
4. the code will make a local (private static final EnumType[] VALUES = values();) copy of the values() invocation.
(values() is an expensive method, you don't want to call it all that often). Alternatively prove that hotspot will optimize it.
5. PR includes the implementation for both eclipse as well as javac, AND the docs page + a bunch of tests.
6. As the feature will start in experimental, for now no need to coordinate with the intellij plugin team on adding it there.
7. The behaviour is such that if the result is not found, one of two things happens:
Either [1] one of the enum values is explicitly marked as the not found default (with the @FindBy.NotFound annotation),
in which case that is returned if no search result is found, or
[2] there is no such marked value, in which case null is returned.
I can foresee folks wanting an exception instead, but to those I say: Create an 'UNKNOWN' sentinel value and use that via @FindBy.NotFound.
I can even foresee folks wanting the method to return Optional<ET>, but we'd veto any PR that includes that functionality;
we're not particularly big fans of optional, you see.
Proposal "keep it easy"
this.englishName = englishName;
this.germanName = germanName;
this.safeForRedGreenColourBlindness = safeForRedGreenColourBlindness;
- Maybe one day the feature is expanded and you can ask lombok to implement this via a map-based lookup instead;
possibly if you don't explicitly specify which variant you want, lombok looks at the # of enum constants inside the enum,
and picks the map mode if it's large, and the loop if it is small, with the boundary value decided by analysis of when
the map will outperform the loop. But let's not start making it that complicated.
- Small chance that hotspot can eliminate the inherent array copy inside the implementation of EnumType.values(),
which would suggest we should NOT make that copy. In that case, we need some sort of proof
(by way of 'this bit of code in hotspot solves precisely this problem', or a JMH report).
That research would be an awesome contribution, too :)
- Custom search mappings might be a concern. Imagine wanting to do a case insensitive search, or a search by string,
but the idea is to take the string, parse it into a localdate, and then search with that.
I don't see a particularly clean way to support any of this, but perhaps it can be added on later.
If someone can come up with a nice design to do it, by all means, let's hear it:)
"Management Requirement"
------------------------
1. The API is such that it'll search directly (loop over all enum values until a match is found),
using ==, for all primitives except double and float.
2. @FindBy on a float or double value is an error
(equality for these things is just too hairy to deal with).
3. For non-primitives, .equals() is used, and null is dealt with correctly (not NPE, but == instead).
There is either no way to specify alternate equalities (notably, no way to search case insensitively),
or you have a _REALLY_ good idea as to how to try to tackle it.
4. the code will make a local (private static final EnumType[] VALUES = values();) copy of the values() invocation.
(values() is an expensive method, you don't want to call it all that often). Alternatively prove that hotspot will optimize it.
5. PR includes the implementation for both eclipse as well as javac, AND the docs page + a bunch of tests.
6. As the feature will start in experimental, for now no need to coordinate with the intellij plugin team on adding it there.
7. The behaviour is such that if the result is not found, one of two things happens:
Either [1] one of the enum values is explicitly marked as the not found default (with the @FindBy.NotFound annotation),
in which case that is returned if no search result is found, or
[2] there is no such marked value, in which case null is returned.
I can foresee folks wanting an exception instead, but to those I say: Create an 'UNKNOWN' sentinel value and use that via @FindBy.NotFound.
I can even foresee folks wanting the method to return Optional<ET>, but we'd veto any PR that includes that functionality;
we're not particularly big fans of optional, you see.
Proposal "keep it easy"
this.englishName = englishName;
this.germanName = germanName;
this.safeForRedGreenColourBlindness = safeForRedGreenColourBlindness;