Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] preprocessors #80

Open
guiguilechat opened this issue Jan 22, 2020 · 2 comments
Open

[feature] preprocessors #80

guiguilechat opened this issue Jan 22, 2020 · 2 comments
Labels

Comments

@guiguilechat
Copy link

guiguilechat commented Jan 22, 2020

I am thinking of a new feature, the preprocessor that can add data in the JCM before it is built.

Use cases

use cases I can think of :

  • add @Override on the created classes that declare a method that overrides a parent's.
  • add @author on classes.
  • for a set of the classes generated, automatically add getter for the fields, and setters for non-final fields
  • for the classes that don't have it and declare fields, override the hashcode() and/or equals(Object) methods.
  • add @Immutable on classes that inherit from an @Immutable class or a java class that does not have mutable field ; and all fields are final with type immutable.
  • (not sure) implement methods as calls to specific static class methods in abstract methods that implement interface.
    eg if class CClass extends abstract class AAbstract, and implements IInterface{int one();}, with a class SInterfaceHelper{int one(IInterface _this){return 1;}} ;
    Then the processor would add the method @Override int one(){return SInterfaceHelper.one(this);} in CClass .

Code usage

use code I can see :

  • The user loads the OverrideProcessor with jcm.processor(OverrideProcessor.class) . On the preprocess phase (in the build() ) the processor does its job. Same for the ImmutableProcessor.
  • The user loads the AuthorProcessor and sets the author with jcm.processor(AuthorProcessor.class).authors("me", "another") . This processor will adds the corresponding author annotations to the classes that don't have them already.
  • The user loads the BeanProcessor with beanproc = jcm.processor(BeanProcessor.class). He then declares classes as bean with beanproc.add(myJCMClass); . This processor will add the missing methods.
  • The user loads the EqualityProcessor and asks to add equals and hashcode with jcm.processor(EqualityProcessor.class).addEquals().addHashCode();. If a class must not have the methods added, eg because one field can be set to a recursive self reference (like a node in a tree, its children can ref their parent), that class , or its package, can be ignore with eg EqualityProcessor::ignore(JDefinedClass) ; or the processor can be limited to a set of classed and package, with eg EqualityProcessor::limit(JPackage)

Issues

Multiple Processors

If several processors are added, the order in which they are applied can modify the final result. If a processor adds a field, and the BeanProcessor adds methods depending on the fields, the method will only be added IFF the beanprocessor is applied after the fieldprocessor.

To resolve this, as long as at least one processor did modify the JCM during the application, each processor must be applied again. This process will be repeated until no processor has an impact on the JCM.

This however requires that the processors make optional changes, eg the addition of @author is only done if not already present - but also, the addition of setter and getter would not be done, and would not thrown an exception, if the setter/getter is already here. This specific getter/setter issue can be avoided, by passing a boolean parameter as true for the initial pass, and therefore throwing an exception only if a method already exists and during the initial pass.

Processors cleanup

Another issue is, that after a build the JCM is modified. That means that a JCM is dirty after the build, and can't be used anymore. Typically adding the getter once will be fine, but building the JCM again would make the BeanProcessor throw an exception.

Ideally we would want the JCM to be able to revert the additions made by the processors.That means, the processors would store the modification they made, and would enable to undo them (in the reverse order they were made). However this is not easy task, and can lead to issues. We could also lock the JCM and make all the classes delegate modifications to the JCM, when it is locked, to a system that would store them. This would however need a lot of tests, and again prone to errors.

Another possibility is to make the processors work on a copy of the JCM. However since the variables (eg the Jclasses that need to be beanified) stored in the processors refer to the real JCM, this would require a lot of work, and potentially to lock the classes again.

Two solutions seems however good for me. The first one, is to accept that a JCM can only be built once. The second one, is to store the JCM and restore it after the build(). Of course, the first one is the easiest.

Generation errors

What happens if a processor is asked to create a JElement that already exists ? Should it fail ? I think it depends on the processor, and the pass it is being used on. IMO a getter that can't be created because a fucntion already exists with that name, should make the full build fail.

@guiguilechat
Copy link
Author

Q : Why not do a list of script with parameters , eg new BeanScript().withClass(myJCMClass).apply(jcm) ?
A : first, for the reason of ordering : the order in which those scripts would be applied would impact the result. Secondly, because calling the script again in another par of the code could lead to issues, therefore the JCM must be used to ensure there is only one instance of each available.

guiguilechat pushed a commit to guiguilechat/jcodemodel that referenced this issue Jan 22, 2020
@guiguilechat
Copy link
Author

Another use case : the generation of a cached value from a generator method.

typically, either the method takes no argument, therefore one item is cached. Or the method takes an argument, and a map stores the cached generated items .

By applying the processor on those two methods :

    private int makeNext(int value) {
        return (value + 1);
    }

    private int make100() {
        return  100;
    }

It results in the following code being added :

    private HashMap<Integer, Integer> next = new HashMap<Integer, Integer>();
    private int hundred = null;


    public int getNext(int value) {
        int ret = next.get(value);
        if (ret == null) {
            ret = makeNext(value);
            next.put(value, ret);
        }
        return ret;
    }

    public int getHundred() {
        if (hundred == null) {
            hundred = make100();
        }
        return hundred;
    }

Still needs to be synchronized if required, to add comments, and to clean the code.

@phax phax added the pinned label Apr 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants