Skip to content

Commit 8f414b9

Browse files
committed
add StringBuilderGen to the gizmo2 module
This is a simple generator of `StringBuilder` call chains. It exists outside of Gizmo because of unresolved disputes over its API design, particularly around reusability.
1 parent 9c43adc commit 8f414b9

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed

gizmo2/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
<artifactId>junit-jupiter</artifactId>
3333
<scope>test</scope>
3434
</dependency>
35+
<dependency>
36+
<groupId>io.quarkus.gizmo</groupId>
37+
<artifactId>gizmo2</artifactId>
38+
<type>test-jar</type>
39+
<scope>test</scope>
40+
</dependency>
3541
</dependencies>
3642

3743
<build>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package org.jboss.jandex.gizmo2;
2+
3+
import io.quarkus.gizmo2.Const;
4+
import io.quarkus.gizmo2.Expr;
5+
import io.quarkus.gizmo2.Var;
6+
import io.quarkus.gizmo2.creator.BlockCreator;
7+
import io.quarkus.gizmo2.creator.ops.ComparableOps;
8+
import io.quarkus.gizmo2.creator.ops.ObjectOps;
9+
10+
/**
11+
* Generator of {@link StringBuilder} call chains. The expected usage pattern is:
12+
* <ol>
13+
* <li>Create an instance using {@link #ofNew(BlockCreator)}</li>
14+
* <li>Append to it using {@link #append(Expr)}</li>
15+
* <li>Create the final string using {@link #toString_()}</li>
16+
* </ol>
17+
* If you need to perform other operations on the {@code StringBuilder}
18+
* that this class doesn't provide, you should create an instance
19+
* using {@link #of(Var, BlockCreator)}, which allows passing an already
20+
* created {@code StringBuilder}. This class itself doesn't provide access
21+
* to the underlying object.
22+
*/
23+
public final class StringBuilderGen extends ObjectOps implements ComparableOps {
24+
/**
25+
* Allocates a local variable in the given block as if by {@code new StringBuilder()}
26+
* and passes it to {@link #of(Var, BlockCreator)}.
27+
*
28+
* @param bc the block in which the new {@code StringBuilder} should be created
29+
* @return the result of {@link #of(Var, BlockCreator)} called on the new {@code StringBuilder}
30+
*/
31+
public static StringBuilderGen ofNew(BlockCreator bc) {
32+
return new StringBuilderGen(bc, bc.localVar("$$stringBuilder", bc.new_(StringBuilder.class)));
33+
}
34+
35+
/**
36+
* Allocates a local variable in the given block as if by {@code new StringBuilder(capacity)}
37+
* and passes it to {@link #of(Var, BlockCreator)}.
38+
*
39+
* @param bc the block in which the new {@code StringBuilder} should be created
40+
* @return the result of {@link #of(Var, BlockCreator)} called on the new {@code StringBuilder}
41+
*/
42+
public static StringBuilderGen ofNew(int capacity, BlockCreator bc) {
43+
return new StringBuilderGen(bc, bc.localVar("$$stringBuilder", bc.new_(StringBuilder.class, Const.of(capacity))));
44+
}
45+
46+
/**
47+
* Creates a {@code StringBuilder} generator that helps to generate a chain of
48+
* {@code append} calls and a final {@code toString} call.
49+
*
50+
* <pre>
51+
* StringBuilderGen str = StringBuilderGen.of(theStringBuilder, bc);
52+
* str.append("constant");
53+
* str.append(someExpr);
54+
* Expr result = str.toString_();
55+
* </pre>
56+
*
57+
* The {@code append} method mimics the regular {@code StringBuilder.append}, so
58+
* it accepts {@code Expr}s of all types for which {@code StringBuilder}
59+
* has an overload:
60+
* <ul>
61+
* <li>primitive types</li>
62+
* <li>{@code char[]}</li>
63+
* <li>{@code java.lang.String}</li>
64+
* <li>{@code java.lang.CharSequence}</li>
65+
* <li>{@code java.lang.Object}</li>
66+
* </ul>
67+
*
68+
* Notably, arrays except of {@code char[]} are appended using {@code Object.toString}
69+
* and if {@code Arrays.toString} should be used, it must be generated manually
70+
* (see {@link BlockCreator#arrayToString(Expr)}).
71+
* <p>
72+
* Methods for appending only a part of {@code char[]} or {@code CharSequence} are not
73+
* provided.
74+
* <p>
75+
* Note that the returned instance <em>may be reused</em> to append to the same {@code StringBuilder}
76+
* in the same {@code BlockCreator} multiple times. This allows using {@code StringBuilderGen}
77+
* in the same manner a {@code StringBuilder} would normally be used.
78+
*
79+
* @param stringBuilder the {@link StringBuilder}
80+
* @param bc the {@link BlockCreator}
81+
* @return a convenience wrapper for accessing instance methods of the given {@link StringBuilder}
82+
*/
83+
public static StringBuilderGen of(Var stringBuilder, BlockCreator bc) {
84+
return new StringBuilderGen(bc, stringBuilder);
85+
}
86+
87+
private StringBuilderGen(final BlockCreator bc, final Var obj) {
88+
super(StringBuilder.class, bc, obj);
89+
}
90+
91+
/**
92+
* Appends the string value of given {@code expr} to this {@code StringBuilder}.
93+
*
94+
* @param expr the value to append
95+
* @return this instance
96+
*/
97+
public StringBuilderGen append(final Expr expr) {
98+
switch (expr.type().descriptorString()) {
99+
case "Z" -> invokeInstance(StringBuilder.class, "append", boolean.class, expr);
100+
case "B", "S", "I" -> invokeInstance(StringBuilder.class, "append", int.class, expr);
101+
case "J" -> invokeInstance(StringBuilder.class, "append", long.class, expr);
102+
case "F" -> invokeInstance(StringBuilder.class, "append", float.class, expr);
103+
case "D" -> invokeInstance(StringBuilder.class, "append", double.class, expr);
104+
case "C" -> invokeInstance(StringBuilder.class, "append", char.class, expr);
105+
case "[C" -> invokeInstance(StringBuilder.class, "append", char[].class, expr);
106+
case "Ljava/lang/String;" -> invokeInstance(StringBuilder.class, "append", String.class, expr);
107+
case "Ljava/lang/CharSequence;" -> invokeInstance(StringBuilder.class, "append", CharSequence.class, expr);
108+
default -> invokeInstance(StringBuilder.class, "append", Object.class, expr);
109+
}
110+
return this;
111+
}
112+
113+
/**
114+
* Appends the given {@code char} constant to this {@code StringBuilder}.
115+
*
116+
* @param constant the value to append
117+
* @return this instance
118+
*/
119+
public StringBuilderGen append(final char constant) {
120+
return append(Const.of(constant));
121+
}
122+
123+
/**
124+
* Appends the given {@code String} constant to this {@code StringBuilder}.
125+
*
126+
* @param constant the value to append
127+
* @return this instance
128+
*/
129+
public StringBuilderGen append(final String constant) {
130+
return append(Const.of(constant));
131+
}
132+
133+
/**
134+
* Appends the given code point to this {@code StringBuilder}.
135+
*
136+
* @param codePoint the value to append (must not be {@code null})
137+
* @return this instance
138+
*/
139+
public StringBuilderGen appendCodePoint(final Expr codePoint) {
140+
invokeInstance(StringBuilder.class, "appendCodePoint", int.class, codePoint);
141+
return this;
142+
}
143+
144+
/**
145+
* Appends the given code point to this {@code StringBuilder}.
146+
*
147+
* @param codePoint the value to append
148+
* @return this instance
149+
*/
150+
public StringBuilderGen appendCodePoint(final int codePoint) {
151+
return appendCodePoint(Const.of(codePoint));
152+
}
153+
154+
/**
155+
* Set the length of this {@code StringBuilder}.
156+
*
157+
* @param length the length expression (must not be {@code null})
158+
*/
159+
public void setLength(Expr length) {
160+
invokeInstance(void.class, "setLength", int.class, length);
161+
}
162+
163+
/**
164+
* Set the length of this {@code StringBuilder}.
165+
*
166+
* @param length the constant length
167+
*/
168+
public void setLength(int length) {
169+
setLength(Const.of(length));
170+
}
171+
172+
@Override
173+
public Expr compareTo(Expr other) {
174+
return invokeInstance(int.class, "compareTo", StringBuilder.class, other);
175+
}
176+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.jboss.jandex.gizmo2;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.function.Supplier;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import io.quarkus.gizmo2.Const;
10+
import io.quarkus.gizmo2.Gizmo;
11+
import io.quarkus.gizmo2.LocalVar;
12+
import io.quarkus.gizmo2.TestClassMaker;
13+
import io.quarkus.gizmo2.desc.MethodDesc;
14+
15+
public class StringBuilderGenTest {
16+
@Test
17+
public void testStringBuilder() {
18+
TestClassMaker tcm = new TestClassMaker();
19+
Gizmo g = Gizmo.create(tcm);
20+
g.class_("io.quarkus.gizmo2.TestStringBuilder", cc -> {
21+
MethodDesc charSeq = cc.staticMethod("createCharSequence", mc -> {
22+
mc.returning(CharSequence.class);
23+
mc.body(bc -> {
24+
LocalVar strBuilder = bc.localVar("stringBuilder", bc.new_(StringBuilder.class));
25+
StringBuilderGen.of(strBuilder, bc).append("ghi");
26+
bc.return_(strBuilder);
27+
});
28+
});
29+
30+
cc.staticMethod("createString", mc -> {
31+
mc.returning(Object.class); // in fact always `String`
32+
mc.body(bc -> {
33+
bc.return_(StringBuilderGen.ofNew(bc)
34+
.append(Const.of(true))
35+
.append(Const.of((byte) 1))
36+
.append(Const.of((short) 2))
37+
.append(Const.of(3))
38+
.append(Const.of(4L))
39+
.append(Const.of(5.0F))
40+
.append(Const.of(6.0))
41+
.append(Const.of('a'))
42+
.append(bc.newArray(char.class, Const.of('b'), Const.of('c')))
43+
.append(Const.of("def"))
44+
.append(bc.invokeStatic(charSeq))
45+
.append(bc.new_(MyObject.class))
46+
.append(Const.ofNull(Object.class))
47+
.append("...")
48+
.append('!')
49+
.toString_());
50+
});
51+
});
52+
});
53+
assertEquals("true12345.06.0abcdefghijklmnull...!", tcm.staticMethod("createString", Supplier.class).get());
54+
}
55+
56+
public static class MyObject {
57+
@Override
58+
public String toString() {
59+
return "jklm";
60+
}
61+
}
62+
63+
@Test
64+
public void testStringBuilderWithControlFlow() {
65+
TestClassMaker tcm = new TestClassMaker();
66+
Gizmo g = Gizmo.create(tcm);
67+
g.class_("io.quarkus.gizmo2.TestStringBuilder", cc -> {
68+
cc.staticMethod("createString", mc -> {
69+
mc.returning(Object.class); // always `String`
70+
mc.body(b0 -> {
71+
LocalVar msg = b0.localVar("msg", b0.new_(StringBuilder.class));
72+
StringBuilderGen msgBuilder = StringBuilderGen.of(msg, b0).append("FooBar");
73+
LocalVar i = b0.localVar("i", Const.of(0));
74+
b0.while_(b1 -> b1.yield(b1.lt(i, 5)), b1 -> {
75+
StringBuilderGen.of(msg, b1).append("Baz").append(i);
76+
b1.inc(i);
77+
});
78+
msgBuilder.append("Quux");
79+
b0.return_(msgBuilder.toString_());
80+
});
81+
});
82+
});
83+
assertEquals("FooBarBaz0Baz1Baz2Baz3Baz4Quux", tcm.staticMethod("createString", Supplier.class).get());
84+
}
85+
}

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@
8181
<artifactId>gizmo2</artifactId>
8282
<version>${version.gizmo2}</version>
8383
</dependency>
84+
<dependency>
85+
<groupId>io.quarkus.gizmo</groupId>
86+
<artifactId>gizmo2</artifactId>
87+
<type>test-jar</type>
88+
<version>${version.gizmo2}</version>
89+
</dependency>
8490
<dependency>
8591
<groupId>net.bytebuddy</groupId>
8692
<artifactId>byte-buddy</artifactId>

0 commit comments

Comments
 (0)