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

WIP: Element Builder proposal #741

Closed
wants to merge 1 commit into from

Conversation

tdurieux
Copy link
Collaborator

@tdurieux tdurieux commented Jul 5, 2016

I created this PR in order to discuss the architecture of a possible new Element Builder in spoon.

The current factories are neither easy to use nor explicit. This new builder aims to solve these issues.
But I don't know yet the best solution.

This PR contains a POC of the builder. I would like to have your opinion.

Syntaxe

BinaryOperator

Builder B = factory.Builder();
CtBinaryOperator binary = B.Binary(
        B.Literal(2))
        .plus(B.Literal(1))
        .lower(
                B.Binary(B.Literal(10))
                        .minus(B.Literal(5))
        ).build();
Assert.assertEquals("(2 + 1) < (10 - 5)", binary.toString());

Try

Builder B = factory.Builder();
CtTry aTry = B.Try()
        .inBody(B.Increment(x.Literal(1)))
        .createCatch()
            .parameter("e", IllegalArgumentException.class)
            .inBody(x.Increment(x.Literal(1)))
            .close()
        .createCatch()
            .parameter("e", Exception.class)
            .close()
        .inFinally(x.Decrement(x.Literal(1)))
.build();
Assert.assertEquals("try {\n"
        + "    1++;\n"
        + "} catch (java.lang.IllegalArgumentException e) {\n"
        + "    1++;\n"
        + "} catch (java.lang.Exception e) {\n"
        + "} finally {\n"
        + "    1--;\n"
        + "}", aTry.toString());

Method

Builder x = factory.Builder();

        CtReturn<Object> aReturn = factory.Core().createReturn();
        aReturn.setReturnedExpression(x.Literal(1).build());
        CtMethod m = x.Method("m")
                .type(int.class)
                .setPrivate()
                .setStatic()
                .addParam(int.class, "x")
                .inBody(aReturn).build();
        Assert.assertEquals("private static int m(int x) {\n"
                + "    return 1;\n"
                + "}", m.toString());

Limitations

  1. The builder requires a method to indicate the end of the element (here CatchBuilder.close()) in order to get back the parent Builder (here TryBuilder). It is not intuitive...
  2. This architecture will introduce a lot of duplicate codes. Example: all elements that have a body will have the method inBody. Unfortunately the inheritance will not solve this issue (in this case we need multi inheritance)

@tdurieux tdurieux force-pushed the wip-feat-builder branch from 3cd8af0 to 07c192e Compare July 5, 2016 09:26
@alcides
Copy link
Contributor

alcides commented Jul 5, 2016

Love it! I really need this!

On Tue, Jul 5, 2016 at 10:25 AM, Thomas Durieux [email protected]
wrote:

I created this PR in order to discuss the architecture of a possible new
Element Builder in spoon.

The current factories are neither easy to use nor explicit. This new
builder aims to solve these issues.
But I don't know yet the best solution.

This PR contains a POC of the builder. I would like to have your opinion.
Syntaxe BinaryOperator

CtBinaryOperator binary = B.Binary(
B.Literal(2))
.plus(B.Literal(1))
.lower(
B.Binary(B.Literal(10))
.minus(B.Literal(5))
).build();Assert.assertEquals("(2 + 1) < (10 - 5)", binary.toString());

Try

CtTry aTry = x.Try()
.inBody(x.Increment(x.Literal(1)))
.createCatch()
.parameter("e", IllegalArgumentException.class)
.inBody(x.Increment(x.Literal(1)))
.close()
.createCatch()
.parameter("e", Exception.class)
.close()
.inFinally(x.Decrement(x.Literal(1)))
.build();Assert.assertEquals("try {\n"
+ " 1++;\n"
+ "} catch (java.lang.IllegalArgumentException e) {\n"
+ " 1++;\n"
+ "} catch (java.lang.Exception e) {\n"
+ "} finally {\n"
+ " 1--;\n"
+ "}", aTry.toString());

Limitations

The builder requires a method to indicate the end of the element (here
CatchBuilder.close()) in order to get back the parent Builder (here
TryBuilder). It is not intuitive...
2.

This architecture will introduce a lot of duplicate codes. Example:
all elements that have a body will have the method inBody.
Unfortunately the inheritance will not solve this issue (in this case we
need multi inheritance)


You can view, comment on, or merge this pull request online at:

#741
Commit Summary

  • feat(builder): WIP Builder proposal

File Changes

Patch Links:


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#741, or mute the thread
https://github.com/notifications/unsubscribe/AAAbsSKQFnnsZWCVFtpQVTYkMPvDiI2Fks5qSiMQgaJpZM4JE7ge
.

@tdurieux tdurieux force-pushed the wip-feat-builder branch from 07c192e to 35a5fbb Compare July 5, 2016 09:30
@monperrus
Copy link
Collaborator

looks like javapoet for Spoon?

@tdurieux
Copy link
Collaborator Author

tdurieux commented Jul 5, 2016

May be, I did not check.

@GerardPaligot
Copy link
Contributor

@monperrus Yes but better, with love and for Spoon! <3

