Transforming java source code to move annotations from getters to properties

397 Views Asked by At

I have about 300 JPA entities where the getters are annotated with persistence annotations. I would like to find a way to move all such annotations to the properties instead and remove all getters and setters. I did this manually for about 100 of these classes but it's very time consuming and mind numbing work.

I'm looking at source code transformation tools like Spoon but still not sure it can do what I need it to do.

More specifically, how can I transform this code:

@Entity
@Table(name = "crm_ticket")
public class CrmTicket implements Serializable {

    private static final long serialVersionUID = -902718555957517699L;

    private CrmAccount crmAccount;
    private ItsType subType;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "account")
    public CrmAccount getCrmAccount() {
        return crmAccount;
    }

    public void setCrmAccount(CrmAccount crmAccount) {
        this.crmAccount = crmAccount;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sub_type")
    public ItsType getSubType() {
        return subType;
    }

    public void setSubType(ItsType type) {
        this.subType = type;
    }
}

To this:

@Entity
@Table(name = "crm_ticket")
@Data
public class CrmTicket implements Serializable {

    private static final long serialVersionUID = -902718555957517699L;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "account")
    private CrmAccount crmAccount;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sub_type")
    private ItsType subType;
}
2

There are 2 best solutions below

0
On BEST ANSWER

I ended up using Spoon. It wasn't that painful. I configured their maven plugin to run my processor and it transformed the code of my Entity classes. I then copied the generated code back to my project and removed the plugin configuration.

Here's my processor code:

public class JpaAnnotationMover extends AbstractProcessor<CtMethod> {

    Pattern p1 = Pattern.compile("return.*this\\.(.*?)$");
    Pattern p2 = Pattern.compile("return(.*?)$");

    @Override
    public boolean isToBeProcessed(CtMethod method) {
        return isInEntity(method) && isAGetter(method) && hasOneStatement(method) && !isTransient(method);
    }

    @Override
    public void process(CtMethod ctMethod) {
CtType parentClass = ctMethod.getParent(CtType.class);
        String fieldName = getFieldName(ctMethod);

        if (fieldName == null) {
            log.warn(String.format("expected field name for %s not found.", ctMethod.getSimpleName()));
            return;
        }

        CtField field = parentClass.getField(fieldName);
        if (field == null) {
            log.warn(String.format("Expected field %s not found.", fieldName));
            return;
        }

        for (CtAnnotation<? extends Annotation> annotation : ctMethod.getAnnotations()) {
            field.addAnnotation(annotation);
        }

        parentClass.removeMethod(ctMethod);
        log.info(String.format("Processed method %s:%s", parentClass.getSimpleName(), ctMethod.getSimpleName()));

        // find corresponding setter
        String setterName = "set" + WordUtils.capitalize(fieldName);

        @SuppressWarnings("unchecked") CtMethod setter = parentClass
                .getMethod(getFactory().Type().createReference("void"), setterName, ctMethod.getType());

        if (setter == null) {
            log.warn(String.format("Unable to find setter for %s", fieldName));
            return;
        }

        parentClass.removeMethod(setter);

        if (!parentClass.hasAnnotation(Data.class)) {
            parentClass.addAnnotation(getFactory().createAnnotation(getFactory().Type().createReference(Data.class)));
        }
}

private Boolean isInEntity(CtMethod method) {
        CtType parentClass = method.getParent(CtType.class);
        return parentClass.hasAnnotation(Entity.class);
    }

    private Boolean isAGetter(CtMethod method) {
        return method.getSimpleName().contains("get");
    }

    private Boolean hasOneStatement(CtMethod method) {
        return method.getBody().getStatements().size() == 1;
    }

    private Boolean isTransient(CtMethod method) {
        return method.hasAnnotation(Transient.class);
    }

    private String getFieldName(CtMethod method) {
        String statement = method.getBody().getLastStatement().toString();
        Matcher m = p1.matcher(statement);
        Matcher m2 = p2.matcher(statement);
        return m.matches() ? m.group(1).trim() : m2.matches() ? m2.group(1).trim() : null;
    }
}
0
On

Spoon would work well for this, you would use aField.addAnnotation and aSetter.delete.