diff --git a/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java b/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java index 6afe6e8580..d2c7f0adad 100644 --- a/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java +++ b/src/main/java/com/google/api/generator/engine/ast/AstNodeVisitor.java @@ -55,6 +55,8 @@ public interface AstNodeVisitor { public void visit(UnaryOperationExpr unaryOperationExpr); + public void visit(LogicalOperationExpr logicalOperationExpr); + /** =============================== COMMENT =============================== */ public void visit(LineComment lineComment); diff --git a/src/main/java/com/google/api/generator/engine/ast/LogicalOperationExpr.java b/src/main/java/com/google/api/generator/engine/ast/LogicalOperationExpr.java new file mode 100644 index 0000000000..2387345d31 --- /dev/null +++ b/src/main/java/com/google/api/generator/engine/ast/LogicalOperationExpr.java @@ -0,0 +1,88 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.api.generator.engine.ast; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; + +@AutoValue +public abstract class LogicalOperationExpr implements OperationExpr { + + public abstract Expr lhsExpr(); + + public abstract Expr rhsExpr(); + + public abstract OperatorKind operatorKind(); + + @Override + public TypeNode type() { + return TypeNode.BOOLEAN; + } + + @Override + public void accept(AstNodeVisitor visitor) { + visitor.visit(this); + } + + // Convenience wrapper. + public static LogicalOperationExpr logicalAndWithExprs(Expr lhsExpr, Expr rhsExpr) { + return builder() + .setLhsExpr(lhsExpr) + .setRhsExpr(rhsExpr) + .setOperatorKind(OperatorKind.LOGICAL_AND) + .build(); + } + + // Convenience wrapper. + public static LogicalOperationExpr logicalOrWithExprs(Expr lhsExpr, Expr rhsExpr) { + return builder() + .setLhsExpr(lhsExpr) + .setRhsExpr(rhsExpr) + .setOperatorKind(OperatorKind.LOGICAL_OR) + .build(); + } + + private static Builder builder() { + return new AutoValue_LogicalOperationExpr.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + // Private setter. + abstract Builder setLhsExpr(Expr expr); + + // Private setter. + abstract Builder setRhsExpr(Expr expr); + + // Private setter. + abstract Builder setOperatorKind(OperatorKind operator); + + abstract LogicalOperationExpr autoBuild(); + + private LogicalOperationExpr build() { + LogicalOperationExpr logicalOperationExpr = autoBuild(); + TypeNode lhsExprType = logicalOperationExpr.lhsExpr().type(); + TypeNode rhsExprType = logicalOperationExpr.rhsExpr().type(); + OperatorKind operator = logicalOperationExpr.operatorKind(); + final String errorMsg = + String.format( + "Logical operator %s is valid only on boolean or its boxed type, found %s, %s.", + operator, lhsExprType.toString(), rhsExprType.toString()); + Preconditions.checkState( + lhsExprType.equals(TypeNode.BOOLEAN) && rhsExprType.equals(TypeNode.BOOLEAN), errorMsg); + return logicalOperationExpr; + } + } +} diff --git a/src/main/java/com/google/api/generator/engine/ast/OperatorKind.java b/src/main/java/com/google/api/generator/engine/ast/OperatorKind.java index 8805695ed8..5c5a6a4642 100644 --- a/src/main/java/com/google/api/generator/engine/ast/OperatorKind.java +++ b/src/main/java/com/google/api/generator/engine/ast/OperatorKind.java @@ -18,6 +18,8 @@ public enum OperatorKind { ARITHMETIC_ADDITION, + LOGICAL_AND, + LOGICAL_OR, RELATIONAL_EQUAL_TO, RELATIONAL_NOT_EQUAL_TO, UNARY_LOGICAL_NOT, 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 2107b92d23..fadfaaad0d 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 @@ -34,6 +34,7 @@ import com.google.api.generator.engine.ast.InstanceofExpr; import com.google.api.generator.engine.ast.JavaDocComment; import com.google.api.generator.engine.ast.LineComment; +import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; @@ -226,6 +227,12 @@ public void visit(UnaryOperationExpr unaryOperationExpr) { unaryOperationExpr.expr().accept(this); } + @Override + public void visit(LogicalOperationExpr logicalOperationExpr) { + logicalOperationExpr.lhsExpr().accept(this); + logicalOperationExpr.rhsExpr().accept(this); + } + /** =============================== STATEMENTS =============================== */ @Override public void visit(ExprStatement exprStatement) { 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 6acbcbd7fa..ff99718c74 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 @@ -34,6 +34,7 @@ import com.google.api.generator.engine.ast.InstanceofExpr; import com.google.api.generator.engine.ast.JavaDocComment; import com.google.api.generator.engine.ast.LineComment; +import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; @@ -110,6 +111,8 @@ public class JavaWriterVisitor implements AstNodeVisitor { private static final String OPERATOR_NOT_EQUAL_TO = "!="; private static final String OPERATOR_INCREMENT = "++"; private static final String OPERATOR_LOGICAL_NOT = "!"; + private static final String OPERATOR_LOGICAL_AND = "&&"; + private static final String OPERATOR_LOGICAL_OR = "||"; private final StringBuffer buffer = new StringBuffer(); private final ImportWriterVisitor importWriterVisitor = new ImportWriterVisitor(); @@ -407,6 +410,15 @@ public void visit(UnaryOperationExpr unaryOperationExpr) { } } + @Override + public void visit(LogicalOperationExpr logicalOperationExpr) { + logicalOperationExpr.lhsExpr().accept(this); + space(); + operator(logicalOperationExpr.operatorKind()); + space(); + logicalOperationExpr.rhsExpr().accept(this); + } + /** =============================== STATEMENTS =============================== */ @Override public void visit(ExprStatement exprStatement) { @@ -905,6 +917,12 @@ private void operator(OperatorKind kind) { case ARITHMETIC_ADDITION: buffer.append(OPERATOR_ADDITION); break; + case LOGICAL_AND: + buffer.append(OPERATOR_LOGICAL_AND); + break; + case LOGICAL_OR: + buffer.append(OPERATOR_LOGICAL_OR); + break; } } } diff --git a/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel b/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel index 95c75058d8..9809e2fa11 100644 --- a/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel +++ b/src/test/java/com/google/api/generator/engine/ast/BUILD.bazel @@ -36,6 +36,7 @@ TESTS = [ "WhileStatementTest", "ArithmeticOperationExprTest", "UnaryOperationExprTest", + "LogicalOperationExprTest", ] filegroup( diff --git a/src/test/java/com/google/api/generator/engine/ast/LogicalOperationExprTest.java b/src/test/java/com/google/api/generator/engine/ast/LogicalOperationExprTest.java new file mode 100644 index 0000000000..9ea489ef27 --- /dev/null +++ b/src/test/java/com/google/api/generator/engine/ast/LogicalOperationExprTest.java @@ -0,0 +1,107 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.api.generator.engine.ast; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +public class LogicalOperationExprTest { + /** =============================== Logic And Operation Expr =============================== */ + @Test + public void logicalAnd_validBasic() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("isGood").build()); + VariableExpr rhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("isValid").build()); + LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr); + // No exception thrown, so we succeeded. + } + + @Test + public void logicalAnd_validBoxedBoolean() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("isGood").build()); + VariableExpr rhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN_OBJECT).setName("isValid").build()); + LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr); + // No exception thrown, so we succeeded. + } + + @Test + public void logicalAnd_invalidNumericType() { + VariableExpr lhsExpr = + VariableExpr.withVariable(Variable.builder().setType(TypeNode.INT).setName("x").build()); + VariableExpr rhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("isValid").build()); + assertThrows( + IllegalStateException.class, + () -> LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr)); + } + + @Test + public void logicalAnd_invalidStringType() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN_OBJECT).setName("x").build()); + VariableExpr rhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.STRING).setName("isValid").build()); + assertThrows( + IllegalStateException.class, + () -> LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr)); + } + + /** =============================== Logic Or Operation Expr =============================== */ + @Test + public void logicalOr_validBoxedBoolean() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN_OBJECT).setName("isGood").build()); + VariableExpr rhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN_OBJECT).setName("isValid").build()); + LogicalOperationExpr.logicalOrWithExprs(lhsExpr, rhsExpr); + // No exception thrown, so we succeeded. + } + + @Test + public void logicalOr_invalidVoidType() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("x").build()); + MethodInvocationExpr rhsExpr = + MethodInvocationExpr.builder().setMethodName("doNothing").build(); + assertThrows( + IllegalStateException.class, + () -> LogicalOperationExpr.logicalOrWithExprs(lhsExpr, rhsExpr)); + } + + @Test + public void logicalOr_invalidNullType() { + VariableExpr lhsExpr = + VariableExpr.withVariable( + Variable.builder().setType(TypeNode.BOOLEAN).setName("x").build()); + ValueExpr rhsExpr = ValueExpr.withValue(NullObjectValue.create()); + assertThrows( + IllegalStateException.class, + () -> LogicalOperationExpr.logicalOrWithExprs(lhsExpr, rhsExpr)); + } +} 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 1968d2f681..d82683a843 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 @@ -30,6 +30,7 @@ import com.google.api.generator.engine.ast.ExprStatement; import com.google.api.generator.engine.ast.IfStatement; import com.google.api.generator.engine.ast.InstanceofExpr; +import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; @@ -938,6 +939,25 @@ public void writeUnaryOperationExprImports_PostIncrement() { assertEquals(writerVisitor.write(), "import com.google.api.generator.engine.ast.Expr;\n\n"); } + @Test + public void writeLogicalOperationExprImports() { + MethodInvocationExpr lhsExpr = + MethodInvocationExpr.builder() + .setStaticReferenceType( + TypeNode.withReference(ConcreteReference.withClazz(UnaryOperationExpr.class))) + .setMethodName("isValid") + .setReturnType(TypeNode.BOOLEAN) + .build(); + VariableExpr rhsExpr = + VariableExpr.builder().setVariable(createVariable("isGood", TypeNode.BOOLEAN)).build(); + LogicalOperationExpr logicalOperationExpr = + LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr); + logicalOperationExpr.accept(writerVisitor); + assertEquals( + writerVisitor.write(), + "import com.google.api.generator.engine.ast.UnaryOperationExpr;\n\n"); + } + private static TypeNode createType(Class clazz) { return TypeNode.withReference(ConcreteReference.withClazz(clazz)); } 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 ada3f647bc..afe36c3430 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 @@ -37,6 +37,7 @@ import com.google.api.generator.engine.ast.InstanceofExpr; import com.google.api.generator.engine.ast.JavaDocComment; import com.google.api.generator.engine.ast.LineComment; +import com.google.api.generator.engine.ast.LogicalOperationExpr; import com.google.api.generator.engine.ast.MethodDefinition; import com.google.api.generator.engine.ast.MethodInvocationExpr; import com.google.api.generator.engine.ast.NewObjectExpr; @@ -2057,6 +2058,38 @@ public void writeUnaryOperationExpr_logicalNot() { assertThat(writerVisitor.write()).isEqualTo("!isEmpty()"); } + @Test + public void writeLogicalOperationExpr_logicalAnd() { + VariableExpr lhsExpr = VariableExpr.withVariable(createVariable("isEmpty", TypeNode.BOOLEAN)); + VaporReference ref = + VaporReference.builder().setName("Student").setPakkage("com.google.example.v1").build(); + TypeNode classType = TypeNode.withReference(ref); + MethodInvocationExpr rhsExpr = + MethodInvocationExpr.builder() + .setMethodName("isValid") + .setExprReferenceExpr(ValueExpr.withValue(ThisObjectValue.withType(classType))) + .setReturnType(TypeNode.BOOLEAN) + .build(); + LogicalOperationExpr logicalOperationExpr = + LogicalOperationExpr.logicalAndWithExprs(lhsExpr, rhsExpr); + logicalOperationExpr.accept(writerVisitor); + assertThat(writerVisitor.write()).isEqualTo("isEmpty && this.isValid()"); + } + + @Test + public void writeLogicalOperationExpr_logicalOr() { + VariableExpr lhsExpr = VariableExpr.withVariable(createVariable("isGood", TypeNode.BOOLEAN)); + MethodInvocationExpr rhsExpr = + MethodInvocationExpr.builder() + .setMethodName("isValid") + .setReturnType(TypeNode.BOOLEAN) + .build(); + LogicalOperationExpr logicalOperationExpr = + LogicalOperationExpr.logicalOrWithExprs(lhsExpr, rhsExpr); + logicalOperationExpr.accept(writerVisitor); + assertThat(writerVisitor.write()).isEqualTo("isGood || isValid()"); + } + private static String createLines(int numLines) { return new String(new char[numLines]).replace("\0", "%s"); }