From b7cf105764bdc6c0a952e7b2705a454bbce3eadf Mon Sep 17 00:00:00 2001 From: Emily Ball Date: Thu, 17 Mar 2022 09:56:51 -0700 Subject: [PATCH] feat: SampleComposer, Sample, Region Tag (pt1) (#933) * 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 --- .../generator/engine/ast/ClassDefinition.java | 6 + .../engine/writer/ImportWriterVisitor.java | 7 - .../engine/writer/JavaWriterVisitor.java | 29 +- .../composer/comment/CommentComposer.java | 12 + ....java => SampleCodeBodyJavaFormatter.java} | 21 +- .../composer/samplecode/SampleCodeWriter.java | 25 +- .../composer/samplecode/SampleComposer.java | 163 ++++++++++ .../api/generator/gapic/model/RegionTag.java | 131 ++++++++ .../api/generator/gapic/model/Sample.java | 83 +++++ .../SampleCodeJavaFormatterTest.java | 11 +- .../samplecode/SampleCodeWriterTest.java | 99 +++++- .../samplecode/SampleComposerTest.java | 294 ++++++++++++++++++ .../generator/gapic/model/RegionTagTest.java | 150 +++++++++ .../api/generator/gapic/model/SampleTest.java | 73 +++++ 14 files changed, 1072 insertions(+), 32 deletions(-) rename src/main/java/com/google/api/generator/gapic/composer/samplecode/{SampleCodeJavaFormatter.java => SampleCodeBodyJavaFormatter.java} (80%) create mode 100644 src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleComposer.java create mode 100644 src/main/java/com/google/api/generator/gapic/model/RegionTag.java create mode 100644 src/main/java/com/google/api/generator/gapic/model/Sample.java create mode 100644 src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleComposerTest.java create mode 100644 src/test/java/com/google/api/generator/gapic/model/RegionTagTest.java create mode 100644 src/test/java/com/google/api/generator/gapic/model/SampleTest.java diff --git a/src/main/java/com/google/api/generator/engine/ast/ClassDefinition.java b/src/main/java/com/google/api/generator/engine/ast/ClassDefinition.java index ec8f4f7b43..4e1211ba3a 100644 --- a/src/main/java/com/google/api/generator/engine/ast/ClassDefinition.java +++ b/src/main/java/com/google/api/generator/engine/ast/ClassDefinition.java @@ -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; @@ -26,6 +27,9 @@ public abstract class ClassDefinition implements AstNode { // Optional. public abstract ImmutableList fileHeader(); + // Required for samples classes. + @Nullable + public abstract RegionTag regionTag(); // Required. public abstract ScopeNode scope(); // Required. @@ -92,6 +96,8 @@ public Builder setFileHeader(CommentStatement... headerComments) { public abstract Builder setFileHeader(List fileHeader); + public abstract Builder setRegionTag(RegionTag regionTag); + public Builder setHeaderCommentStatements(CommentStatement... comments) { return setHeaderCommentStatements(Arrays.asList(comments)); } 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 85274e34b9..0967cea087 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 @@ -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; @@ -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)); 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 b1d750157e..d1ef68f34c 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 @@ -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; @@ -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()) { @@ -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); } } diff --git a/src/main/java/com/google/api/generator/gapic/composer/comment/CommentComposer.java b/src/main/java/com/google/api/generator/gapic/composer/comment/CommentComposer.java index 4428170162..68ea3259be 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/comment/CommentComposer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/comment/CommentComposer.java @@ -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 = @@ -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 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."))); } diff --git a/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatter.java b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeBodyJavaFormatter.java similarity index 80% rename from src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatter.java rename to src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeBodyJavaFormatter.java index fe8f0005c7..cb3d9d0039 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatter.java +++ b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeBodyJavaFormatter.java @@ -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 = "}}"; /** @@ -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 diff --git a/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriter.java b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriter.java index 07abeb9a17..60a5092598 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriter.java +++ b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriter.java @@ -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 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 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
{@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));
+  }
 }
