1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.api.generator.engine.ast; 16 17 import com.google.api.generator.gapic.model.RegionTag; 18 import com.google.auto.value.AutoValue; 19 import com.google.common.base.Preconditions; 20 import com.google.common.collect.ImmutableList; 21 import java.util.Arrays; 22 import java.util.Collections; 23 import java.util.List; 24 import javax.annotation.Nullable; 25 26 @AutoValue 27 public abstract class ClassDefinition implements AstNode { 28 // Optional. fileHeader()29 public abstract ImmutableList<CommentStatement> fileHeader(); 30 // Required for samples classes. 31 @Nullable regionTag()32 public abstract RegionTag regionTag(); 33 // Required. scope()34 public abstract ScopeNode scope(); 35 // Required. classIdentifier()36 public abstract IdentifierNode classIdentifier(); 37 // Required for outer classes. 38 @Nullable packageString()39 public abstract String packageString(); 40 isNested()41 public abstract boolean isNested(); 42 43 // Optional. headerCommentStatements()44 public abstract ImmutableList<CommentStatement> headerCommentStatements(); 45 annotations()46 public abstract ImmutableList<AnnotationNode> annotations(); 47 48 // Using a list helps with determinism in unit tests. implementsTypes()49 public abstract ImmutableList<TypeNode> implementsTypes(); 50 51 @Nullable extendsType()52 public abstract TypeNode extendsType(); 53 isStatic()54 public abstract boolean isStatic(); 55 isFinal()56 public abstract boolean isFinal(); 57 isAbstract()58 public abstract boolean isAbstract(); 59 statements()60 public abstract ImmutableList<Statement> statements(); 61 methods()62 public abstract ImmutableList<MethodDefinition> methods(); 63 nestedClasses()64 public abstract ImmutableList<ClassDefinition> nestedClasses(); 65 66 // Private. name()67 abstract String name(); 68 69 @Override accept(AstNodeVisitor visitor)70 public void accept(AstNodeVisitor visitor) { 71 visitor.visit(this); 72 } 73 builder()74 public static Builder builder() { 75 return new AutoValue_ClassDefinition.Builder() 76 .setFileHeader(Collections.emptyList()) 77 .setHeaderCommentStatements(Collections.emptyList()) 78 .setIsNested(false) 79 .setIsFinal(false) 80 .setIsStatic(false) 81 .setIsAbstract(false) 82 .setAnnotations(Collections.emptyList()) 83 .setImplementsTypes(Collections.emptyList()) 84 .setStatements(Collections.emptyList()) 85 .setMethods(Collections.emptyList()) 86 .setNestedClasses(Collections.emptyList()); 87 } 88 toBuilder()89 public abstract Builder toBuilder(); 90 91 @AutoValue.Builder 92 public abstract static class Builder { setFileHeader(CommentStatement... headerComments)93 public Builder setFileHeader(CommentStatement... headerComments) { 94 return setFileHeader(Arrays.asList(headerComments)); 95 } 96 setFileHeader(List<CommentStatement> fileHeader)97 public abstract Builder setFileHeader(List<CommentStatement> fileHeader); 98 setRegionTag(RegionTag regionTag)99 public abstract Builder setRegionTag(RegionTag regionTag); 100 setHeaderCommentStatements(CommentStatement... comments)101 public Builder setHeaderCommentStatements(CommentStatement... comments) { 102 return setHeaderCommentStatements(Arrays.asList(comments)); 103 } 104 setHeaderCommentStatements( List<CommentStatement> headerCommentStatements)105 public abstract Builder setHeaderCommentStatements( 106 List<CommentStatement> headerCommentStatements); 107 setScope(ScopeNode scope)108 public abstract Builder setScope(ScopeNode scope); 109 setPackageString(String pkg)110 public abstract Builder setPackageString(String pkg); 111 setName(String name)112 public abstract Builder setName(String name); 113 setIsNested(boolean isNested)114 public abstract Builder setIsNested(boolean isNested); 115 setAnnotations(List<AnnotationNode> annotations)116 public abstract Builder setAnnotations(List<AnnotationNode> annotations); 117 setIsAbstract(boolean isAbstract)118 public abstract Builder setIsAbstract(boolean isAbstract); 119 setIsStatic(boolean isStatic)120 public abstract Builder setIsStatic(boolean isStatic); 121 setIsFinal(boolean isFinal)122 public abstract Builder setIsFinal(boolean isFinal); 123 setExtendsType(TypeNode type)124 public abstract Builder setExtendsType(TypeNode type); 125 setImplementsTypes(List<TypeNode> types)126 public abstract Builder setImplementsTypes(List<TypeNode> types); 127 setStatements(List<Statement> body)128 public abstract Builder setStatements(List<Statement> body); 129 setMethods(List<MethodDefinition> methods)130 public abstract Builder setMethods(List<MethodDefinition> methods); 131 setNestedClasses(List<ClassDefinition> nestedClasses)132 public abstract Builder setNestedClasses(List<ClassDefinition> nestedClasses); 133 134 // Private accessors. name()135 abstract String name(); 136 autoBuild()137 abstract ClassDefinition autoBuild(); 138 setClassIdentifier(IdentifierNode methodIdentifier)139 abstract Builder setClassIdentifier(IdentifierNode methodIdentifier); 140 build()141 public ClassDefinition build() { 142 IdentifierNode classIdentifier = IdentifierNode.builder().setName(name()).build(); 143 setClassIdentifier(classIdentifier); 144 145 ClassDefinition classDef = autoBuild(); 146 performNullChecks(classDef); 147 148 // Only nested classes can forego having a package. 149 if (!classDef.isNested()) { 150 Preconditions.checkNotNull( 151 classDef.packageString(), "Outer classes must have a package name defined"); 152 Preconditions.checkState(!classDef.isStatic(), "Outer classes cannot be static"); 153 Preconditions.checkState( 154 !classDef.scope().equals(ScopeNode.PRIVATE), "Outer classes cannot be private"); 155 } else { 156 Preconditions.checkState( 157 classDef.fileHeader().isEmpty(), "Nested classes cannot have a file header"); 158 } 159 160 // Abstract classes cannot be marked final. 161 if (classDef.isAbstract()) { 162 Preconditions.checkState(!classDef.isFinal(), "Abstract classes cannot be marked final"); 163 } 164 165 // Check abstract extended type. 166 if (classDef.extendsType() != null) { 167 Preconditions.checkState( 168 TypeNode.isReferenceType(classDef.extendsType()), 169 "Classes cannot extend non-reference types"); 170 Preconditions.checkState( 171 !classDef.implementsTypes().contains(classDef.extendsType()), 172 "Classes cannot extend and implement the same type"); 173 } 174 175 // Check implemented interface types. 176 for (TypeNode implType : classDef.implementsTypes()) { 177 Preconditions.checkState( 178 TypeNode.isReferenceType(implType), "Classes cannot implement non-reference types"); 179 } 180 181 for (Statement statement : classDef.statements()) { 182 Preconditions.checkState( 183 statement instanceof CommentStatement 184 || statement instanceof EmptyLineStatement 185 || statement instanceof ExprStatement 186 || statement instanceof BlockStatement, 187 "Class statement type must be either an expression, block, or comment statement"); 188 if (statement instanceof ExprStatement) { 189 Expr expr = ((ExprStatement) statement).expression(); 190 if (expr instanceof VariableExpr) { 191 VariableExpr variableExpr = (VariableExpr) expr; 192 Preconditions.checkState( 193 variableExpr.isDecl(), "Class expression variable statements must be declarations"); 194 Preconditions.checkState( 195 !variableExpr.scope().equals(ScopeNode.LOCAL), 196 "Class variable statement cannot have a local scope"); 197 } else { 198 Preconditions.checkState( 199 expr instanceof AssignmentExpr, 200 "Class expression statement must be assignment or variable declaration"); 201 VariableExpr variableExpr = ((AssignmentExpr) expr).variableExpr(); 202 Preconditions.checkState( 203 !variableExpr.scope().equals(ScopeNode.LOCAL), 204 "Class variable in assignment statement cannot have a local scope"); 205 } 206 } 207 } 208 209 return classDef; 210 } 211 performNullChecks(ClassDefinition classDef)212 void performNullChecks(ClassDefinition classDef) { 213 String contextInfo = String.format("class definition of %s", name()); 214 NodeValidator.checkNoNullElements( 215 classDef.headerCommentStatements(), "header comments", contextInfo); 216 NodeValidator.checkNoNullElements(classDef.annotations(), "annotations", contextInfo); 217 218 NodeValidator.checkNoNullElements( 219 classDef.implementsTypes(), "implemented types", contextInfo); 220 NodeValidator.checkNoNullElements(classDef.statements(), "statements", contextInfo); 221 222 NodeValidator.checkNoNullElements(classDef.methods(), "methods", contextInfo); 223 NodeValidator.checkNoNullElements(classDef.nestedClasses(), "nested classes", contextInfo); 224 } 225 } 226 } 227