Comments (12)
Hey, would you be interested in giving the new functionality a try before I finish implementing it? If yes, you need to build this library yourself from the feature/postprocessors branch.
What I did is I implemented both our suggestions: You can use either way to post-process a class (or record) or both at the same time, like in the following example:
@Configuration class MyConfig { @PostProcess(key = "my_mini_message") String message1 = "..."; @PostProcess(key = "my_mini_message") String message2 = "..."; // method is executed AFTER post-processors from annotation @PostProcess private void postProcess() { // method name can be anything message2 = message2.repeat(3); // ... } } ConfigurationProperties.newBuilder() .addPostProcessor( ConfigurationElementFilter.byPostProcessKey("my_mini_message"), (String input) -> input.repeat(2) // or what ever post-processor you want ) .build();I'm not done testing everything but I'm already confident that there won't be any API changes. However, I still have a few TODOs open that will cause some expections to be thrown earlier (in order to catch misconfiguration from developers).
I hope that I'll find time to finish this request this weekend, although I can't promise anything since I'm quite busy with my work these days.
Sure, I also busy in these day, I will give a try for the new feature when I free
from configlib.
Hey, have you had time to have a look? I also now (more or less) finished the documentation, if you want to have a look: Post-processing
from configlib.
Now available in version 4.5.0.
from configlib.
Hey, thanks also for your second suggestion. 🙂
My first approach to this would be to make the following three additions to this library:
First, some annotation that is used to filter fields:
public @interface PostProcess {
String key();
}
Second, a new method added to the ConfigurationProperties.Builder
class that is used to add postprocessors:
public final <T> B addPostProcessor(
FieldFilter filter,
Function<T, T> processor
)
Third, a new static factory to create FieldFilter
objects:
public static FieldFilter filterByPostProcessAnnotation(String key) {
return field -> {
PostProcess annotation = field.getAnnotation(PostProcess.class);
return (annotation != null) && (annotation.key().equals(key));
};
}
With these three additions you could then write
@Configuration
class MyConfig {
@PostProcess(key = "my_mini_message")
String message1 = "...";
@PostProcess(key = "my_mini_message")
String message2 = "...";
}
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addFieldPostProcessor(
filterByPostProcessAnnotation("my_mini_message"),
MyUtilsClass::translateColor
)
.build();
What do you think about this implementation? Does it meet your requirements?
from configlib.
Sound great!
from configlib.
I was thinking a little bit more about your request and I wonder whether this additional complexity in the API is really necessary.
Instead of using annotations and adding postprocessors, how about you simply define a method in your configuration class and call that method once your configuration has been loaded:
@Configuration
class MyConfig {
String message1 = "...";
String message2 = "...";
public void postProcess() {
this.message1 = MyUtilsClass.translateColor(this.message1);
this.message2 = MyUtilsClass.translateColor(this.message2);
}
}
MyConfig config = // load config ...
config.postProcess();
// ... use config
In my opinion, this approach is (a) simpler because you don't have to learn an additional API but can simply stick to normal Java code and (b) much more flexible because you don't have to define a new processor for each distinct field type or transformation that you want to apply.
All in all, I don't think this API is necessary, but maybe I'm missing something?
from configlib.
In my opinion, this approach is (a) simpler because you don't have to learn an additional API but can simply stick to normal Java code and (b) much more flexible because you don't have to define a new processor for each distinct field type or transformation that you want to apply.
All in all, I don't think this API is necessary, but maybe I'm missing something?
I agree with your point that this approach has made the library easier to use and requires a less steep learning curve. However:
a) It ultimately creates difficulties for users of the library because they need to manually write the postProcess method where necessary.
b) Consider a configuration file like customNPC.yml that contains many nested classes. Users will be required to invoke the postProcess method of each subclass within the postProcess method of the main class. This is also a pain point that I have encountered.
c) As for data class complexity - as someone who appreciates the fundamentals of OOP, I prefer to separate data classes and actual code logic into different instances. Adding a postProcess method introduces additional complexity to the class. While I am comfortable with including data processing within the data class, it does make the class more complex, especially when dealing with nested objects.
d) Reducing code errors - manually calling a method after the file has been parsed into an object is an unnecessary line of code and can easily be overlooked. Although you can use a wrapper instance to call the method after the library has parsed the file, the question remains: why should this be necessary?
e) The most important issue is the FINAL modifier. What if the field is final? Methods are not allowed to rewrite final fields.
This is the problem whether the library offers a better solution for the user or requires the user to resolve issues with the library itself. Accually Im pretty okay at opening a PR contributing this library, but regarding to adding a new API / structure change, I still think you are the best to add/decides as you created this library.
from configlib.
It ultimately creates difficulties for users of the library because they need to manually write the postProcess method where necessary.
I don't really see that as a problem. To postprocess your fields you'll have to write at least some code. With the new approach, instead of having to write a single postProcess
method you'd have to write one or multiple postprocessor functions and annotate each and every field to which you want to apply these functions.
The most important issue is the FINAL modifier. What if the field is final? Methods are not allowed to rewrite final fields.
That's not an issue because this library ignores final
fields as they are, as their name says, final. So there would be no difference to the current behavior in this case.
Reducing code errors - manually calling a method after the file has been parsed into an object is an unnecessary line of code and can easily be overlooked.
I don't really agree with this. Unless you don't test your code at all, you should immediately spot that the values are not correct and then add the missing call. In your example above, you'd see that the colors are all messed up.
Consider a configuration file like customNPC.yml that contains many nested classes.
I agree that postprocessing can be cumbersome if you are working with nested classes. I see two alternative approaches that should solve this issue:
The first is to introduce a PostProcess
annotation that can be applied to a method in your configuration class. If such an annotated method exists, it is called after an instance of that class has been initialized. In the following example, s
would be converted to uppercase and i
would be multiplied by 10 when initializing MainConfig
:
@Configuration
static final class MainConfig {
private String s = "a";
private NestedConfig nested = new NestedConfig();
@PostProcess
private void postProcess() {
s = s.toUpperCase();
}
}
@Configuration
static final class NestedConfig {
private int i = 10;
@PostProcess
private void postProcess() {
i = i * 10;
}
}
The second approach is to introduce a PostProcess
annotation that can be applied at the type level (i.e. to some configuration class) and that requires one to provide another class that resembles some kind of processor (for example, a class implements the Consumer
interface):
@Configuration
@PostProcess(MainConfigPostProcessor.class)
static final class MainConfig {
private String s = "a";
private NestedConfig nested = new NestedConfig();
}
@Configuration
@PostProcess(NestedConfigPostProcessor.class)
static final class NestedConfig {
private int i = 10;
}
static final class MainConfigPostProcessor implements Consumer<MainConfig> {
@Override
public void accept(MainConfig mainConfig) {
// requires fields to be accessible or, alternatively, setters
mainConfig.s = mainConfig.s.toUpperCase();
}
}
static final class NestedConfigPostProcessor implements Consumer<NestedConfig> {
@Override
public void accept(NestedConfig nestedConfig) {
nestedConfig.i = nestedConfig.i * 10;
}
}
Alternatively, instead of using the PostProcess
annotation, defining postprocessors for types could also be possible through a ConfigurationProperties
object:
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addPostProcessor(MainConfig.class, new MainConfigPostProcessor())
.addPostProcessor(NestedConfig.class, new NestedConfigPostProcessor())
.build().
The first of these three approaches looks much simpler to me which is also why I prefer it. The second and third approach take into consideration your argument (c) which I agree is also a valid point (although you'd probably have to write setters to not break encapsulation).
from configlib.
That's not an issue because this library ignores final fields as they are, as their name says, final. So there would be no difference to the current behavior in this case.
But postProcess method dont. It cant rewrite the field
final String message; //Library load well
public void postProcess() {
message = MyUtilsClass.color(message); //Compiler error :(
}
I don't really agree with this. Unless you don't test your code at all, you should immediately spot that the values are not correct and then add the missing call. In your example above, you'd see that the colors are all messed up.
It just increase the rate you misslook the code. Easier to see whether the field got post process than write the code down in a method.
In your solution, the final field cannot be rewritten in the method we use @PostProcess
. The solution I hope for is to put the @PostProcess
annotation on the field, and the library will go through the post processors first before setting the value into the field. As you said, this library ignores final, which solves the problem of "final field cannot be rewritten".
For the third solution (alternative solution) you provided, I still hope there is a matcher(with interface) to allow user to do their own matching or use default provided matcher. User may need to match field type, annotation matcher, field name matcher if post process run with field. Same as class if you choose to use your solution.
Regarding your solution, there is a fact that while users are doing the same post-process across their config:
a) the problem I mentioned above (final cannot be rewritten)
b) Sometimes users are doing the same thing across their config, for example, translating colors:
class ConfigA {
String message;
}
class ConfigB {
String message;
}
Users will be required to code the same thing twice:
class ConfigA {
String message;
@PostProcess
void post() {
message = XXXXXXXX;
}
}
class ConfigB {
String message;
@PostProcess
void post() {
message = XXXXXXXX;
}
}
It ends up with a lot of copying and pasting code (and modifying the variable name themselves), which makes no sense compared to the first solution I suggested above.
Actually, pre-processors also have their own use case, but I just requested a post processor (which I need this a lot). Different people had their own usage, this is why those famous libraries (e.g, Jackson - Converter class) exist this feature.
But! Your solution is still acceptable. I will just create a global PostProcessor consumer class and implement 'suggestions in my first comment' (using annotation per field), but instead of making it easier for me to use this library, your solution just gave me availability to use my own code to use this library easier (instead of forking). I respect your choice.
from configlib.
When I said that the library ignores final
fields I didn't mean that it ignores the final
modifier. If a field is final
it is neither written when saving a configuration instance nor set/updated when loading a configuration. This is documented in the README under the Ignoring and filtering fields section.
In any case, I see that there can be value in custom matchers. Perhaps offering both solutions is the way to go then.
from configlib.
When I said that the library ignores
final
fields I didn't mean that it ignores thefinal
modifier. If a field isfinal
it is neither written when saving a configuration instance nor set/updated when loading a configuration. This is documented in the README under the Ignoring and filtering fields section.
Oh! Sorry for my miss understanding.
In any case, I see that there can be value in custom matchers. Perhaps offering both solutions is the way to go then.
Sure! That will be nice!
from configlib.
Hey, would you be interested in giving the new functionality a try before I finish implementing it? If yes, you need to build this library yourself from the feature/postprocessors branch.
What I did is I implemented both our suggestions: You can use either way to post-process a class (or record) or both at the same time, like in the following example:
@Configuration
class MyConfig {
@PostProcess(key = "my_mini_message")
String message1 = "...";
@PostProcess(key = "my_mini_message")
String message2 = "...";
// method is executed AFTER post-processors from annotation
@PostProcess
private void postProcess() { // method name can be anything
message2 = message2.repeat(3);
// ...
}
}
ConfigurationProperties.newBuilder()
.addPostProcessor(
ConfigurationElementFilter.byPostProcessKey("my_mini_message"),
(String input) -> input.repeat(2) // or what ever post-processor you want
)
.build();
I'm not done testing everything but I'm already confident that there won't be any API changes. However, I still have a few TODOs open that will cause some expections to be thrown earlier (in order to catch misconfiguration from developers).
I hope that I'll find time to finish this request this weekend, although I can't promise anything since I'm quite busy with my work these days.
from configlib.
Related Issues (20)
- Add option to ignore fields from serialization HOT 1
- GitHub authentication problem HOT 8
- [Help] Unknown class deserialization HOT 16
- Add support for java 16+ HOT 5
- How does the library handle breaking changes(examples provided)? HOT 2
- [BUG] Unable to Initialize "Configuration class 'ConfigYAML' does not contain any (de-)serializable fields." HOT 2
- Achieve cross-version support by making the library compatible with Java 8 HOT 5
- Shade ConfigLib using Java 8 HOT 2
- Charset string problem HOT 6
- Add better support for updating configurations HOT 2
- Allow loading configs from internal JAR resources via an InputStream HOT 5
- Native support for Adventure API Component HOT 2
- Not working jitpack.io HOT 4
- Will comments be added in lists/maps etc? HOT 2
- Publish to maven central HOT 3
- Polymorphic problem HOT 5
- Enums as Keys in Maps HOT 2
- Comments are not processed HOT 1
- Server is offline HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from configlib.