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 5f86e69d3f..5e4808d28a 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 @@ -22,6 +22,9 @@ public abstract class ThrowExpr implements Expr { // TODO(miraleung): Refactor with StringObjectValue and possibly with NewObjectExpr. + @Nullable + public abstract Expr throwExpr(); + @Override public abstract TypeNode type(); @@ -42,6 +45,9 @@ public static Builder builder() { @AutoValue.Builder public abstract static class Builder { + public abstract Builder setThrowExpr(Expr throwExpr); + + // No-op if setThrowExpr is called. public abstract Builder setType(TypeNode type); public Builder setMessageExpr(String message) { @@ -53,6 +59,8 @@ public Builder setMessageExpr(String message) { public abstract Builder setCauseExpr(Expr expr); // Private. + abstract Expr throwExpr(); + abstract TypeNode type(); abstract Expr messageExpr(); @@ -62,6 +70,24 @@ public Builder setMessageExpr(String message) { abstract ThrowExpr autoBuild(); public ThrowExpr build() { + if (throwExpr() != null) { + setType(throwExpr().type()); + Preconditions.checkState( + messageExpr() == null && causeExpr() == null, + "Only one of throwExpr or [messageExpr or causeExpr, inclusive] can be present."); + + if (throwExpr() instanceof VariableExpr) { + Preconditions.checkState( + !((VariableExpr) throwExpr()).isDecl(), "Cannot throw a variable declaration"); + } + + Preconditions.checkState( + TypeNode.isExceptionType(throwExpr().type()), + String.format("Only exception types can be thrown, found %s", throwExpr().type())); + + return autoBuild(); + } + Preconditions.checkState( TypeNode.isExceptionType(type()), String.format("Type %s must be an exception type", type())); 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 259a027402..297500ae7f 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 @@ -231,6 +231,13 @@ public void visit(AnonymousClassExpr anonymousClassExpr) { @Override public void visit(ThrowExpr throwExpr) { throwExpr.type().accept(this); + // If throwExpr is present, then messageExpr and causeExpr will not be present. Relies on AST + // build-time checks. + if (throwExpr.throwExpr() != null) { + throwExpr.throwExpr().accept(this); + return; + } + if (throwExpr.messageExpr() != null) { throwExpr.messageExpr().accept(this); } 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 11ddff441d..9006b0490b 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 @@ -392,6 +392,13 @@ public void visit(AnonymousClassExpr anonymousClassExpr) { public void visit(ThrowExpr throwExpr) { buffer.append(THROW); space(); + // If throwExpr is present, then messageExpr and causeExpr will not be present. Relies on AST + // build-time checks. + if (throwExpr.throwExpr() != null) { + throwExpr.throwExpr().accept(this); + return; + } + buffer.append(NEW); space(); throwExpr.type().accept(this); 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 f27d000113..7bc1589feb 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 @@ -14,6 +14,7 @@ package com.google.api.generator.engine.ast; +import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertThrows; import org.junit.Test; @@ -24,7 +25,24 @@ public void createThrowExpr_basic() { TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.class); ThrowExpr.builder().setType(npeType).build(); // No exception thrown, we're good. + } + @Test + public void createThrowExpr_basicExpr() { + TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.class); + VariableExpr throwVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("e") + .setType(TypeNode.withExceptionClazz(RuntimeException.class)) + .build()) + .build(); + ThrowExpr throwExpr = ThrowExpr.builder().setThrowExpr(throwVarExpr).build(); + assertEquals(throwVarExpr.variable().type(), throwExpr.type()); + // Setting the type doesn't matter. + throwExpr = ThrowExpr.builder().setThrowExpr(throwVarExpr).setType(npeType).build(); + assertEquals(throwVarExpr.variable().type(), throwExpr.type()); } @Test @@ -123,4 +141,75 @@ public void createThrowExpr_messageAndCauseExpr() { .build(); // Successfully created a ThrowExpr. } + + @Test + public void createThrowExpr_cannotThrowVariableDeclaration() { + VariableExpr throwVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("e") + .setType(TypeNode.withExceptionClazz(RuntimeException.class)) + .build()) + .build(); + assertThrows( + IllegalStateException.class, + () -> + ThrowExpr.builder() + .setThrowExpr(throwVarExpr.toBuilder().setIsDecl(true).build()) + .build()); + } + + @Test + public void createThrowExpr_cannotThrowNonExceptionTypedExpr() { + VariableExpr throwVarExpr = + VariableExpr.builder() + .setVariable(Variable.builder().setName("str").setType(TypeNode.STRING).build()) + .build(); + assertThrows( + IllegalStateException.class, () -> ThrowExpr.builder().setThrowExpr(throwVarExpr).build()); + } + + @Test + public void createThrowExpr_cannotHaveThrowVariableAndMessageExprPresent() { + Expr messageExpr = + MethodInvocationExpr.builder() + .setMethodName("foobar") + .setReturnType(TypeNode.STRING) + .build(); + VariableExpr throwVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("e") + .setType(TypeNode.withExceptionClazz(RuntimeException.class)) + .build()) + .build(); + assertThrows( + IllegalStateException.class, + () -> ThrowExpr.builder().setThrowExpr(throwVarExpr).setMessageExpr(messageExpr).build()); + } + + @Test + public void createThrowExpr_cannotHaveThrowVariableAndCauseExprPresent() { + VariableExpr throwVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("e") + .setType(TypeNode.withExceptionClazz(RuntimeException.class)) + .build()) + .build(); + assertThrows( + IllegalStateException.class, + () -> + ThrowExpr.builder() + .setThrowExpr(throwVarExpr) + .setCauseExpr( + NewObjectExpr.builder() + .setType( + TypeNode.withReference(ConcreteReference.withClazz(Throwable.class))) + .build()) + .build()); + } } 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 053ea774d5..6b9d20ff0b 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 @@ -449,6 +449,26 @@ public void writeThrowExprImports_basic() { assertEquals("import java.io.IOException;\n\n", writerVisitor.write()); } + @Test + public void writeThrowExprImports_throwExpr() { + Expr exprToThrow = + MethodInvocationExpr.builder() + .setStaticReferenceType( + TypeNode.withReference(ConcreteReference.withClazz(Statement.class))) + .setMethodName("createException") + .setReturnType(TypeNode.withExceptionClazz(Exception.class)) + .build(); + + TypeNode ignoredExceptionType = + TypeNode.withReference(ConcreteReference.withClazz(IOException.class)); + ThrowExpr throwExpr = + ThrowExpr.builder().setType(ignoredExceptionType).setThrowExpr(exprToThrow).build(); + throwExpr.accept(writerVisitor); + assertEquals( + LineFormatter.lines("import com.google.api.generator.engine.ast.Statement;\n\n"), + writerVisitor.write()); + } + @Test public void writeThrowExprImports_messageExpr() { TypeNode npeType = TypeNode.withExceptionClazz(NullPointerException.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 c07fbd32cf..4be2c916d4 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 @@ -1031,6 +1031,21 @@ public void writeThrowExpr_basic() { assertEquals("throw new NullPointerException()", writerVisitor.write()); } + @Test + public void writeThrowExpr_basicThrowExpr() { + Expr exprToThrow = + MethodInvocationExpr.builder() + .setStaticReferenceType( + TypeNode.withReference(ConcreteReference.withClazz(Statement.class))) + .setMethodName("createException") + .setReturnType(TypeNode.withExceptionClazz(Exception.class)) + .build(); + + ThrowExpr throwExpr = ThrowExpr.builder().setThrowExpr(exprToThrow).build(); + throwExpr.accept(writerVisitor); + assertEquals("throw Statement.createException()", writerVisitor.write()); + } + @Test public void writeThrowExpr_basicWithMessage() { TypeNode npeType =