diff --git a/src/main/java/com/google/api/generator/engine/ast/AnnotationNode.java b/src/main/java/com/google/api/generator/engine/ast/AnnotationNode.java index 45464b333b..7cb0f5ec37 100644 --- a/src/main/java/com/google/api/generator/engine/ast/AnnotationNode.java +++ b/src/main/java/com/google/api/generator/engine/ast/AnnotationNode.java @@ -108,6 +108,17 @@ public Builder setDescription(VariableExpr variableExpr) { return setDescriptionExprs(Arrays.asList(variableExpr)); } + /** + * To set single ArrayExpr as description. + * + * @param arrayExpr + * @return Builder + */ + public Builder setDescription(ArrayExpr arrayExpr) { + Preconditions.checkState(descriptionExprs() == null, REPEAT_SINGLE_EXCEPTION_MESSAGE); + return setDescriptionExprs(Arrays.asList(arrayExpr)); + } + /** * To add an AssignmentExpr as parameter. Can be used repeatedly to add multiple parameters. * diff --git a/src/main/java/com/google/api/generator/engine/ast/ArrayExpr.java b/src/main/java/com/google/api/generator/engine/ast/ArrayExpr.java new file mode 100644 index 0000000000..92d79246a1 --- /dev/null +++ b/src/main/java/com/google/api/generator/engine/ast/ArrayExpr.java @@ -0,0 +1,92 @@ +// Copyright 2022 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; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.List; + +@AutoValue +public abstract class ArrayExpr implements Expr { + + public abstract List exprs(); + + public abstract TypeNode type(); + + @Override + public void accept(AstNodeVisitor visitor) { + visitor.visit(this); + } + + public static ArrayExpr.Builder builder() { + return new AutoValue_ArrayExpr.Builder().setExprs(ImmutableList.of()); + } + + public static ArrayExpr withStrings(String... stringValues) { + ArrayExpr.Builder builder = ArrayExpr.builder(); + Arrays.asList(stringValues).stream().forEach(s -> builder.addExpr(s)); + return builder.build(); + } + + public static ArrayExpr withExprs(Expr... exprs) { + return ArrayExpr.builder().setExprs(Arrays.asList(exprs)).build(); + } + + @AutoValue.Builder + public abstract static class Builder { + + private static final String SAME_TYPE_EXPRS_MESSAGE = + "All expressions must be of the type" + " specified in this ArrayExpr"; + private static final String EXPR_ALLOWED_CLASSES_MESSAGE = + "Only VariableExpr and ValueExpr can be used as elements of ArrayExpr"; + + abstract List exprs(); + + abstract TypeNode type(); + + /** + * To add a string expression same-type validation is performed + * + * @param expr + * @return Builder + */ + public ArrayExpr.Builder addExpr(String expr) { + return addExpr(ValueExpr.withValue(StringObjectValue.withValue(expr))); + } + + public ArrayExpr.Builder addExpr(Expr expr) { + return setExprs((new ImmutableList.Builder().addAll(exprs()).add(expr).build())); + } + + public abstract ArrayExpr.Builder setExprs(List exprs); + + public abstract ArrayExpr.Builder setType(TypeNode type); + + abstract ArrayExpr autoBuild(); + + public ArrayExpr build() { + Preconditions.checkState( + exprs().stream().allMatch(exp -> exp instanceof ValueExpr || exp instanceof VariableExpr), + EXPR_ALLOWED_CLASSES_MESSAGE); + TypeNode elementType = TypeNode.createElementTypeFromArrayType(type()); + Preconditions.checkState( + exprs().stream().allMatch(exp -> exp.type().equals(elementType)), + SAME_TYPE_EXPRS_MESSAGE); + return autoBuild(); + } + } +} 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 3d3fbe8c47..8680e94fcb 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 @@ -24,6 +24,8 @@ public interface AstNodeVisitor { public void visit(AnnotationNode annotation); + public void visit(ArrayExpr expr); + public void visit(ConcreteReference reference); public void visit(VaporReference reference); 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 1814be11aa..b217feb057 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 @@ -101,6 +101,23 @@ public enum TypeKind { public abstract boolean isArray(); + public static TypeNode createArrayTypeOf(TypeNode type) { + return builder() + .setTypeKind(type.typeKind()) + .setReference(type.reference()) + .setIsArray(true) + .build(); + } + + public static TypeNode createElementTypeFromArrayType(TypeNode type) { + Preconditions.checkArgument(type.isArray(), "Input type must be an array"); + return builder() + .setTypeKind(type.typeKind()) + .setReference(type.reference()) + .setIsArray(false) + .build(); + } + @Nullable public abstract Reference reference(); 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 b3b2c9d997..37dd4a5733 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 @@ -17,6 +17,7 @@ import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AnonymousClassExpr; import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.ArrayExpr; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.AssignmentOperationExpr; import com.google.api.generator.engine.ast.AstNodeVisitor; @@ -167,6 +168,11 @@ public void visit(AnnotationNode annotation) { annotation.type().accept(this); } + @Override + public void visit(ArrayExpr expr) { + expr.type().accept(this); + } + /** =============================== EXPRESSIONS =============================== */ @Override public void visit(ValueExpr valueExpr) { 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 731411bab6..e092f1c69c 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 @@ -17,6 +17,7 @@ import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AnonymousClassExpr; import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.ArrayExpr; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.AssignmentOperationExpr; import com.google.api.generator.engine.ast.AstNodeVisitor; @@ -165,6 +166,19 @@ public void visit(ScopeNode scope) { buffer.append(scope.toString()); } + @Override + public void visit(ArrayExpr expr) { + buffer.append(LEFT_BRACE); + for (int i = 0; i < expr.exprs().size(); i++) { + expr.exprs().get(i).accept(this); + if (i < expr.exprs().size() - 1) { + buffer.append(COMMA); + buffer.append(SPACE); + } + } + buffer.append(RIGHT_BRACE); + } + @Override public void visit(AnnotationNode annotation) { buffer.append(AT); diff --git a/src/test/java/com/google/api/generator/engine/ast/ArrayExprTest.java b/src/test/java/com/google/api/generator/engine/ast/ArrayExprTest.java new file mode 100644 index 0000000000..723c60ad4f --- /dev/null +++ b/src/test/java/com/google/api/generator/engine/ast/ArrayExprTest.java @@ -0,0 +1,69 @@ +// Copyright 2022 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 com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.api.generator.util.TestUtils; +import org.junit.Test; + +public class ArrayExprTest { + + @Test + public void validAnonymousArray_sametype() { + ArrayExpr.Builder exprBuilder = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.STRING)) + .addExpr(TestUtils.generateStringValueExpr("test1")) + .addExpr(TestUtils.generateStringValueExpr("test2")) + .addExpr(TestUtils.generateStringValueExpr("test3")) + .addExpr( + ValueExpr.withValue( + PrimitiveValue.builder().setValue("1").setType(TypeNode.INT).build())); + + Exception thrown = assertThrows(IllegalStateException.class, () -> exprBuilder.build()); + assertThat(thrown) + .hasMessageThat() + .contains("All expressions must be of the type specified in this ArrayExpr"); + } + + @Test + public void validAnonymousArray_unsetTypeThrows() { + ArrayExpr.Builder exprBuilder = ArrayExpr.builder(); + IllegalStateException thrown = + assertThrows(IllegalStateException.class, () -> exprBuilder.build()); + assertThat(thrown).hasMessageThat().contains("Property \"type\" has not been set"); + } + + @Test + public void validAnonymousArray_onlyVariableAndValueExprs() { + ArrayExpr.Builder exprBuilder = + ArrayExpr.builder().setType(TypeNode.createArrayTypeOf(TypeNode.INT)); + Variable variable = Variable.builder().setName("x").setType(TypeNode.INT).build(); + VariableExpr variableExpr = + VariableExpr.builder().setVariable(variable).setIsDecl(true).build(); + Value value = PrimitiveValue.builder().setType(TypeNode.INT).setValue("3").build(); + Expr valueExpr = ValueExpr.builder().setValue(value).build(); + AssignmentExpr assignExpr = + AssignmentExpr.builder().setVariableExpr(variableExpr).setValueExpr(valueExpr).build(); + exprBuilder.addExpr(assignExpr); + IllegalStateException thrown = + assertThrows(IllegalStateException.class, () -> exprBuilder.build()); + assertThat(thrown) + .hasMessageThat() + .contains("Only VariableExpr and ValueExpr can be used as elements of ArrayExpr"); + } +} 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 bd8d62afb7..865f505a62 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 @@ -20,6 +20,7 @@ import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AnonymousClassExpr; import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.ArrayExpr; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.BlockComment; import com.google.api.generator.engine.ast.CommentStatement; @@ -919,6 +920,20 @@ public void writeLambdaExprImports() { writerVisitor.write()); } + @Test + public void importArrayExprTypes() { + ArrayExpr arrayExpr = + ArrayExpr.builder() + .setType( + TypeNode.createArrayTypeOf( + TypeNode.withReference(ConcreteReference.withClazz(UnaryOperationExpr.class)))) + .build(); + arrayExpr.accept(writerVisitor); + assertEquals( + "import com.google.api.generator.engine.ast.UnaryOperationExpr;\n\n", + writerVisitor.write()); + } + /** =============================== HELPERS =============================== */ private static Variable createVariable(String variableName, TypeNode type) { return Variable.builder().setName(variableName).setType(type).build(); 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 713aa57939..9852b44a65 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 @@ -21,6 +21,7 @@ import com.google.api.generator.engine.ast.AnnotationNode; import com.google.api.generator.engine.ast.AnonymousClassExpr; import com.google.api.generator.engine.ast.ArithmeticOperationExpr; +import com.google.api.generator.engine.ast.ArrayExpr; import com.google.api.generator.engine.ast.AssignmentExpr; import com.google.api.generator.engine.ast.AssignmentOperationExpr; import com.google.api.generator.engine.ast.BlockComment; @@ -71,6 +72,7 @@ import com.google.api.generator.engine.ast.VariableExpr; import com.google.api.generator.engine.ast.WhileStatement; import com.google.api.generator.testutils.LineFormatter; +import com.google.api.generator.util.TestUtils; import com.google.common.base.Function; import java.io.IOException; import java.util.Arrays; @@ -308,6 +310,148 @@ public void writeAnnotation_withInvalidDescriptions() { .contains("Multiple parameters must have names"); } + @Test + public void writeAnnotation_withArrayExpr() { + TypeNode fakeAnnotationType = + TypeNode.withReference( + VaporReference.builder().setName("FakeAnnotation").setPakkage("com.foo.bar").build()); + AnnotationNode annotation = + AnnotationNode.builder() + .setType(fakeAnnotationType) + .setDescription( + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .addExpr(TestUtils.generateClassValueExpr("Class1")) + .addExpr(TestUtils.generateClassValueExpr("Class2")) + .build()) + .build(); + annotation.accept(writerVisitor); + assertEquals("@FakeAnnotation({Class1.class, Class2.class})\n", writerVisitor.write()); + } + + @Test + public void writeAnnotation_withArrayExprAssignment() { + TypeNode fakeAnnotationType = + TypeNode.withReference( + VaporReference.builder().setName("FakeAnnotation").setPakkage("com.foo.bar").build()); + ArrayExpr arrayExpr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .addExpr(TestUtils.generateClassValueExpr("Class1")) + .addExpr(TestUtils.generateClassValueExpr("Class2")) + .build(); + AssignmentExpr clazz1AssignExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("value1") + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .build()) + .build()) + .setValueExpr(arrayExpr) + .build(); + AssignmentExpr clazz2AssignExpr = + AssignmentExpr.builder() + .setVariableExpr( + VariableExpr.withVariable( + Variable.builder() + .setName("value2") + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .build())) + .setValueExpr(arrayExpr) + .build(); + AnnotationNode annotation = + AnnotationNode.builder() + .setType(fakeAnnotationType) + .addDescription(clazz1AssignExpr) + .addDescription(clazz2AssignExpr) + .build(); + annotation.accept(writerVisitor); + assertEquals( + "@FakeAnnotation(value1 = {Class1.class, Class2.class}, " + + "value2 = {Class1.class, Class2.class})\n", + writerVisitor.write()); + } + + @Test + public void writeArrayExpr_add1StringExpr() { + ArrayExpr expr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.STRING)) + .addExpr(ValueExpr.builder().setValue(StringObjectValue.withValue("test1")).build()) + .build(); + expr.accept(writerVisitor); + assertEquals("{\"test1\"}", writerVisitor.write()); + } + + @Test + public void writeArrayExpr_addManyStrExpr() { + ArrayExpr expr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.STRING)) + .addExpr(TestUtils.generateStringValueExpr("test1")) + .addExpr(TestUtils.generateStringValueExpr("test2")) + .addExpr(TestUtils.generateStringValueExpr("test3")) + .build(); + expr.accept(writerVisitor); + assertEquals("{\"test1\", \"test2\", \"test3\"}", writerVisitor.write()); + } + + @Test + public void writeArrayExpr_addManyClassExpr() { + ArrayExpr expr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .addExpr(TestUtils.generateClassValueExpr("Class1")) + .addExpr(TestUtils.generateClassValueExpr("Class2")) + .addExpr(TestUtils.generateClassValueExpr("Class3")) + .build(); + expr.accept(writerVisitor); + assertEquals("{Class1.class, Class2.class, Class3.class}", writerVisitor.write()); + } + + @Test + public void writeArrayExpr_mixedVariablesStaticAndNormalReference() { + VariableExpr clazzVar = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("clazz1Var").setType(TypeNode.CLASS_OBJECT).build()) + .build(); + ArrayExpr expr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.CLASS_OBJECT)) + .addExpr(clazzVar) + .addExpr(TestUtils.generateClassValueExpr("Class2")) + .build(); + expr.accept(writerVisitor); + assertEquals("{clazz1Var, Class2.class}", writerVisitor.write()); + } + + @Test + public void writeArrayExpr_assignemntWithDeclaration() { + VariableExpr varExpr = + VariableExpr.builder() + .setVariable( + Variable.builder() + .setName("varExpr") + .setType(TypeNode.createArrayTypeOf(TypeNode.STRING)) + .build()) + .setIsDecl(true) + .build(); + ArrayExpr expr = + ArrayExpr.builder() + .setType(TypeNode.createArrayTypeOf(TypeNode.STRING)) + .addExpr(TestUtils.generateStringValueExpr("str1")) + .addExpr(TestUtils.generateStringValueExpr("str2")) + .build(); + AssignmentExpr assignmentExpr = + AssignmentExpr.builder().setVariableExpr(varExpr).setValueExpr(expr).build(); + assignmentExpr.accept(writerVisitor); + assertEquals("String[] varExpr = {\"str1\", \"str2\"}", writerVisitor.write()); + } + @Test public void writeNewObjectExpr_basic() { // isGeneric() is true, but generics() is empty. diff --git a/src/test/java/com/google/api/generator/util/TestUtils.java b/src/test/java/com/google/api/generator/util/TestUtils.java new file mode 100644 index 0000000000..87ccb5832d --- /dev/null +++ b/src/test/java/com/google/api/generator/util/TestUtils.java @@ -0,0 +1,41 @@ +// Copyright 2022 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.util; + +import com.google.api.generator.engine.ast.StringObjectValue; +import com.google.api.generator.engine.ast.TypeNode; +import com.google.api.generator.engine.ast.TypeNode.TypeKind; +import com.google.api.generator.engine.ast.ValueExpr; +import com.google.api.generator.engine.ast.VaporReference; +import com.google.api.generator.engine.ast.Variable; +import com.google.api.generator.engine.ast.VariableExpr; + +public class TestUtils { + public static ValueExpr generateStringValueExpr(String value) { + return ValueExpr.builder().setValue(StringObjectValue.withValue(value)).build(); + } + + public static VariableExpr generateClassValueExpr(String clazzName) { + return VariableExpr.builder() + .setVariable(Variable.builder().setType(TypeNode.CLASS_OBJECT).setName("class").build()) + .setStaticReferenceType( + TypeNode.builder() + .setReference( + VaporReference.builder().setName(clazzName).setPakkage("com.test").build()) + .setTypeKind(TypeKind.OBJECT) + .build()) + .build(); + } +}