phax / jcodemodel

A heavily extended fork of the com.sun.codemodel (from 2013/09)
Other
93 stars 34 forks source link

[feature] preprocessors #80

Open guiguilechat opened 4 years ago

guiguilechat commented 4 years ago

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 :

Code usage

use code I can see :

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 commented 4 years ago

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 commented 4 years ago

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.