fix: take required options as constructor params (#274)

This commit is contained in:
Yury Semikhatsky 2021-02-09 17:58:55 -08:00 committed by GitHub
parent 429f2969aa
commit fcc1d8672a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 70 additions and 157 deletions

View File

@ -74,13 +74,9 @@ public interface BrowserContext extends AutoCloseable {
*/
public SameSiteAttribute sameSite;
public AddCookie withName(String name) {
public AddCookie(String name, String value) {
this.name = name;
return this;
}
public AddCookie withValue(String value) {
this.value = value;
return this;
}
public AddCookie withUrl(String url) {
this.url = url;

View File

@ -40,22 +40,4 @@ public class Clip {
this.width = width;
this.height = height;
}
public Clip() {
}
public Clip withX(double x) {
this.x = x;
return this;
}
public Clip withY(double y) {
this.y = y;
return this;
}
public Clip withWidth(double width) {
this.width = width;
return this;
}
public Clip withHeight(double height) {
this.height = height;
return this;
}
}

View File

@ -24,14 +24,4 @@ public class HttpCredentials {
this.username = username;
this.password = password;
}
public HttpCredentials() {
}
public HttpCredentials withUsername(String username) {
this.username = username;
return this;
}
public HttpCredentials withPassword(String password) {
this.password = password;
return this;
}
}

View File

@ -34,14 +34,6 @@ public class Margin {
*/
public String left;
public Margin(String top, String right, String bottom, String left) {
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}
public Margin() {
}
public Margin withTop(String top) {
this.top = top;
return this;

View File

@ -24,14 +24,4 @@ public class Position {
this.x = x;
this.y = y;
}
public Position() {
}
public Position withX(double x) {
this.x = x;
return this;
}
public Position withY(double y) {
this.y = y;
return this;
}
}

View File

@ -35,17 +35,8 @@ public class Proxy {
*/
public String password;
public Proxy(String server, String bypass, String username, String password) {
public Proxy(String server) {
this.server = server;
this.bypass = bypass;
this.username = username;
this.password = password;
}
public Proxy() {
}
public Proxy withServer(String server) {
this.server = server;
return this;
}
public Proxy withBypass(String bypass) {
this.bypass = bypass;

View File

@ -27,18 +27,11 @@ public class RecordHar {
*/
public Path path;
public RecordHar(Boolean omitContent, Path path) {
this.omitContent = omitContent;
public RecordHar(Path path) {
this.path = path;
}
public RecordHar() {
}
public RecordHar withOmitContent(boolean omitContent) {
this.omitContent = omitContent;
return this;
}
public RecordHar withPath(Path path) {
this.path = path;
return this;
}
}

View File

@ -29,15 +29,8 @@ public class RecordVideo {
*/
public Size size;
public RecordVideo(Path dir, Size size) {
public RecordVideo(Path dir) {
this.dir = dir;
this.size = size;
}
public RecordVideo() {
}
public RecordVideo withDir(Path dir) {
this.dir = dir;
return this;
}
public RecordVideo withSize(Size size) {
this.size = size;

View File

@ -30,14 +30,4 @@ public class Size {
this.width = width;
this.height = height;
}
public Size() {
}
public Size withWidth(int width) {
this.width = width;
return this;
}
public Size withHeight(int height) {
this.height = height;
return this;
}
}

View File

@ -35,7 +35,7 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldWork() {
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("password").withValue("123456")));
new BrowserContext.AddCookie("password", "123456").withUrl(server.EMPTY_PAGE)));
assertEquals("password=123456", page.evaluate("document.cookie"));
}
@ -52,9 +52,7 @@ public class TestBrowserContextAddCookies extends TestBase {
List<BrowserContext.Cookie> cookies = context.cookies();
context.clearCookies();
assertEquals(emptyList(), context.cookies());
context.addCookies(asList(new BrowserContext.AddCookie()
.withName(cookies.get(0).name())
.withValue(cookies.get(0).value())
context.addCookies(asList(new BrowserContext.AddCookie(cookies.get(0).name(), cookies.get(0).value())
.withDomain(cookies.get(0).domain())
.withPath(cookies.get(0).path())
.withExpires(cookies.get(0).expires())
@ -66,7 +64,7 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldSendCookieHeader() throws ExecutionException, InterruptedException {
Future<Server.Request> request = server.futureRequest("/empty.html");
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("cookie").withValue("value")));
new BrowserContext.AddCookie("cookie", "value").withUrl(server.EMPTY_PAGE)));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
List<String> cookies = request.get().headers.get("cookie");
@ -77,9 +75,9 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldIsolateCookiesInBrowserContexts() {
BrowserContext anotherContext = browser.newContext();
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("isolatecookie").withValue("page1value")));
new BrowserContext.AddCookie("isolatecookie", "page1value").withUrl(server.EMPTY_PAGE)));
anotherContext.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("isolatecookie").withValue("page2value")));
new BrowserContext.AddCookie("isolatecookie", "page2value").withUrl(server.EMPTY_PAGE)));
List<BrowserContext.Cookie> cookies1 = context.cookies();
List<BrowserContext.Cookie> cookies2 = anotherContext.cookies();
assertEquals(1, cookies1.size());
@ -147,7 +145,7 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldIsolateSendCookieHeader() throws ExecutionException, InterruptedException {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("sendcookie").withValue("value")));
new BrowserContext.AddCookie("sendcookie", "value").withUrl(server.EMPTY_PAGE)));
{
Page page = context.newPage();
Future<Server.Request> request = server.futureRequest("/empty.html");
@ -171,10 +169,8 @@ public class TestBrowserContextAddCookies extends TestBase {
// TODO: const browser1 = browserType.launch(browserOptions);
Browser browser1 = browserType.launch();
BrowserContext context1 = browser1.newContext();
context1.addCookies(asList(new BrowserContext.AddCookie()
context1.addCookies(asList(new BrowserContext.AddCookie("cookie-in-context-1", "value")
.withUrl(server.EMPTY_PAGE)
.withName("cookie-in-context-1")
.withValue("value")
.withExpires(Instant.now().getEpochSecond() + + 10000)));
browser1.close();
@ -190,8 +186,8 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldSetMultipleCookies() {
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("multiple-1").withValue("123456"),
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("multiple-2").withValue("bar")
new BrowserContext.AddCookie("multiple-1", "123456").withUrl(server.EMPTY_PAGE),
new BrowserContext.AddCookie("multiple-2", "bar").withUrl(server.EMPTY_PAGE)
));
assertEquals(asList("multiple-1=123456", "multiple-2=bar"), page.evaluate("() => {\n" +
" const cookies = document.cookie.split(';');\n" +
@ -202,7 +198,7 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldHaveExpiresSetTo1ForSessionCookies() {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("expires").withValue("123456")));
new BrowserContext.AddCookie("expires", "123456").withUrl(server.EMPTY_PAGE)));
List<BrowserContext.Cookie> cookies = context.cookies();
assertEquals(-1, cookies.get(0).expires());
}
@ -210,7 +206,7 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldSetCookieWithReasonableDefaults() {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("defaults").withValue("123456")));
new BrowserContext.AddCookie("defaults", "123456").withUrl(server.EMPTY_PAGE)));
List<BrowserContext.Cookie> cookies = context.cookies();
assertJsonEquals("[{\n" +
" name: 'defaults',\n" +
@ -227,11 +223,9 @@ public class TestBrowserContextAddCookies extends TestBase {
@Test
void shouldSetACookieWithAPath() {
page.navigate(server.PREFIX + "/grid.html");
context.addCookies(asList(new BrowserContext.AddCookie()
context.addCookies(asList(new BrowserContext.AddCookie("gridcookie", "GRID")
.withDomain("localhost")
.withPath("/grid.html")
.withName("gridcookie")
.withValue("GRID")));
.withPath("/grid.html")));
List<BrowserContext.Cookie> cookies = context.cookies();
assertJsonEquals("[{\n" +
" name: 'gridcookie',\n" +
@ -254,8 +248,8 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldNotSetACookieWithBlankPageURL() {
try {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("example-cookie").withValue("best"),
new BrowserContext.AddCookie().withUrl("about:blank").withName("example-cookie-blank").withValue("best")
new BrowserContext.AddCookie("example-cookie", "best").withUrl(server.EMPTY_PAGE),
new BrowserContext.AddCookie("example-cookie-blank", "best").withUrl("about:blank")
));
fail("did not throw");
} catch (PlaywrightException e) {
@ -267,7 +261,7 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldNotSetACookieOnADataURLPage() {
try {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl("data:,Hello%2C%20World!").withName("example-cookie").withValue("best")
new BrowserContext.AddCookie("example-cookie", "best").withUrl("data:,Hello%2C%20World!")
));
fail("did not throw");
} catch (PlaywrightException e) {
@ -280,7 +274,7 @@ public class TestBrowserContextAddCookies extends TestBase {
page.navigate(server.EMPTY_PAGE);
String SECURE_URL = "https://example.com";
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(SECURE_URL).withName("foo").withValue("bar")
new BrowserContext.AddCookie("foo", "bar").withUrl(SECURE_URL)
));
List<BrowserContext.Cookie> cookies = context.cookies(SECURE_URL);
assertEquals(1, cookies.size());
@ -292,7 +286,7 @@ public class TestBrowserContextAddCookies extends TestBase {
page.navigate(server.EMPTY_PAGE);
String HTTP_URL = "http://example.com";
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(HTTP_URL).withName("foo").withValue("bar")
new BrowserContext.AddCookie("foo", "bar").withUrl(HTTP_URL)
));
List<BrowserContext.Cookie> cookies = context.cookies(HTTP_URL);
assertEquals(1, cookies.size());
@ -303,7 +297,7 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldSetACookieOnADifferentDomain() {
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl("https://www.example.com").withName("example-cookie").withValue("best")
new BrowserContext.AddCookie("example-cookie", "best").withUrl("https://www.example.com")
));
assertEquals("", page.evaluate("document.cookie"));
assertJsonEquals("[{\n" +
@ -322,7 +316,7 @@ public class TestBrowserContextAddCookies extends TestBase {
void shouldSetCookiesForAFrame() {
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.PREFIX).withName("frame-cookie").withValue("value")
new BrowserContext.AddCookie("frame-cookie", "value").withUrl(server.PREFIX)
));
page.evaluate("src => {\n" +
" let fulfill;\n" +

View File

@ -27,7 +27,7 @@ public class TestBrowserContextClearCookies extends TestBase {
void shouldClearCookies() {
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("cookie1").withValue("1")));
new BrowserContext.AddCookie("cookie1", "1").withUrl(server.EMPTY_PAGE)));
assertEquals("cookie1=1", page.evaluate("document.cookie"));
context.clearCookies();
assertEquals(emptyList(), context.cookies());
@ -39,9 +39,9 @@ public class TestBrowserContextClearCookies extends TestBase {
void shouldIsolateCookiesWhenClearing() {
BrowserContext anotherContext = browser.newContext();
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("page1cookie").withValue("page1value")));
new BrowserContext.AddCookie("page1cookie", "page1value").withUrl(server.EMPTY_PAGE)));
anotherContext.addCookies(asList(
new BrowserContext.AddCookie().withUrl(server.EMPTY_PAGE).withName("page2cookie").withValue("page2value")));
new BrowserContext.AddCookie("page2cookie", "page2value").withUrl(server.EMPTY_PAGE)));
assertEquals(1, (context.cookies()).size());
assertEquals(1, (anotherContext.cookies()).size());