@tdurieux tdurieux force-pushed the wip-feat-builder branch from 35a5fbb to 8776689 Compare July 5, 2016 11:27
public void simpleBinaryTest() {
Factory factory = new Launcher().getFactory();
Builder B = factory.Builder();
CtBinaryOperator binary = B.Binary(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

infix notation? Lit(2).times(Lit(3))

@monperrus monperrus changed the title Element Builder proposal WIP: Element Builder proposal Jul 5, 2016
@monperrus
Copy link
Collaborator

I love this idea. It will be an excellent alternative for intercession in addition to the core intercession API, snippets and templates.

@tdurieux tdurieux force-pushed the wip-feat-builder branch from 8776689 to 7411209 Compare July 5, 2016 12:15
@monperrus monperrus closed this Aug 16, 2016
@monperrus
Copy link
Collaborator

generate the builder with https://github.com/google/FreeBuilder?

@pvojtechovsky
Copy link
Collaborator

The builder requires a method to indicate the end of the element (here CatchBuilder.close()) in order to get back the parent Builder (here TryBuilder). It is not intuitive...

What about API like this?

Builder B = factory.Builder();
CtTry aTry = B.Try()
        .inBody(B.Increment(x.Literal(1)))
        .createCatch(B.Catch("e", IllegalArgumentException.class)
            .inBody(x.Increment(x.Literal(1)))
        ).createCatch(B.Catch("e", Exception.class)
        ).inFinally(x.Decrement(x.Literal(1)))
.build();

the close() can be avoided by building whole catch content as a parameter of createCatch(...)

@tdurieux
Copy link
Collaborator Author

Why not but there are a lot of parenthesis and call to the factory (that I was trying to avoid)

@pvojtechovsky
Copy link
Collaborator

pvojtechovsky commented Feb 11, 2017

I do not prefer any yet. Just giving ideas into this nice concept, to help to move it forward 👍

CtTry aTry = B.Try();
aTry.inBody(B.Increment(x.Literal(1)))
aTry.createCatch()
            .parameter("e", IllegalArgumentException.class)
            .inBody(x.Increment(x.Literal(1)))
aTry.createCatch()
            .parameter("e", Exception.class)
aTry.inFinally(x.Decrement(x.Literal(1)))
aTry.build();

or just rename of methods?

CtTry aTry = B.Try()
        .inBody(B.Increment(x.Literal(1)))
        .begingCatch()
            .parameter("e", IllegalArgumentException.class)
            .inBody(x.Increment(x.Literal(1)))
        .end()
        .beginCatch()
            .parameter("e", Exception.class)
        .end()
        .inFinally(x.Decrement(x.Literal(1)))
.build();

@tdurieux
Copy link
Collaborator Author

tdurieux commented Feb 11, 2017

I do not prefer any yet. Just giving ideas into this nice concept, to help to move it forward

Yes, it is very hard to create a good design for this builder.

or just rename of methods?

I prefer this solution

@pvojtechovsky
Copy link
Collaborator

there are a lot of parenthesis and call to the factory (that I was trying to avoid)

May be we can avoid other calls of factory too

CtTry aTry = B.Try()
        .beginTry()
            .Increment(x.Literal(1)))
        .endTry()
        .beginCatch()...

the endTry(), cannot be replaced by end(), because endTry() returns CtTryBuilder instance. The consistency of begin/end pairs would be assured in runtime by builder.

But I agree that this idea is really wild. So parenthesis and use of factory is better in this case, because we are building another spoon model element.

So the rule would be:

  • building of each spoon model element starts on factory
  • fluent API is used to fill all attributes of that one spoon model element

@tdurieux
Copy link
Collaborator Author

tdurieux commented Feb 11, 2017

I would said:

CtTry aTry = B.Try()
        .beginBody()
            .Increment(x.Literal(1)))
        .endBody()
        .beginCatch()...

@pvojtechovsky
Copy link
Collaborator

This architecture will introduce a lot of duplicate codes.

it is no problem, if code of builder will be generated.

@pvojtechovsky
Copy link
Collaborator

pvojtechovsky commented Feb 11, 2017

CtTry aTry = B.Try()
.beginBody()
   .Increment(x.Literal(1)))
.endBody()
.beginCatch()
    .parameter("e", IllegalArgumentException.class)
    .beginBody()
       .Increment(x.Literal(1)))
     .endBody()
.endCatch()

The problem of this API is "What is the return type of endBody() method"? Will be the same endBody method used in building of CtMethod?
A) If endBody returns generic SpoonModelBuilder class which is model element independent, then it there will be a lot of methods and API client will be not guided.
B) If endBody returns CtTry specific CtTryBuilder class, then how will be the Increment(..) method implemented to be able to return CtTryBuilder ??
Therefore I suggested endTry ... but I do not think that it is good way at all.

@pvojtechovsky pvojtechovsky mentioned this pull request Feb 11, 2017
2 tasks
@tdurieux
Copy link
Collaborator Author

You are right, I did not think enough

@pvojtechovsky
Copy link
Collaborator

One more idea ... to use lambda expression to generate nested elements. Like this:

CtTry aTry = factory.Builder().Try()
        .inBody(body ->
             body.Increment(x.Literal(1)))
        .createCatch(IllegalArgumentException.class, e, body->
             body.statement(st -> st.Increment(x.Literal(1)))
                     .statement(st -> st.invoke("java.lang.System.out.println()"))
        ).createCatch(Exception.class, "e")
        .inFinally(body -> 
             body.statemenet(st -> st.Decrement(x.Literal(1)))
.build();
  • autocompletion will work on each level
  • builder code has no duplicities

@tdurieux
Copy link
Collaborator Author

I can be a solution but it is only nice for Java 8.
In Java 7 it is not practicable but I don't know if it is a problem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants