From 9ea47171fdcdbb4bdfd75ad793c26cffbc8f0c33 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Mon, 2 Aug 2021 08:50:00 -0700 Subject: [PATCH 1/2] feat(ast): Add support for throwable causes --- .../api/generator/engine/ast/ThrowExpr.java | 18 ++++++ .../engine/writer/ImportWriterVisitor.java | 3 + .../engine/writer/JavaWriterVisitor.java | 7 +++ .../generator/engine/ast/ThrowExprTest.java | 61 +++++++++++++++++++ .../writer/ImportWriterVisitorTest.java | 37 +++++++++++ .../engine/writer/JavaWriterVisitorTest.java | 39 ++++++++++++ 6 files changed, 165 insertions(+) diff --git a/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java b/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java index 805499b074..b96a7abe53 100644 --- a/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java +++ b/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java @@ -28,6 +28,9 @@ public abstract class ThrowExpr implements Expr { @Nullable public abstract Expr messageExpr(); + @Nullable + public abstract Expr causeExpr(); + @Override public void accept(AstNodeVisitor visitor) { visitor.visit(this); @@ -47,22 +50,37 @@ public Builder setMessageExpr(String message) { public abstract Builder setMessageExpr(Expr expr); + public abstract Builder setCauseExpr(Expr expr); + // Private. abstract TypeNode type(); abstract Expr messageExpr(); + abstract Expr causeExpr(); + abstract ThrowExpr autoBuild(); public ThrowExpr build() { Preconditions.checkState( TypeNode.isExceptionType(type()), String.format("Type %s must be an exception type", type())); + if (messageExpr() != null) { Preconditions.checkState( messageExpr().type().equals(TypeNode.STRING), String.format("Message expression type must be a string for exception %s", type())); } + + if (causeExpr() != null) { + Preconditions.checkState( + ConcreteReference.withClazz(Throwable.class) + .isSupertypeOrEquals(causeExpr().type().reference()), + String.format( + "Cause expression type must be a subclass of Throwable, but found %s", + causeExpr().type())); + } + return autoBuild(); } } diff --git a/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java b/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java index d54d1f0dfc..259a027402 100644 --- a/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java +++ b/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java @@ -234,6 +234,9 @@ public void visit(ThrowExpr throwExpr) { if (throwExpr.messageExpr() != null) { throwExpr.messageExpr().accept(this); } + if (throwExpr.causeExpr() != null) { + throwExpr.causeExpr().accept(this); + } } @Override diff --git a/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java b/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java index 6b4cbc69d1..11ddff441d 100644 --- a/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java +++ b/src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java @@ -399,6 +399,13 @@ public void visit(ThrowExpr throwExpr) { if (throwExpr.messageExpr() != null) { throwExpr.messageExpr().accept(this); } + if (throwExpr.causeExpr() != null) { + if (throwExpr.messageExpr() != null) { + buffer.append(COMMA); + space(); + } + throwExpr.causeExpr().accept(this); + } rightParen(); } diff --git a/src/test/java/com/google/api/generator/engine/ast/ThrowExprTest.java b/src/test/java/com/google/api/generator/engine/ast/ThrowExprTest.java index 48202e8552..f27d000113 100644 --- a/src/test/java/com/google/api/generator/engine/ast/ThrowExprTest.java +++ b/src/test/java/com/google/api/generator/engine/ast/ThrowExprTest.java @@ -62,4 +62,65 @@ public void createThrowExpr_badMessageExpr() { IllegalStateException.class, () -> ThrowExpr.builder().setType(npeType).setMessageExpr(messageExpr).build()); } + + @Test + public void createThrowExpr_causeExpr() { + TypeNode npeType = + TypeNode.withReference(ConcreteReference.withClazz(NullPointerException.class)); + ThrowExpr.builder() + .setType(npeType) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build()) + .build(); + // Successfully created a ThrowExpr. + } + + @Test + public void createThrowExpr_causeExpr_throwableSubtype() { + TypeNode npeType = + TypeNode.withReference(ConcreteReference.withClazz(NullPointerException.class)); + ThrowExpr.builder() + .setType(npeType) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withExceptionClazz(IllegalStateException.class)) + .build()) + .build(); + // Successfully created a ThrowExpr. + } + + @Test + public void createThrowExpr_causeExpr_onThrowableSubtype() { + TypeNode npeType = + TypeNode.withReference(ConcreteReference.withClazz(NullPointerException.class)); + assertThrows( + IllegalStateException.class, + () -> + ThrowExpr.builder() + .setType(npeType) + .setCauseExpr(NewObjectExpr.builder().setType(TypeNode.STRING).build()) + .build()); + } + + @Test + public void createThrowExpr_messageAndCauseExpr() { + TypeNode npeType = + TypeNode.withReference(ConcreteReference.withClazz(NullPointerException.class)); + Expr messageExpr = + MethodInvocationExpr.builder() + .setMethodName("foobar") + .setReturnType(TypeNode.STRING) + .build(); + ThrowExpr.builder() + .setType(npeType) + .setMessageExpr(messageExpr) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build()) + .build(); + // Successfully created a ThrowExpr. + } } diff --git a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java index 47a63ea6ee..053ea774d5 100644 --- a/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java +++ b/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java @@ -58,6 +58,7 @@ import com.google.common.base.Function; import com.google.common.base.Strings; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -475,6 +476,42 @@ public void writeThrowExprImports_messageExpr() { writerVisitor.write()); } + @Test + public void writeThrowExprImports_messageAndCauseExpr() { + TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.class); + Expr messageExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + TypeNode.withReference(ConcreteReference.withClazz(IfStatement.class))) + .setMethodName("conditionExpr") + .setReturnType(TypeNode.withReference(ConcreteReference.withClazz(Expr.class))) + .build(); + + messageExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(messageExpr) + .setMethodName("foobar") + .setReturnType(TypeNode.STRING) + .build(); + ThrowExpr throwExpr = + ThrowExpr.builder() + .setType(npeType) + .setMessageExpr(messageExpr) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withExceptionClazz(FileNotFoundException.class)) + .build()) + .build(); + + throwExpr.accept(writerVisitor); + assertEquals( + LineFormatter.lines( + "import com.google.api.generator.engine.ast.Expr;\n", + "import com.google.api.generator.engine.ast.IfStatement;\n", + "import java.io.FileNotFoundException;\n\n"), + writerVisitor.write()); + } + @Test public void writeInstanceofExprImports_basic() { TypeNode exprType = TypeNode.withReference(ConcreteReference.withClazz(Expr.class)); diff --git a/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java b/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java index ac1f515169..c07fbd32cf 100644 --- a/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java +++ b/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java @@ -1041,6 +1041,22 @@ public void writeThrowExpr_basicWithMessage() { assertEquals("throw new NullPointerException(\"Some message asdf\")", writerVisitor.write()); } + @Test + public void writeThrowExpr_basicWithCause() { + TypeNode npeType = + TypeNode.withReference(ConcreteReference.withClazz(NullPointerException.class)); + ThrowExpr throwExpr = + ThrowExpr.builder() + .setType(npeType) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build()) + .build(); + throwExpr.accept(writerVisitor); + assertEquals("throw new NullPointerException(new Throwable())", writerVisitor.write()); + } + @Test public void writeThrowExpr_messageExpr() { TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.class); @@ -1055,6 +1071,29 @@ public void writeThrowExpr_messageExpr() { assertEquals("throw new NullPointerException(foobar())", writerVisitor.write()); } + @Test + public void writeThrowExpr_messageAndCauseExpr() { + TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.class); + Expr messageExpr = + MethodInvocationExpr.builder() + .setMethodName("foobar") + .setReturnType(TypeNode.STRING) + .build(); + ThrowExpr throwExpr = + ThrowExpr.builder() + .setType(npeType) + .setMessageExpr(messageExpr) + .setCauseExpr( + NewObjectExpr.builder() + .setType(TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build()) + .build(); + + throwExpr.accept(writerVisitor); + assertEquals( + "throw new NullPointerException(foobar(), new Throwable())", writerVisitor.write()); + } + @Test public void writeInstanceofExpr() { Variable variable = Variable.builder().setName("x").setType(TypeNode.STRING).build(); From 5ea11bf8f7ac502e23929a26416b36a2dd4bcb74 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Fri, 30 Jul 2021 14:52:12 -0700 Subject: [PATCH 2/2] fix: make Throwable a static final in TypeNode --- .../java/com/google/api/generator/engine/ast/ThrowExpr.java | 3 +-- .../java/com/google/api/generator/engine/ast/TypeNode.java | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java b/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java index b96a7abe53..5f86e69d3f 100644 --- a/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java +++ b/src/main/java/com/google/api/generator/engine/ast/ThrowExpr.java @@ -74,8 +74,7 @@ public ThrowExpr build() { if (causeExpr() != null) { Preconditions.checkState( - ConcreteReference.withClazz(Throwable.class) - .isSupertypeOrEquals(causeExpr().type().reference()), + TypeNode.THROWABLE.reference().isSupertypeOrEquals(causeExpr().type().reference()), String.format( "Cause expression type must be a subclass of Throwable, but found %s", causeExpr().type())); diff --git a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java index 5685efb1f0..99de1a213d 100644 --- a/src/main/java/com/google/api/generator/engine/ast/TypeNode.java +++ b/src/main/java/com/google/api/generator/engine/ast/TypeNode.java @@ -81,6 +81,9 @@ public enum TypeKind { public static final TypeNode STRING = withReference(ConcreteReference.withClazz(String.class)); public static final TypeNode VOID_OBJECT = withReference(ConcreteReference.withClazz(Void.class)); + public static final TypeNode THROWABLE = + withReference(ConcreteReference.withClazz(Throwable.class)); + public static final TypeNode DEPRECATED = withReference(ConcreteReference.withClazz(Deprecated.class));