View File

@ -157,9 +157,9 @@ public class TestBrowserContextCookies extends TestBase {
@Test
void shouldGetCookiesFromMultipleUrls() {
context.addCookies(asList(
new BrowserContext.AddCookie().withUrl("https://foo.com").withName("doggo").withValue("woofs"),
new BrowserContext.AddCookie().withUrl("https://bar.com").withName("catto").withValue("purrs"),
new BrowserContext.AddCookie().withUrl("https://baz.com").withName("birdo").withValue("tweets")));
new BrowserContext.AddCookie("doggo", "woofs").withUrl("https://foo.com"),
new BrowserContext.AddCookie("catto", "purrs").withUrl("https://bar.com"),
new BrowserContext.AddCookie("birdo", "tweets").withUrl("https://baz.com")));
List<BrowserContext.Cookie> cookies = context.cookies(asList("https://foo.com", "https://baz.com"));
cookies.sort(Comparator.comparing(BrowserContext.Cookie::name));
assertJsonEquals("[{\n" +

View File

@ -33,7 +33,7 @@ public class TestBrowserContextProxy extends TestBase {
// Hide base class method to provide extra option.
static void launchBrowser() {
BrowserType.LaunchOptions options = createLaunchOptions();
options.withProxy(new Proxy().withServer("per-context"));
options.withProxy(new Proxy("per-context"));
launchBrowser(options);
}
@ -41,7 +41,7 @@ public class TestBrowserContextProxy extends TestBase {
void shouldThrowForMissingGlobalProxy() {
Browser browser = browserType.launch(createLaunchOptions());
try {
browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy().withServer("localhost:" + server.PORT)));
browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy("localhost:" + server.PORT)));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy"));
@ -62,8 +62,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
assertEquals("Served by the proxy", page.title());
@ -78,8 +78,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
page.navigate("http://non-existent-2.com/target.html");
@ -95,8 +95,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("localhost:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
@ -117,8 +117,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("127.0.0.1:" + server.PORT)));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("127.0.0.1:" + server.PORT)));
Page page = context.newPage();
page.navigate("http://non-existent.com/target.html");
@ -141,8 +141,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>" + auth.get(0) + "</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("localhost:" + server.PORT)
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("localhost:" + server.PORT)
.withUsername("user")
.withPassword("secret")));
Page page = context.newPage();
@ -164,8 +164,8 @@ public class TestBrowserContextProxy extends TestBase {
writer.write("<html><title>Served by the proxy</title></html>");
}
});
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("127.0.0.1:" + server.PORT)
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("127.0.0.1:" + server.PORT)
// FYI: using long and weird domain names to avoid ATT DNS hijacking
// that resolves everything to some weird search results page.
//
@ -210,8 +210,8 @@ public class TestBrowserContextProxy extends TestBase {
@Test
void doesLaunchWithoutAPort() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(new Proxy()
.withServer("http://localhost")));
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withProxy(
new Proxy("http://localhost")));
context.close();
}
}

