From 84d0b78ca5a5bf4000bf534a8673998110ca2474 Mon Sep 17 00:00:00 2001 From: AlexisMardas Date: Fri, 17 May 2024 19:27:28 +0300 Subject: [PATCH] Implement switch statements and expressions --- .../java/com/squareup/javapoet/CodeBlock.java | 127 ++++++++++++++++++ .../com/squareup/javapoet/MethodSpec.java | 103 ++++++++++++++ .../com/squareup/javapoet/MethodSpecTest.java | 98 ++++++++++++++ 3 files changed, 328 insertions(+) diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java index 5376984..98174b5 100644 --- a/src/main/java/com/squareup/javapoet/CodeBlock.java +++ b/src/main/java/com/squareup/javapoet/CodeBlock.java @@ -398,6 +398,133 @@ public Builder endControlFlow(String controlFlow, Object... args) { return this; } + /** + * Starts a switch statement. To start a switch expression it should be + * used with {@link #add(String, Object...) add}. + * @param expressionFormat the format of the expression that will be calculated + * by the switch statement. It should not include parentheses, braces or + * newline characters + * @param args the values to be placed instead of the format's placeholders + */ + public Builder beginSwitchStatement(String expressionFormat, Object... args) { + add("switch ("+expressionFormat+") {\n", args); + indent(); + return this; + } + + /** + * Starts a switch statement. To start a switch expression it should be + * used with {@link #add(String, Object...) add}. + * @param codeblock the expression that will be calculated + * by the switch statement. It should not include parentheses, braces or + * newline characters + */ + public Builder beginSwitchStatement(CodeBlock codeBlock) { + return beginSwitchStatement("$L", codeBlock); + } + + + + /** + * Adds a case to a switch statement or expression. + * @param isFirstCase indicates whether the case is the first of the switch + * statement. Necessary to apply proper indentation + * @param valueFormat the value(-s) that the calculated expression will be compared + * against + * @param args the values to be placed instead of the format's placeholders + */ + public Builder addSwitchCase(Boolean isFirstCase, String valueFormat, Object... args) { + // If a previous case already exists, we must unindent + if (!isFirstCase) { + unindent(); + } + add("case " + valueFormat + ":\n", args); + indent(); + return this; + } + + /** + * Adds a case to a switch statement or expression. + * @param isFirstCase indicates whether the case is the first of the switch + * statement. Necessary to apply proper indentation + * @param codeBlock the value(-s) that the calculated expression will be compared + * against + */ + public Builder addSwitchCase(Boolean isFirstCase, CodeBlock codeBlock) { + return addSwitchCase(isFirstCase,"$L", codeBlock); + } + + /** + * Adds a case to an extended switch statement or expression. + * @param body the code that needs to be executed if the case is true. + * @param valueFormat the value(-s) that the calculated expression will be compared + * against + * @param args the values to be placed instead of the format's placeholders + */ + public Builder addExtendedSwitchCase(CodeBlock body, + String valueFormat, Object... args) { + String bodySide = body.toString(); + // If the body contains multiple lines or expressions it should be + // contained in braces + if (bodySide.lines().count() > 1 || bodySide.split(";").length > 1) { + bodySide = "{ " + bodySide + " }"; + } + add("case " + valueFormat + " -> " + bodySide + "\n", args); + return this; + } + + /** + * Adds a case to an extended switch statement or expression. + * @param body the code that needs to be executed if the case is true. + * @param value the value(-s) that the calculated expression will be compared + * against + */ + public Builder addExtendedSwitchCase(CodeBlock body, CodeBlock value) { + return addExtendedSwitchCase(body, "$L", value); + } + + + /** + * Adds the default case to a switch statement or expression + */ + public Builder addDefaultCase() { + unindent(); + add("default:\n"); + indent(); + return this; + } + + /** + * Adds the default case to an enhanced switch statement or expression + * @param body the code that needs to be executed if none of the cases + * are true. + */ + public Builder addExtendedDefaultCase (CodeBlock body) { + String bodySide = body.toString(); + // If the body contains multiple lines or expressions it should be + // contained in braces + if (bodySide.lines().count() > 1 || bodySide.split(";").length > 1) { + bodySide = "{" + bodySide + "}"; + } + add("default -> " + bodySide + "\n", args); + return this; + + } + + /** + * Closes the switch statement or expression + * @param isExtended whether the switch block to close is an extended switch + * block or not. Necessary to implement proper indentation + */ + public Builder endSwitchStatement(Boolean isExtended) { + // unindent needs to be called twice if the switch block isn't an extended + // switch block + if (!isExtended) { + unindent(); + } + return endControlFlow(); + } + public Builder addStatement(String format, Object... args) { add("$["); add(format, args); diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java index 6914858..9fed2d3 100644 --- a/src/main/java/com/squareup/javapoet/MethodSpec.java +++ b/src/main/java/com/squareup/javapoet/MethodSpec.java @@ -522,6 +522,109 @@ public Builder endControlFlow(CodeBlock codeBlock) { return endControlFlow("$L", codeBlock); } + /** + * Starts a switch statement. To start a switch expression it should be + * used with {@link #addCode(String, Object...) addCode}. + * @param expressionFormat the format of the expression that will be calculated + * by the switch statement. It should not include parentheses, braces or + * newline characters + * @param args the values to be placed instead of the format's placeholders + */ + public Builder beginSwitchStatement(String expressionFormat, Object... args) { + code.beginSwitchStatement(expressionFormat, args); + return this; + } + + /** + * Starts a switch statement. To start a switch expression it should be + * used with {@link #addCode(String, Object...) addCode}. + * @param codeblock the expression that will be calculated + * by the switch statement. It should not include parentheses, braces or + * newline characters + */ + public Builder beginSwitchStatement (CodeBlock codeblock) { + code.beginSwitchStatement(codeblock); + return this; + } + + /** + * Adds a case to a switch statement or expression. + * @param isFirstCase indicates whether the case is the first of the switch + * statement. Necessary to apply proper indentation + * @param valueFormat the value that the calculated expression will be compared + * against + * @param args the values to be placed instead of the format's placeholders + */ + public Builder addSwitchCase(Boolean isFirstCase, String valueFormat, Object... args) { + code.addSwitchCase(isFirstCase, valueFormat, args); + return this; + } + + /** + * Adds a case to a switch statement or expression. + * @param isFirstCase indicates whether the case is the first of the switch + * statement. Necessary to apply proper indentation + * @param codeblock the value that the calculated expression will be compared + * against + */ + public Builder addSwitchCase(Boolean isFirstCase, CodeBlock codeblock) { + code.addSwitchCase(isFirstCase, codeblock); + return this; + } + + /** + * Adds a case to an extended switch statement or expression. + * @param body the code that needs to be executed if the case is true. + * @param valueFormat the value(-s) that the calculated expression will be compared + * against + * @param args the values to be placed instead of the format's placeholders + */ + public Builder addExtendedSwitchCase(CodeBlock body, + String valueFormat, Object... args) { + code.addExtendedSwitchCase(body, valueFormat, args); + return this; + + } + + /** + * Adds a case to an extended switch statement or expression. + * @param body the code that needs to be executed if the case is true. + * @param value the value(-s) that the calculated expression will be compared + * against + */ + public Builder addExtendedSwitchCase(CodeBlock body, CodeBlock value) { + code.addExtendedSwitchCase(body, value); + return this; + } + + /** + * Adds the default case to a switch statement or expression + */ + public Builder addDefaultCase() { + code.addDefaultCase(); + return this; + } + + /** + * Adds the default case to an enhanced switch statement or expression + * @param body the code that needs to be executed if none of the cases + * are true. + */ + public Builder addExtendedDefaultCase (CodeBlock body) { + code.addExtendedDefaultCase(body); + return this; + } + + /** + * Closes the switch statement or expression + * @param isExtended whether the switch block to close is an extended switch + * block or not. Necessary to implement proper indentation + */ + public Builder endSwitchStatement(Boolean isExtended) { + code.endSwitchStatement(isExtended); + return this; + } + public Builder addStatement(String format, Object... args) { code.addStatement(format, args); return this; diff --git a/src/test/java/com/squareup/javapoet/MethodSpecTest.java b/src/test/java/com/squareup/javapoet/MethodSpecTest.java index 56cc3b7..8042ed8 100644 --- a/src/test/java/com/squareup/javapoet/MethodSpecTest.java +++ b/src/test/java/com/squareup/javapoet/MethodSpecTest.java @@ -486,4 +486,102 @@ private static CodeBlock named(String format, Map args){ return CodeBlock.builder().addNamed(format, args).build(); } + @Test public void ensureSwitchBlockFunctionality() { + CodeBlock cb1 = CodeBlock.builder() + .addStatement("result = $S", "domestic animal") + .addStatement("break") + .build(); + + CodeBlock cb2 = CodeBlock.builder() + .addStatement("result = $S", "wild animal") + .addStatement("break") + .build(); + + CodeBlock cb3 = CodeBlock.builder() + .addStatement("result = $S", "unknown animal") + .addStatement("break") + .build(); + + MethodSpec method = MethodSpec.methodBuilder("method") + .addModifiers(Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(String.class, "animal").build()) + .returns(String.class) + .addStatement("$T result", String.class) + .beginSwitchStatement("animal") + .addSwitchCase(true, "$S", "DOG") + .addSwitchCase(false, "$S", "CAT") + .addCode(cb1) + .addSwitchCase(false, CodeBlock.of("$S", "TIGER")) + .addCode(cb2) + .addDefaultCase() + .addCode(cb3) + .endSwitchStatement(false) + .addStatement("return result") + .build(); + + assertThat(method.toString()).isEqualTo( + "public java.lang.String method(java.lang.String animal) {\n" + +" java.lang.String result;\n" + +" switch (animal) {\n" + +" case \"DOG\":\n" + +" case \"CAT\":\n" + +" result = \"domestic animal\";\n" + +" break;\n" + +" case \"TIGER\":\n" + +" result = \"wild animal\";\n" + +" break;\n" + +" default:\n" + +" result = \"unknown animal\";\n" + +" break;\n" + +" }\n" + +" return result;\n" + +"}\n" + ); + } + + @Test public void ensureExtendedSwitchExpressionFunctionality() { + // Assume an enum named month is defined, which contains every month of the year + MethodSpec method = MethodSpec.methodBuilder("method") + .addModifiers(Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(String.class, "month").build()) + .returns(String.class) + .addCode("$T season = ", String.class) + .beginSwitchStatement(CodeBlock.of("month")) + .addExtendedSwitchCase(CodeBlock.of("$S;","WINTER"), "$L, $L, $L", "DECEMBER", "JANUARY", "FEBRUARY") + .addExtendedSwitchCase(CodeBlock.of("$S;","SPRING"), "$L, $L, $L", "MARCH", "APRIL", "MAY") + .addExtendedSwitchCase(CodeBlock.of("$S;","SUMMER"), CodeBlock.of("$L, $L, $L", "JUNE", "JULY", "AUGUST")) + .addExtendedDefaultCase(CodeBlock.of("$S;","AUTUMN")) + .endSwitchStatement(true) + .addStatement("return season") + .build(); + + assertThat(method.toString()).isEqualTo( + "public java.lang.String method(java.lang.String month) {\n" + +" java.lang.String season = switch (month) {\n" + +" case DECEMBER, JANUARY, FEBRUARY -> \"WINTER\";\n" + +" case MARCH, APRIL, MAY -> \"SPRING\";\n" + +" case JUNE, JULY, AUGUST -> \"SUMMER\";\n" + +" default -> \"AUTUMN\";\n" + +" }\n" + +" return season;\n" + +"}\n" + ); + } + + @Test public void bracesInExtendedSwitch() { + MethodSpec method = MethodSpec.methodBuilder("method") + .addExtendedSwitchCase(CodeBlock.of("x++; y++;"),"increase") + // Semicolumns ommited deliberately to ensure that multiline codeblocks are included in braces + .addExtendedSwitchCase(CodeBlock.of("x--\n y--"),"decrease") + .build(); + + assertThat(method.toString()).isEqualTo( + "void method() {\n" + +" case increase -> { x++; y++; }\n" + +" case decrease -> { x--\n" + +" y-- }\n" + +"}\n" + ); + } + }