diff --git a/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleComposer.java b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleComposer.java
new file mode 100644
index 0000000000..86eb187fd1
--- /dev/null
+++ b/src/main/java/com/google/api/generator/gapic/composer/samplecode/SampleComposer.java
@@ -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 composeInlineSample(List 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 bodyWithComment(
+      List autoGeneratedComment, List sampleBody) {
+    List bodyWithComment = new ArrayList<>(autoGeneratedComment);
+    bodyWithComment.addAll(sampleBody);
+    return bodyWithComment;
+  }
+
+  private static ClassDefinition createExecutableSample(
+      List fileHeader,
+      String packageName,
+      String sampleClassName,
+      List sampleVariableAssignments,
+      List sampleBody,
+      RegionTag regionTag) {
+
+    String sampleMethodName = JavaStyle.toLowerCamelCase(sampleClassName);
+    List 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 composeSampleMethodArgs(
+      List sampleVariableAssignments) {
+    return sampleVariableAssignments.stream()
+        .map(v -> v.variableExpr().toBuilder().setIsDecl(true).build())
+        .collect(Collectors.toList());
+  }
+
+  private static Statement composeInvokeMethodStatement(
+      String sampleMethodName, List sampleMethodArgs) {
+    List 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 composeMainBody(
+      List sampleVariableAssignments, Statement invokeMethod) {
+    List setVariables =
+        sampleVariableAssignments.stream()
+            .map(var -> ExprStatement.withExpr(var))
+            .collect(Collectors.toList());
+    List body = new ArrayList<>(setVariables);
+    body.add(invokeMethod);
+    return body;
+  }
+
+  private static ClassDefinition composeSampleClass(
+      List 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 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 sampleMethodArgs,
+      List 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();
+  }
+}
diff --git a/src/main/java/com/google/api/generator/gapic/model/RegionTag.java b/src/main/java/com/google/api/generator/gapic/model/RegionTag.java
new file mode 100644
index 0000000000..cc740ec33e
--- /dev/null
+++ b/src/main/java/com/google/api/generator/gapic/model/RegionTag.java
@@ -0,0 +1,131 @@
+// 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.model;
+
+import com.google.api.generator.engine.ast.CommentStatement;
+import com.google.api.generator.engine.ast.LineComment;
+import com.google.api.generator.gapic.utils.JavaStyle;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+
+@AutoValue
+public abstract class RegionTag {
+  public abstract String apiShortName();
+
+  public abstract String apiVersion();
+
+  public abstract String serviceName();
+
+  public abstract String rpcName();
+
+  public abstract String overloadDisambiguation();
+
+  public abstract Boolean isAsynchronous();
+
+  public static Builder builder() {
+    return new AutoValue_RegionTag.Builder()
+        .setApiVersion("")
+        .setApiShortName("")
+        .setOverloadDisambiguation("")
+        .setIsAsynchronous(false);
+  }
+
+  abstract RegionTag.Builder toBuilder();
+
+  public final RegionTag withApiVersion(String apiVersion) {
+    return toBuilder().setApiVersion(apiVersion).build();
+  }
+
+  public final RegionTag withApiShortName(String apiShortName) {
+    return toBuilder().setApiShortName(apiShortName).build();
+  }
+
+  public final RegionTag withOverloadDisambiguation(String overloadDisambiguation) {
+    return toBuilder().setOverloadDisambiguation(overloadDisambiguation).build();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setApiVersion(String apiVersion);
+
+    public abstract Builder setApiShortName(String apiShortName);
+
+    public abstract Builder setServiceName(String serviceName);
+
+    public abstract Builder setRpcName(String rpcName);
+
+    public abstract Builder setOverloadDisambiguation(String overloadDisambiguation);
+
+    public abstract Builder setIsAsynchronous(Boolean isAsynchronous);
+
+    abstract String apiVersion();
+
+    abstract String apiShortName();
+
+    abstract String serviceName();
+
+    abstract String rpcName();
+
+    abstract String overloadDisambiguation();
+
+    abstract RegionTag autoBuild();
+
+    public final RegionTag build() {
+      setApiVersion(sanitizeAttributes(apiVersion()));
+      setApiShortName(sanitizeAttributes(apiShortName()));
+      setServiceName(sanitizeAttributes(serviceName()));
+      setRpcName(sanitizeAttributes(rpcName()));
+      setOverloadDisambiguation(sanitizeAttributes(overloadDisambiguation()));
+      return autoBuild();
+    }
+
+    private final String sanitizeAttributes(String attribute) {
+      return JavaStyle.toUpperCamelCase(attribute.replaceAll("[^a-zA-Z0-9]", ""));
+    }
+  }
+
+  public enum RegionTagRegion {
+    START,
+    END
+  }
+
+  public String generate() {
+    Preconditions.checkState(!apiShortName().isEmpty(), "apiShortName can't be empty");
+    Preconditions.checkState(!serviceName().isEmpty(), "serviceName can't be empty");
+    Preconditions.checkState(!rpcName().isEmpty(), "rpcName can't be empty");
+
+    String rt = apiShortName() + "_";
+    if (!apiVersion().isEmpty()) {
+      rt = rt + apiVersion() + "_";
+    }
+    rt = rt + "generated_" + serviceName() + "_" + rpcName();
+    if (!overloadDisambiguation().isEmpty()) {
+      rt = rt + "_" + overloadDisambiguation();
+    }
+    if (isAsynchronous()) {
+      rt = rt + "_async";
+    } else {
+      rt = rt + "_sync";
+    }
+
+    return rt.toLowerCase();
+  }
+
+  public static CommentStatement generateTag(
+      RegionTagRegion regionTagRegion, String regionTagContent) {
+    return CommentStatement.withComment(
+        LineComment.withComment("[" + regionTagRegion.name() + " " + regionTagContent + "]"));
+  }
+}
diff --git a/src/main/java/com/google/api/generator/gapic/model/Sample.java b/src/main/java/com/google/api/generator/gapic/model/Sample.java
new file mode 100644
index 0000000000..5e5458a54d
--- /dev/null
+++ b/src/main/java/com/google/api/generator/gapic/model/Sample.java
@@ -0,0 +1,83 @@
+// 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.model;
+
+import com.google.api.generator.engine.ast.AssignmentExpr;
+import com.google.api.generator.engine.ast.CommentStatement;
+import com.google.api.generator.engine.ast.Statement;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+@AutoValue
+public abstract class Sample {
+  public abstract List body();
+
+  public abstract List variableAssignments();
+
+  public abstract List fileHeader();
+
+  public abstract RegionTag regionTag();
+
+  public abstract String name();
+
+  public static Builder builder() {
+    return new AutoValue_Sample.Builder()
+        .setBody(ImmutableList.of())
+        .setVariableAssignments(ImmutableList.of())
+        .setFileHeader(ImmutableList.of());
+  }
+
+  abstract Builder toBuilder();
+
+  public final Sample withHeader(List header) {
+    return toBuilder().setFileHeader(header).build();
+  }
+
+  public final Sample withRegionTag(RegionTag regionTag) {
+    return toBuilder()
+        .setName(generateSampleClassName(regionTag()))
+        .setRegionTag(regionTag)
+        .build();
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder setBody(List body);
+
+    public abstract Builder setVariableAssignments(List variableAssignments);
+
+    public abstract Builder setFileHeader(List header);
+
+    public abstract Builder setRegionTag(RegionTag regionTag);
+
+    abstract Builder setName(String name);
+
+    abstract Sample autoBuild();
+
+    abstract RegionTag regionTag();
+
+    public final Sample build() {
+      setName(generateSampleClassName(regionTag()));
+      return autoBuild();
+    }
+  }
+
+  private static String generateSampleClassName(RegionTag regionTag) {
+    return (regionTag.isAsynchronous() ? "Async" : "Sync")
+        + regionTag.rpcName()
+        + regionTag.overloadDisambiguation();
+  }
+}
diff --git a/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatterTest.java b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatterTest.java
index 1606c0a440..44fe47158c 100644
--- a/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatterTest.java
+++ b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeJavaFormatterTest.java
@@ -17,7 +17,6 @@
 import static junit.framework.TestCase.assertEquals;
 import static org.junit.Assert.assertThrows;
 
-import com.google.api.generator.gapic.composer.samplecode.SampleCodeJavaFormatter.FormatException;
 import com.google.api.generator.testutils.LineFormatter;
 import org.junit.Test;
 
@@ -26,7 +25,7 @@ public class SampleCodeJavaFormatterTest {
   @Test
   public void validFormatSampleCode_tryCatchStatement() {
     String samplecode = LineFormatter.lines("try(boolean condition = false){", "int x = 3;", "}");
-    String result = SampleCodeJavaFormatter.format(samplecode);
+    String result = SampleCodeBodyJavaFormatter.format(samplecode);
     String expected =
         LineFormatter.lines("try (boolean condition = false) {\n", "  int x = 3;\n", "}");
     assertEquals(expected, result);
@@ -37,7 +36,7 @@ public void validFormatSampleCode_longLineStatement() {
     String sampleCode =
         "SubscriptionAdminSettings subscriptionAdminSettings = "
             + "SubscriptionAdminSettings.newBuilder().setEndpoint(myEndpoint).build();";
-    String result = SampleCodeJavaFormatter.format(sampleCode);
+    String result = SampleCodeBodyJavaFormatter.format(sampleCode);
     String expected =
         LineFormatter.lines(
             "SubscriptionAdminSettings subscriptionAdminSettings =\n",
@@ -51,7 +50,7 @@ public void validFormatSampleCode_longChainMethod() {
         "echoSettingsBuilder.echoSettings().setRetrySettings("
             + "echoSettingsBuilder.echoSettings().getRetrySettings().toBuilder()"
             + ".setTotalTimeout(Duration.ofSeconds(30)).build());";
-    String result = SampleCodeJavaFormatter.format(sampleCode);
+    String result = SampleCodeBodyJavaFormatter.format(sampleCode);
     String expected =
         LineFormatter.lines(
             "echoSettingsBuilder\n",
@@ -69,9 +68,9 @@ public void validFormatSampleCode_longChainMethod() {
   @Test
   public void invalidFormatSampleCode_nonStatement() {
     assertThrows(
-        FormatException.class,
+        SampleCodeBodyJavaFormatter.FormatException.class,
         () -> {
-          SampleCodeJavaFormatter.format("abc");
+          SampleCodeBodyJavaFormatter.format("abc");
         });
   }
 }
diff --git a/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriterTest.java b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriterTest.java
index d9456c7dcf..21012be3c5 100644
--- a/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriterTest.java
+++ b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleCodeWriterTest.java
@@ -14,10 +14,10 @@
 
 package com.google.api.generator.gapic.composer.samplecode;
 
-import static junit.framework.TestCase.assertEquals;
-
 import com.google.api.gax.rpc.ClientSettings;
 import com.google.api.generator.engine.ast.AssignmentExpr;
+import com.google.api.generator.engine.ast.BlockComment;
+import com.google.api.generator.engine.ast.CommentStatement;
 import com.google.api.generator.engine.ast.ConcreteReference;
 import com.google.api.generator.engine.ast.ExprStatement;
 import com.google.api.generator.engine.ast.MethodInvocationExpr;
@@ -28,12 +28,23 @@
 import com.google.api.generator.engine.ast.ValueExpr;
 import com.google.api.generator.engine.ast.Variable;
 import com.google.api.generator.engine.ast.VariableExpr;
+import com.google.api.generator.gapic.model.RegionTag;
+import com.google.api.generator.gapic.model.Sample;
+import com.google.api.generator.testutils.LineFormatter;
 import java.util.Arrays;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class SampleCodeWriterTest {
-  @Test
-  public void writeSampleCode_statements() {
+  private static String packageName = "com.google.samples";
+  private static List testingSampleStatements;
+  private static Sample testingSample;
+  private static RegionTag regionTag;
+
+  @BeforeClass
+  public static void setUp() {
     TypeNode settingType =
         TypeNode.withReference(ConcreteReference.withClazz(ClientSettings.class));
     Variable aVar = Variable.builder().setName("clientSettings").setType(settingType).build();
@@ -53,23 +64,95 @@ public void writeSampleCode_statements() {
             .setVariableExpr(aVarExpr.toBuilder().setIsDecl(true).build())
             .setValueExpr(aValueExpr)
             .build();
-    Statement sampleStatement =
+    TryCatchStatement sampleStatement =
         TryCatchStatement.builder()
             .setTryResourceExpr(createAssignmentExpr("aBool", "false", TypeNode.BOOLEAN))
             .setTryBody(
                 Arrays.asList(ExprStatement.withExpr(createAssignmentExpr("x", "3", TypeNode.INT))))
             .setIsSampleCode(true)
             .build();
-    String result = SampleCodeWriter.write(ExprStatement.withExpr(assignmentExpr), sampleStatement);
+
+    testingSampleStatements =
+        Arrays.asList(ExprStatement.withExpr(assignmentExpr), sampleStatement);
+    regionTag =
+        RegionTag.builder()
+            .setApiShortName("testing")
+            .setApiVersion("v1")
+            .setServiceName("samples")
+            .setRpcName("write")
+            .build();
+    testingSample =
+        Sample.builder()
+            .setFileHeader(
+                Arrays.asList(
+                    CommentStatement.withComment(BlockComment.withComment("Apache License"))))
+            .setBody(testingSampleStatements)
+            .setRegionTag(regionTag)
+            .build();
+  }
+
+  @Test
+  public void writeSampleCodeStatements() {
+    String result = SampleCodeWriter.write(testingSampleStatements);
     String expected =
         "ClientSettings clientSettings = ClientSettings.newBuilder().build();\n"
             + "try (boolean aBool = false) {\n"
             + "  int x = 3;\n"
             + "}";
-    assertEquals(expected, result);
+    Assert.assertEquals(expected, result);
+  }
+
+  @Test
+  public void writeInlineSample() {
+    String result = SampleCodeWriter.writeInlineSample(testingSampleStatements);
+    String expected =
+        LineFormatter.lines(
+            "// This snippet has been automatically generated for illustrative purposes only.\n",
+            "// It may require modifications to work in your environment.\n",
+            "ClientSettings clientSettings = ClientSettings.newBuilder().build();\n",
+            "try (boolean aBool = false) {\n",
+            "  int x = 3;\n",
+            "}");
+    Assert.assertEquals(expected, result);
+  }
+
+  @Test
+  public void writeExecutableSample() {
+    Sample sample =
+        testingSample.withRegionTag(regionTag.withOverloadDisambiguation("ExecutableSample"));
+    String result = SampleCodeWriter.writeExecutableSample(sample, packageName);
+    String expected =
+        LineFormatter.lines(
+            "/*\n",
+            " * Apache License\n",
+            " */\n",
+            "\n",
+            "package com.google.samples;\n",
+            "\n",
+            "// [START testing_v1_generated_samples_write_executablesample_sync]\n",
+            "import com.google.api.gax.rpc.ClientSettings;\n",
+            "\n",
+            "public class SyncWriteExecutableSample {\n",
+            "\n",
+            "  public static void main(String[] args) throws Exception {\n",
+            "    syncWriteExecutableSample();\n",
+            "  }\n",
+            "\n",
+            "  public static void syncWriteExecutableSample() throws Exception {\n",
+            "    // This snippet has been automatically generated for illustrative purposes only.\n",
+            "    // It may require modifications to work in your environment.\n",
+            "    ClientSettings clientSettings = ClientSettings.newBuilder().build();\n",
+            "    try (boolean aBool = false) {\n",
+            "      int x = 3;\n",
+            "    }\n",
+            "  }\n",
+            "}\n",
+            "// [END testing_v1_generated_samples_write_executablesample_sync]\n");
+    Assert.assertEquals(expected, result);
   }
 
-  private AssignmentExpr createAssignmentExpr(String varName, String varValue, TypeNode type) {
+  private static AssignmentExpr createAssignmentExpr(
+      String varName, String varValue, TypeNode type) {
     Variable variable = Variable.builder().setName(varName).setType(type).build();
     VariableExpr variableExpr =
         VariableExpr.builder().setVariable(variable).setIsDecl(true).build();
diff --git a/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleComposerTest.java b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleComposerTest.java
new file mode 100644
index 0000000000..72062753b5
--- /dev/null
+++ b/src/test/java/com/google/api/generator/gapic/composer/samplecode/SampleComposerTest.java
@@ -0,0 +1,294 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.google.api.generator.engine.ast.AssignmentExpr;
+import com.google.api.generator.engine.ast.ClassDefinition;
+import com.google.api.generator.engine.ast.Expr;
+import com.google.api.generator.engine.ast.ExprStatement;
+import com.google.api.generator.engine.ast.MethodInvocationExpr;
+import com.google.api.generator.engine.ast.NewObjectExpr;
+import com.google.api.generator.engine.ast.PrimitiveValue;
+import com.google.api.generator.engine.ast.Statement;
+import com.google.api.generator.engine.ast.StringObjectValue;
+import com.google.api.generator.engine.ast.TypeNode;
+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;
+import com.google.api.generator.gapic.model.RegionTag;
+import com.google.api.generator.gapic.model.Sample;
+import com.google.api.generator.testutils.LineFormatter;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class SampleComposerTest {
+  private final String packageName = "com.google.example";
+  private final RegionTag.Builder regionTag =
+      RegionTag.builder().setApiShortName("apiName").setServiceName("echo");
+
+  @Test
+  public void createInlineSample() {
+    List sampleBody = Arrays.asList(ExprStatement.withExpr(systemOutPrint("testing")));
+    String sampleResult = writeSample(SampleComposer.composeInlineSample(sampleBody));
+    String expected =
+        LineFormatter.lines(
+            "// This snippet has been automatically generated for illustrative purposes only.\n",
+            "// It may require modifications to work in your environment.\n",
+            "System.out.println(\"testing\");");
+
+    assertEquals(expected, sampleResult);
+  }
+
+  @Test
+  public void createExecutableSampleEmptyStatementSample() {
+    Sample sample =
+        Sample.builder()
+            .setRegionTag(
+                regionTag
+                    .setRpcName("createExecutableSample")
+                    .setOverloadDisambiguation("EmptyStatementSample")
+                    .build())
+            .build();
+
+    String sampleResult = writeSample(SampleComposer.composeExecutableSample(sample, packageName));
+    String expected =
+        LineFormatter.lines(
+            "package com.google.example;\n",
+            "\n",
+            "// [START apiname_generated_echo_createexecutablesample_emptystatementsample_sync]\n",
+            "public class SyncCreateExecutableSampleEmptyStatementSample {\n",
+            "\n",
+            "  public static void main(String[] args) throws Exception {\n",
+            "    syncCreateExecutableSampleEmptyStatementSample();\n",
+            "  }\n",
+            "\n",
+            "  public static void syncCreateExecutableSampleEmptyStatementSample() throws Exception {\n",
+            "    // This snippet has been automatically generated for illustrative purposes only.\n",
+            "    // It may require modifications to work in your environment.\n",
+            "  }\n",
+            "}\n",
+            "// [END apiname_generated_echo_createexecutablesample_emptystatementsample_sync]\n");
+
+    assertEquals(expected, sampleResult);
+  }
+
+  @Test
+  public void createExecutableSampleMethodArgsNoVar() {
+    Statement sampleBody =
+        ExprStatement.withExpr(systemOutPrint("Testing CreateExecutableSampleMethodArgsNoVar"));
+    Sample sample =
+        Sample.builder()
+            .setBody(ImmutableList.of(sampleBody))
+            .setRegionTag(
+                regionTag
+                    .setRpcName("createExecutableSample")
+                    .setOverloadDisambiguation("MethodArgsNoVar")
+                    .build())
+            .build();
+
+    String sampleResult = writeSample(SampleComposer.composeExecutableSample(sample, packageName));
+    String expected =
+        LineFormatter.lines(
+            "package com.google.example;\n",
+            "\n",
+            "// [START apiname_generated_echo_createexecutablesample_methodargsnovar_sync]\n",
+            "public class SyncCreateExecutableSampleMethodArgsNoVar {\n",
+            "\n",
+            "  public static void main(String[] args) throws Exception {\n",
+            "    syncCreateExecutableSampleMethodArgsNoVar();\n",
+            "  }\n",
+            "\n",
+            "  public static void syncCreateExecutableSampleMethodArgsNoVar() throws Exception {\n",
+            "    // This snippet has been automatically generated for illustrative purposes only.\n",
+            "    // It may require modifications to work in your environment.\n",
+            "    System.out.println(\"Testing CreateExecutableSampleMethodArgsNoVar\");\n",
+            "  }\n",
+            "}\n",
+            "// [END apiname_generated_echo_createexecutablesample_methodargsnovar_sync]\n");
+
+    assertEquals(expected, sampleResult);
+  }
+
+  @Test
+  public void createExecutableSampleMethod() {
+    VariableExpr variableExpr =
+        VariableExpr.builder()
+            .setVariable(Variable.builder().setType(TypeNode.STRING).setName("content").build())
+            .setIsDecl(true)
+            .build();
+    AssignmentExpr varAssignment =
+        AssignmentExpr.builder()
+            .setVariableExpr(variableExpr)
+            .setValueExpr(
+                ValueExpr.withValue(
+                    StringObjectValue.withValue("Testing CreateExecutableSampleMethod")))
+            .build();
+    Statement sampleBody = ExprStatement.withExpr(systemOutPrint(variableExpr));
+    Sample sample =
+        Sample.builder()
+            .setBody(ImmutableList.of(sampleBody))
+            .setVariableAssignments(ImmutableList.of(varAssignment))
+            .setRegionTag(regionTag.setRpcName("createExecutableSample").build())
+            .build();
+
+    String sampleResult = writeSample(SampleComposer.composeExecutableSample(sample, packageName));
+    String expected =
+        LineFormatter.lines(
+            "package com.google.example;\n",
+            "\n",
+            "// [START apiname_generated_echo_createexecutablesample_sync]\n",
+            "public class SyncCreateExecutableSample {\n",
+            "\n",
+            "  public static void main(String[] args) throws Exception {\n",
+            "    String content = \"Testing CreateExecutableSampleMethod\";\n",
+            "    syncCreateExecutableSample(content);\n",
+            "  }\n",
+            "\n",
+            "  public static void syncCreateExecutableSample(String content) throws Exception {\n",
+            "    // This snippet has been automatically generated for illustrative purposes only.\n",
+            "    // It may require modifications to work in your environment.\n",
+            "    System.out.println(content);\n",
+            "  }\n",
+            "}\n",
+            "// [END apiname_generated_echo_createexecutablesample_sync]\n");
+
+    assertEquals(expected, sampleResult);
+  }
+
+  @Test
+  public void createExecutableSampleMethodMultipleStatements() {
+    VariableExpr strVariableExpr =
+        VariableExpr.builder()
+            .setVariable(Variable.builder().setType(TypeNode.STRING).setName("content").build())
+            .setIsDecl(true)
+            .build();
+    VariableExpr intVariableExpr =
+        VariableExpr.builder()
+            .setVariable(Variable.builder().setType(TypeNode.INT).setName("num").build())
+            .setIsDecl(true)
+            .build();
+    VariableExpr objVariableExpr =
+        VariableExpr.builder()
+            .setVariable(Variable.builder().setType(TypeNode.OBJECT).setName("thing").build())
+            .setIsDecl(true)
+            .build();
+    AssignmentExpr strVarAssignment =
+        AssignmentExpr.builder()
+            .setVariableExpr(strVariableExpr)
+            .setValueExpr(
+                ValueExpr.withValue(
+                    StringObjectValue.withValue(
+                        "Testing CreateExecutableSampleMethodMultipleStatements")))
+            .build();
+    AssignmentExpr intVarAssignment =
+        AssignmentExpr.builder()
+            .setVariableExpr(intVariableExpr)
+            .setValueExpr(
+                ValueExpr.withValue(
+                    PrimitiveValue.builder().setType(TypeNode.INT).setValue("2").build()))
+            .build();
+    AssignmentExpr objVarAssignment =
+        AssignmentExpr.builder()
+            .setVariableExpr(objVariableExpr)
+            .setValueExpr(NewObjectExpr.builder().setType(TypeNode.OBJECT).build())
+            .build();
+
+    Statement strBodyStatement = ExprStatement.withExpr(systemOutPrint(strVariableExpr));
+    Statement intBodyStatement = ExprStatement.withExpr(systemOutPrint(intVariableExpr));
+    Statement objBodyStatement =
+        ExprStatement.withExpr(
+            systemOutPrint(
+                MethodInvocationExpr.builder()
+                    .setExprReferenceExpr(objVariableExpr.toBuilder().setIsDecl(false).build())
+                    .setMethodName("response")
+                    .build()));
+    Sample sample =
+        Sample.builder()
+            .setBody(ImmutableList.of(strBodyStatement, intBodyStatement, objBodyStatement))
+            .setVariableAssignments(
+                ImmutableList.of(strVarAssignment, intVarAssignment, objVarAssignment))
+            .setRegionTag(
+                regionTag
+                    .setRpcName("createExecutableSample")
+                    .setOverloadDisambiguation("MethodMultipleStatements")
+                    .build())
+            .build();
+
+    String sampleResult = writeSample(SampleComposer.composeExecutableSample(sample, packageName));
+    String expected =
+        LineFormatter.lines(
+            "package com.google.example;\n",
+            "\n",
+            "// [START apiname_generated_echo_createexecutablesample_methodmultiplestatements_sync]\n",
+            "public class SyncCreateExecutableSampleMethodMultipleStatements {\n",
+            "\n",
+            "  public static void main(String[] args) throws Exception {\n",
+            "    String content = \"Testing CreateExecutableSampleMethodMultipleStatements\";\n",
+            "    int num = 2;\n",
+            "    Object thing = new Object();\n",
+            "    syncCreateExecutableSampleMethodMultipleStatements(content, num, thing);\n",
+            "  }\n",
+            "\n",
+            "  public static void syncCreateExecutableSampleMethodMultipleStatements(\n",
+            "      String content, int num, Object thing) throws Exception {\n",
+            "    // This snippet has been automatically generated for illustrative purposes only.\n",
+            "    // It may require modifications to work in your environment.\n",
+            "    System.out.println(content);\n",
+            "    System.out.println(num);\n",
+            "    System.out.println(thing.response());\n",
+            "  }\n",
+            "}\n",
+            "// [END apiname_generated_echo_createexecutablesample_methodmultiplestatements_sync]\n");
+    assertEquals(expected, sampleResult);
+  }
+
+  private Expr systemOutPrint(MethodInvocationExpr response) {
+    return composeSystemOutPrint(response);
+  }
+
+  private static MethodInvocationExpr systemOutPrint(String content) {
+    return composeSystemOutPrint(ValueExpr.withValue(StringObjectValue.withValue(content)));
+  }
+
+  private static MethodInvocationExpr systemOutPrint(VariableExpr variableExpr) {
+    return composeSystemOutPrint(variableExpr.toBuilder().setIsDecl(false).build());
+  }
+
+  private static MethodInvocationExpr composeSystemOutPrint(Expr content) {
+    VaporReference out =
+        VaporReference.builder()
+            .setEnclosingClassNames("System")
+            .setName("out")
+            .setPakkage("java.lang")
+            .build();
+    return MethodInvocationExpr.builder()
+        .setStaticReferenceType(TypeNode.withReference(out))
+        .setMethodName("println")
+        .setArguments(content)
+        .build();
+  }
+
+  private static String writeSample(ClassDefinition sample) {
+    return SampleCodeWriter.write(sample);
+  }
+
+  private static String writeSample(List sample) {
+    return SampleCodeWriter.write(sample);
+  }
+}
diff --git a/src/test/java/com/google/api/generator/gapic/model/RegionTagTest.java b/src/test/java/com/google/api/generator/gapic/model/RegionTagTest.java
new file mode 100644
index 0000000000..a45d206de9
--- /dev/null
+++ b/src/test/java/com/google/api/generator/gapic/model/RegionTagTest.java
@@ -0,0 +1,150 @@
+// 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.model;
+
+import com.google.api.generator.gapic.composer.samplecode.SampleCodeWriter;
+import com.google.api.generator.testutils.LineFormatter;
+import java.util.Arrays;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RegionTagTest {
+  private final String serviceName = "serviceName";
+  private final String apiVersion = "v1";
+  private final String apiShortName = "shortName";
+  private final String rpcName = "rpcName";
+  private final String disambiguation = "disambiguation";
+
+  @Test
+  public void regionTagNoRpcName() {
+    Assert.assertThrows(
+        IllegalStateException.class,
+        () ->
+            RegionTag.builder()
+                .setApiVersion(apiVersion)
+                .setApiShortName(apiShortName)
+                .setServiceName(serviceName)
+                .setOverloadDisambiguation(disambiguation)
+                .setIsAsynchronous(true)
+                .build());
+  }
+
+  @Test
+  public void regionTagNoServiceName() {
+    Assert.assertThrows(
+        IllegalStateException.class,
+        () ->
+            RegionTag.builder()
+                .setApiVersion(apiVersion)
+                .setApiShortName(apiShortName)
+                .setRpcName(rpcName)
+                .setOverloadDisambiguation(disambiguation)
+                .setIsAsynchronous(true)
+                .build());
+  }
+
+  @Test
+  public void regionTagValidMissingFields() {
+    RegionTag regionTag =
+        RegionTag.builder().setServiceName(serviceName).setRpcName(rpcName).build();
+
+    Assert.assertEquals("", regionTag.apiShortName());
+    Assert.assertEquals("", regionTag.apiVersion());
+    Assert.assertEquals("", regionTag.overloadDisambiguation());
+    Assert.assertEquals(false, regionTag.isAsynchronous());
+  }
+
+  @Test
+  public void regionTagSanitizeAttributes() {
+    String apiVersion = "1.4.0-";
+    String serviceName = "service_Na@m*.e!{}";
+    String rpcName = "rpc _Nam^#,e   [String]10";
+    RegionTag regionTag =
+        RegionTag.builder()
+            .setApiVersion(apiVersion)
+            .setServiceName(serviceName)
+            .setRpcName(rpcName)
+            .build();
+
+    Assert.assertEquals("140Version", regionTag.apiVersion());
+    Assert.assertEquals("ServiceNameString", regionTag.serviceName());
+    Assert.assertEquals("RpcNameString10", regionTag.rpcName());
+  }
+
+  @Test
+  public void generateRegionTagsMissingRequiredFields() {
+    RegionTag rtMissingShortName =
+        RegionTag.builder()
+            .setApiVersion(apiVersion)
+            .setServiceName(serviceName)
+            .setRpcName(rpcName)
+            .build();
+    Assert.assertThrows(IllegalStateException.class, () -> rtMissingShortName.generate());
+  }
+
+  @Test
+  public void generateRegionTagsValidMissingFields() {
+    RegionTag regionTag =
+        RegionTag.builder()
+            .setApiShortName(apiShortName)
+            .setServiceName(serviceName)
+            .setRpcName(rpcName)
+            .build();
+
+    String result = regionTag.generate();
+    String expected = "shortname_generated_servicename_rpcname_sync";
+    Assert.assertEquals(expected, result);
+  }
+
+  @Test
+  public void generateRegionTagsAllFields() {
+    RegionTag regionTag =
+        RegionTag.builder()
+            .setApiShortName(apiShortName)
+            .setApiVersion(apiVersion)
+            .setServiceName(serviceName)
+            .setRpcName(rpcName)
+            .setOverloadDisambiguation(disambiguation)
+            .setIsAsynchronous(true)
+            .build();
+
+    String result = regionTag.generate();
+    String expected = "shortname_v1_generated_servicename_rpcname_disambiguation_async";
+    Assert.assertEquals(expected, result);
+  }
+
+  @Test
+  public void generateRegionTagTag() {
+    RegionTag regionTag =
+        RegionTag.builder()
+            .setApiShortName(apiShortName)
+            .setApiVersion(apiVersion)
+            .setServiceName(serviceName)
+            .setRpcName(rpcName)
+            .setOverloadDisambiguation(disambiguation)
+            .build();
+
+    String result =
+        SampleCodeWriter.write(
+            Arrays.asList(
+                regionTag.generateTag(RegionTag.RegionTagRegion.START, regionTag.generate()),
+                regionTag.generateTag(RegionTag.RegionTagRegion.END, regionTag.generate())));
+    String expected =
+        LineFormatter.lines(
+            "// [START shortname_v1_generated_servicename_rpcname_disambiguation_sync]\n",
+            "// [END shortname_v1_generated_servicename_rpcname_disambiguation_sync]");
+    Assert.assertEquals(expected, result);
+  }
+}
diff --git a/src/test/java/com/google/api/generator/gapic/model/SampleTest.java b/src/test/java/com/google/api/generator/gapic/model/SampleTest.java
new file mode 100644
index 0000000000..566c4f37d6
--- /dev/null
+++ b/src/test/java/com/google/api/generator/gapic/model/SampleTest.java
@@ -0,0 +1,73 @@
+// 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.model;
+
+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 com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SampleTest {
+  private final RegionTag regionTag =
+      RegionTag.builder().setServiceName("serviceName").setRpcName("rpcName").build();
+  private final List sampleBody =
+      Arrays.asList(CommentStatement.withComment(LineComment.withComment("testing")));
+  private final List header =
+      Arrays.asList(CommentStatement.withComment(BlockComment.withComment("apache license")));
+
+  @Test
+  public void sampleNoRegionTag() {
+    Assert.assertThrows(
+        IllegalStateException.class,
+        () -> Sample.builder().setBody(sampleBody).setFileHeader(header).build());
+  }
+
+  @Test
+  public void sampleValidMissingFields() {
+    Sample sample = Sample.builder().setRegionTag(regionTag).build();
+
+    Assert.assertEquals(ImmutableList.of(), sample.fileHeader());
+    Assert.assertEquals(ImmutableList.of(), sample.body());
+    Assert.assertEquals(ImmutableList.of(), sample.variableAssignments());
+  }
+
+  @Test
+  public void sampleWithHeader() {
+    Sample sample = Sample.builder().setRegionTag(regionTag).setBody(sampleBody).build();
+    Assert.assertEquals(ImmutableList.of(), sample.fileHeader());
+
+    sample = sample.withHeader(header);
+    Assert.assertEquals(header, sample.fileHeader());
+  }
+
+  @Test
+  public void sampleNameWithRegionTag() {
+    Sample sample = Sample.builder().setRegionTag(regionTag).build();
+    Assert.assertEquals("SyncRpcName", sample.name());
+
+    RegionTag rt = regionTag.toBuilder().setOverloadDisambiguation("disambiguation").build();
+    sample = sample.withRegionTag(rt);
+    Assert.assertEquals("SyncRpcNameDisambiguation", sample.name());
+
+    rt = rt.toBuilder().setIsAsynchronous(true).build();
+    sample = sample.withRegionTag(rt);
+    Assert.assertEquals("AsyncRpcNameDisambiguation", sample.name());
+  }
+}