View File

@ -43,8 +43,8 @@ public class TestHar extends TestBase {
PageWithHar() throws IOException {
harFile = Files.createTempFile("test-", ".har");
context = browser.newContext(new Browser.NewContextOptions().withRecordHar(new RecordHar()
.withPath(harFile)).withIgnoreHTTPSErrors(true));
context = browser.newContext(new Browser.NewContextOptions().withRecordHar(
new RecordHar(harFile)).withIgnoreHTTPSErrors(true));
page = context.newPage();
}
@ -74,7 +74,7 @@ public class TestHar extends TestBase {
@Test
void shouldThrowWithoutPath() {
try {
browser.newContext(new Browser.NewContextOptions().withRecordHar(new RecordHar()));
browser.newContext(new Browser.NewContextOptions().withRecordHar(new RecordHar(null)));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("recordHar.path: expected string, got undefined"));
@ -119,7 +119,7 @@ public class TestHar extends TestBase {
Path userDataDir = Files.createTempDirectory("user-data-dir-");
BrowserContext context = browserType.launchPersistentContext(userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.withRecordHar(new RecordHar().withPath(harPath)).withIgnoreHTTPSErrors(true));
.withRecordHar(new RecordHar(harPath)).withIgnoreHTTPSErrors(true));
Page page = context.pages().get(0);
page.navigate("data:text/html,<title>Hello</title>");

View File

@ -148,8 +148,8 @@ public class TestPageRoute extends TestBase {
void shouldProperlyReturnNavigationResponseWhenURLHasCookies() {
// Setup cookie.
page.navigate(server.EMPTY_PAGE);
context.addCookies(asList(new BrowserContext.AddCookie()
.withUrl(server.EMPTY_PAGE).withName("foo").withValue("bar")));
context.addCookies(asList(new BrowserContext.AddCookie("foo", "bar")
.withUrl(server.EMPTY_PAGE)));
// Setup request interception.
page.route("**/*", route -> route.resume());
Response response = page.reload();

View File

@ -44,7 +44,7 @@ public class TestPageScreenshot extends TestBase {
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/grid.html");
byte[] screenshot = page.screenshot(new Page.ScreenshotOptions()
.withClip(new Clip().withX(50).withY(100).withWidth(150).withHeight(100)));
.withClip(new Clip(50, 100, 150, 100)));
BufferedImage image = ImageIO.read(new ByteArrayInputStream(screenshot));
assertEquals(150, image.getWidth());
assertEquals(100, image.getHeight());

View File

@ -30,7 +30,7 @@ public class TestScreencast extends TestBase {
@Test
void shouldExposeVideoPath(@TempDir Path videosDir) {
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withRecordVideo(new RecordVideo().withDir(videosDir).withSize(new Size(320, 240)))
.withRecordVideo(new RecordVideo(videosDir).withSize(new Size(320, 240)))
.withViewport(320, 240));
Page page = context.newPage();
page.evaluate("() => document.body.style.backgroundColor = 'red'");

View File

@ -24,9 +24,11 @@ import com.google.gson.JsonObject;
import java.io.*;
import java.nio.file.FileSystems;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.reverse;
import static java.util.stream.Collectors.toList;
abstract class Element {
final String jsonName;
@ -764,6 +766,11 @@ class Field extends Element {
this.type = new TypeRef(this, jsonElement.getAsJsonObject().get("type"));
}
boolean isRequired() {
return jsonElement.getAsJsonObject().has("required") &&
jsonElement.getAsJsonObject().get("required").getAsBoolean();
}
void writeTo(List<String> output, String offset, String access) {
writeJavadoc(output, offset, comment());
if (asList("Frame.waitForNavigation.options.url",
@ -1156,9 +1163,7 @@ class NestedClass extends TypeDefinition {
f.writeGetter(output, bodyOffset);
}
} else {
if (parent.parent instanceof Field) {
writeConstructor(output, bodyOffset);
}
writeConstructor(output, bodyOffset);
writeBuilderMethods(output, bodyOffset);
if (asList("Browser.newContext.options",
"Browser.newPage.options",
@ -1170,24 +1175,21 @@ class NestedClass extends TypeDefinition {
}
private void writeBuilderMethods(List<String> output, String bodyOffset) {
if (parent.typeScope() instanceof NestedClass) {
output.add(bodyOffset + "public " + name + "() {");
output.add(bodyOffset + "}");
}
for (Field f : fields) {
f.writeBuilderMethod(output, bodyOffset, name);
if (!f.isRequired()) {
f.writeBuilderMethod(output, bodyOffset, name);
}
}
}
private void writeConstructor(List<String> output, String bodyOffset) {
List<String> args = new ArrayList<>();
for (Field f : fields) {
args.add(f.type.toJava() + " " + f.name);
List<Field> requiredFields = fields.stream().filter(f -> f.isRequired()).collect(toList());
if (requiredFields.isEmpty()) {
return;
}
List<String> args = requiredFields.stream().map(f -> f.type.toJava() + " " + f.name).collect(toList());
output.add(bodyOffset + "public " + name + "(" + String.join(", ", args) + ") {");
for (Field f : fields) {
output.add(bodyOffset + " this." + f.name + " = " + f.name + ";");
}
requiredFields.forEach(f -> output.add(bodyOffset + " this." + f.name + " = " + f.name + ";"));
output.add(bodyOffset + "}");
}