Skip to content

Commit

Permalink
feat: SampleComposer, Sample, Region Tag (pt1) (#933)
Browse files Browse the repository at this point in the history
* feat: SampleComposer, Sample, Region Tag

* chore: update sample formatter name

* refactor: writing

* refactor: include sync/async region tag attribute

* refactor: naming updates

* refactor: update naming - tests

* chore: update formatter regex

Security Hotspot

* chore: update regex

be more explicit
  • Loading branch information
eaball35 committed Mar 17, 2022
1 parent 8d15cfb commit b7cf105
Show file tree
Hide file tree
Showing 14 changed files with 1,072 additions and 32 deletions.
Expand Up @@ -14,6 +14,7 @@

package com.google.api.generator.engine.ast;

import com.google.api.generator.gapic.model.RegionTag;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
Expand All @@ -26,6 +27,9 @@
public abstract class ClassDefinition implements AstNode {
// Optional.
public abstract ImmutableList<CommentStatement> fileHeader();
// Required for samples classes.
@Nullable
public abstract RegionTag regionTag();
// Required.
public abstract ScopeNode scope();
// Required.
Expand Down Expand Up @@ -92,6 +96,8 @@ public Builder setFileHeader(CommentStatement... headerComments) {

public abstract Builder setFileHeader(List<CommentStatement> fileHeader);

public abstract Builder setRegionTag(RegionTag regionTag);

public Builder setHeaderCommentStatements(CommentStatement... comments) {
return setHeaderCommentStatements(Arrays.asList(comments));
}
Expand Down
Expand Up @@ -60,7 +60,6 @@
import com.google.api.generator.engine.ast.VaporReference;
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.engine.ast.WhileStatement;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -357,13 +356,7 @@ public void visit(TryCatchStatement tryCatchStatement) {
if (tryCatchStatement.tryResourceExpr() != null) {
tryCatchStatement.tryResourceExpr().accept(this);
}

statements(tryCatchStatement.tryBody());

Preconditions.checkState(
!tryCatchStatement.isSampleCode() && !tryCatchStatement.catchVariableExprs().isEmpty(),
"Import generation should not be invoked on sample code, but was found when visiting a"
+ " try-catch block");
for (int i = 0; i < tryCatchStatement.catchVariableExprs().size(); i++) {
tryCatchStatement.catchVariableExprs().get(i).accept(this);
statements(tryCatchStatement.catchBlocks().get(i));
Expand Down
Expand Up @@ -63,6 +63,7 @@
import com.google.api.generator.engine.ast.Variable;
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.engine.ast.WhileStatement;
import com.google.api.generator.gapic.model.RegionTag;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -910,6 +911,15 @@ public void visit(ClassDefinition classDefinition) {
newline();
}

String regionTagReplace = "REPLACE_REGION_TAG";
if (classDefinition.regionTag() != null) {
statements(
Arrays.asList(
classDefinition
.regionTag()
.generateTag(RegionTag.RegionTagRegion.START, regionTagReplace)));
}

// This must go first, so that we can check for type collisions.
classDefinition.accept(importWriterVisitor);
if (!classDefinition.isNested()) {
Expand Down Expand Up @@ -974,10 +984,27 @@ public void visit(ClassDefinition classDefinition) {
classes(classDefinition.nestedClasses());

rightBrace();
if (classDefinition.regionTag() != null) {
statements(
Arrays.asList(
classDefinition
.regionTag()
.generateTag(RegionTag.RegionTagRegion.END, regionTagReplace)));
}

// We should have valid Java by now, so format it.
if (!classDefinition.isNested()) {
buffer.replace(0, buffer.length(), JavaFormatter.format(buffer.toString()));
String formattedClazz = JavaFormatter.format(buffer.toString());

// fixing region tag after formatting
// formatter splits long region tags on multiple lines and moves the end tag up - doesn't meet
// tag requirements
if (classDefinition.regionTag() != null) {
formattedClazz =
formattedClazz.replaceAll(regionTagReplace, classDefinition.regionTag().generate());
formattedClazz = formattedClazz.replaceAll("} // \\[END", "}\n// \\[END");
}
buffer.replace(0, buffer.length(), formattedClazz);
}
}

Expand Down
Expand Up @@ -17,6 +17,9 @@
import com.google.api.generator.engine.ast.BlockComment;
import com.google.api.generator.engine.ast.CommentStatement;
import com.google.api.generator.engine.ast.LineComment;
import com.google.api.generator.engine.ast.Statement;
import java.util.Arrays;
import java.util.List;

public class CommentComposer {
private static final String APACHE_LICENSE_STRING =
Expand Down Expand Up @@ -52,4 +55,13 @@ public class CommentComposer {
public static final CommentStatement AUTO_GENERATED_METHOD_COMMENT =
CommentStatement.withComment(
LineComment.withComment(AUTO_GENERATED_METHOD_DISCLAIMER_STRING));

public static final List<Statement> AUTO_GENERATED_SAMPLE_COMMENT =
Arrays.asList(
CommentStatement.withComment(
LineComment.withComment(
"This snippet has been automatically generated for illustrative purposes only.")),
CommentStatement.withComment(
LineComment.withComment(
"It may require modifications to work in your environment.")));
}
Expand Up @@ -17,14 +17,16 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class SampleCodeJavaFormatter {
public final class SampleCodeBodyJavaFormatter {

private SampleCodeJavaFormatter() {}
private SampleCodeBodyJavaFormatter() {}

private static final Formatter FORMATTER = new Formatter();

private static final String FAKE_CLASS_TITLE = "public class FakeClass { void fakeMethod() {";
private static final String FAKE_CLASS_TITLE = "public class FakeClass { void fakeMethod() {\n";
private static final String FAKE_CLASS_CLOSE = "}}";

/**
Expand Down Expand Up @@ -52,10 +54,15 @@ public static String format(String sampleCode) {
// 1. Removing the first and last two lines.
// 2. Delete the first 4 space for each line.
// 3. Trim the last new empty line.
return formattedString
.replaceAll("^([^\n]*\n){2}|([^\n]*\n){2}$", "")
.replaceAll("(?m)^ {4}", "")
.trim();
Pattern pattern = Pattern.compile("(^([^\n]*\n){2})|(([^\n]*\n){2}$)");
Matcher matcher = pattern.matcher(formattedString);
formattedString = matcher.replaceAll("");

pattern = Pattern.compile("(?m)^ {4}");
matcher = pattern.matcher(formattedString);
formattedString = matcher.replaceAll("");

return formattedString.trim();
}

@VisibleForTesting
Expand Down
Expand Up @@ -14,24 +14,43 @@

package com.google.api.generator.gapic.composer.samplecode;

import com.google.api.generator.engine.ast.ClassDefinition;
import com.google.api.generator.engine.ast.Statement;
import com.google.api.generator.engine.writer.JavaWriterVisitor;
import com.google.api.generator.gapic.model.Sample;
import com.google.common.annotations.VisibleForTesting;
import java.util.Arrays;
import java.util.List;

public final class SampleCodeWriter {

public static String write(Statement... statement) {
return write(Arrays.asList(statement));
public static String writeInlineSample(List<Statement> statements) {
return write(SampleComposer.composeInlineSample(statements));
}

public static String writeExecutableSample(Sample sample, String packkage) {
return write(SampleComposer.composeExecutableSample(sample, packkage));
}

@VisibleForTesting
public static String write(List<Statement> statements) {
JavaWriterVisitor visitor = new JavaWriterVisitor();
for (Statement statement : statements) {
statement.accept(visitor);
}
String formattedSampleCode = SampleCodeJavaFormatter.format(visitor.write());
String formattedSampleCode = SampleCodeBodyJavaFormatter.format(visitor.write());
// Escape character "@" in the markdown code block <pre>{@code...} tags.
return formattedSampleCode.replaceAll("@", "{@literal @}");
}

@VisibleForTesting
public static String write(ClassDefinition classDefinition) {
JavaWriterVisitor visitor = new JavaWriterVisitor();
classDefinition.accept(visitor);
return visitor.write();
}

public static String write(Statement... statement) {
return write(Arrays.asList(statement));
}
}
@@ -0,0 +1,163 @@
// 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.gapic.composer.samplecode;

import com.google.api.generator.engine.ast.AssignmentExpr;
import com.google.api.generator.engine.ast.ClassDefinition;
import com.google.api.generator.engine.ast.CommentStatement;
import com.google.api.generator.engine.ast.Expr;
import com.google.api.generator.engine.ast.ExprStatement;
import com.google.api.generator.engine.ast.MethodDefinition;
import com.google.api.generator.engine.ast.MethodInvocationExpr;
import com.google.api.generator.engine.ast.ScopeNode;
import com.google.api.generator.engine.ast.Statement;
import com.google.api.generator.engine.ast.TypeNode;
import com.google.api.generator.engine.ast.Variable;
import com.google.api.generator.engine.ast.VariableExpr;
import com.google.api.generator.gapic.composer.comment.CommentComposer;
import com.google.api.generator.gapic.model.RegionTag;
import com.google.api.generator.gapic.model.Sample;
import com.google.api.generator.gapic.utils.JavaStyle;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SampleComposer {
static List<Statement> composeInlineSample(List<Statement> sampleBody) {
return bodyWithComment(CommentComposer.AUTO_GENERATED_SAMPLE_COMMENT, sampleBody);
}

// "Executable" meaning it includes the necessary code to execute java code,
// still may require additional configuration to actually execute generated sample code
static ClassDefinition composeExecutableSample(Sample sample, String pakkage) {
return createExecutableSample(
sample.fileHeader(),
pakkage,
sample.name(),
sample.variableAssignments(),
bodyWithComment(CommentComposer.AUTO_GENERATED_SAMPLE_COMMENT, sample.body()),
sample.regionTag());
}

private static List<Statement> bodyWithComment(
List<Statement> autoGeneratedComment, List<Statement> sampleBody) {
List<Statement> bodyWithComment = new ArrayList<>(autoGeneratedComment);
bodyWithComment.addAll(sampleBody);
return bodyWithComment;
}

private static ClassDefinition createExecutableSample(
List<CommentStatement> fileHeader,
String packageName,
String sampleClassName,
List<AssignmentExpr> sampleVariableAssignments,
List<Statement> sampleBody,
RegionTag regionTag) {

String sampleMethodName = JavaStyle.toLowerCamelCase(sampleClassName);
List<VariableExpr> sampleMethodArgs = composeSampleMethodArgs(sampleVariableAssignments);
MethodDefinition mainMethod =
composeMainMethod(
composeMainBody(
sampleVariableAssignments,
composeInvokeMethodStatement(sampleMethodName, sampleMethodArgs)));
MethodDefinition sampleMethod =
composeSampleMethod(sampleMethodName, sampleMethodArgs, sampleBody);
return composeSampleClass(
fileHeader, packageName, sampleClassName, mainMethod, sampleMethod, regionTag);
}

private static List<VariableExpr> composeSampleMethodArgs(
List<AssignmentExpr> sampleVariableAssignments) {
return sampleVariableAssignments.stream()
.map(v -> v.variableExpr().toBuilder().setIsDecl(true).build())
.collect(Collectors.toList());
}

private static Statement composeInvokeMethodStatement(
String sampleMethodName, List<VariableExpr> sampleMethodArgs) {
List<Expr> invokeArgs =
sampleMethodArgs.stream()
.map(arg -> arg.toBuilder().setIsDecl(false).build())
.collect(Collectors.toList());
return ExprStatement.withExpr(
MethodInvocationExpr.builder()
.setMethodName(sampleMethodName)
.setArguments(invokeArgs)
.build());
}

private static List<Statement> composeMainBody(
List<AssignmentExpr> sampleVariableAssignments, Statement invokeMethod) {
List<ExprStatement> setVariables =
sampleVariableAssignments.stream()
.map(var -> ExprStatement.withExpr(var))
.collect(Collectors.toList());
List<Statement> body = new ArrayList<>(setVariables);
body.add(invokeMethod);
return body;
}

private static ClassDefinition composeSampleClass(
List<CommentStatement> fileHeader,
String packageName,
String sampleClassName,
MethodDefinition mainMethod,
MethodDefinition sampleMethod,
RegionTag regionTag) {
return ClassDefinition.builder()
.setFileHeader(fileHeader)
.setRegionTag(regionTag)
.setScope(ScopeNode.PUBLIC)
.setPackageString(packageName)
.setName(sampleClassName)
.setMethods(ImmutableList.of(mainMethod, sampleMethod))
.build();
}

private static MethodDefinition composeMainMethod(List<Statement> mainBody) {
return MethodDefinition.builder()
.setScope(ScopeNode.PUBLIC)
.setIsStatic(true)
.setReturnType(TypeNode.VOID)
.setName("main")
.setArguments(
VariableExpr.builder()
.setVariable(
Variable.builder().setType(TypeNode.STRING_ARRAY).setName("args").build())
.setIsDecl(true)
.build())
.setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(Exception.class)))
.setBody(mainBody)
.build();
}

private static MethodDefinition composeSampleMethod(
String sampleMethodName,
List<VariableExpr> sampleMethodArgs,
List<Statement> sampleMethodBody) {
return MethodDefinition.builder()
.setScope(ScopeNode.PUBLIC)
.setIsStatic(true)
.setReturnType(TypeNode.VOID)
.setName(sampleMethodName)
.setArguments(sampleMethodArgs)
.setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(Exception.class)))
.setBody(sampleMethodBody)
.build();
}
}

0 comments on commit b7cf105

Please sign in to comment.