• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.grpc.server.processor;
18 
19 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
20 import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
21 import static com.google.auto.common.MoreElements.getAnnotationMirror;
22 import static com.google.common.base.CaseFormat.LOWER_CAMEL;
23 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
24 
25 import com.google.auto.common.MoreTypes;
26 import com.google.common.base.Joiner;
27 import com.squareup.javapoet.AnnotationSpec;
28 import com.squareup.javapoet.ClassName;
29 import dagger.grpc.server.ForGrpcService;
30 import dagger.grpc.server.GrpcService;
31 import dagger.grpc.server.processor.SourceGenerator.IoGrpc;
32 import java.util.Optional;
33 import javax.annotation.processing.Messager;
34 import javax.annotation.processing.ProcessingEnvironment;
35 import javax.lang.model.SourceVersion;
36 import javax.lang.model.element.AnnotationMirror;
37 import javax.lang.model.element.AnnotationValue;
38 import javax.lang.model.element.AnnotationValueVisitor;
39 import javax.lang.model.element.TypeElement;
40 import javax.lang.model.type.TypeMirror;
41 import javax.lang.model.util.Elements;
42 import javax.lang.model.util.SimpleAnnotationValueVisitor7;
43 import javax.lang.model.util.Types;
44 import javax.tools.Diagnostic.Kind;
45 
46 class GrpcServiceModel {
47 
48   private static final String GRPC_SERVICE_PARAMETER_NAME = "grpcClass";
49 
50   private final Types types;
51   private final Elements elements;
52   private final SourceVersion sourceVersion;
53   private final Messager messager;
54   final TypeElement serviceImplementation;
55   final ClassName serviceImplementationClassName;
56   final ClassName serviceDefinitionTypeName;
57   final ClassName proxyModuleName;
58   final ClassName serviceDefinitionTypeFactoryName;
59   final ClassName serviceModuleName;
60   final ClassName unscopedServiceModuleName;
61 
GrpcServiceModel(ProcessingEnvironment processingEnv, TypeElement serviceImplementation)62   GrpcServiceModel(ProcessingEnvironment processingEnv, TypeElement serviceImplementation) {
63     this.types = processingEnv.getTypeUtils();
64     this.elements = processingEnv.getElementUtils();
65     this.sourceVersion = processingEnv.getSourceVersion();
66     this.messager = processingEnv.getMessager();
67     this.serviceImplementation = serviceImplementation;
68     this.serviceImplementationClassName = ClassName.get(serviceImplementation);
69     this.serviceDefinitionTypeName = peerClassWithSuffix("ServiceDefinition");
70     this.serviceDefinitionTypeFactoryName = serviceDefinitionTypeName.nestedClass("Factory");
71     this.proxyModuleName = peerClassWithSuffix("GrpcProxyModule");
72     this.serviceModuleName = peerClassWithSuffix("GrpcServiceModule");
73     this.unscopedServiceModuleName = peerClassWithSuffix("UnscopedGrpcServiceModule");
74   }
75 
76   /**
77    * Returns the name of a top-level class in the same package as the service implementation
78    * class, whose name is the simple name of the service implementation class and its enclosing
79    * classes, joined with underscores, and appended with {@code suffix}.
80    */
peerClassWithSuffix(String suffix)81   private ClassName peerClassWithSuffix(String suffix) {
82     return serviceImplementationClassName.peerClass(
83         Joiner.on('_').join(serviceImplementationClassName.simpleNames()) + suffix);
84   }
85 
packageName()86   String packageName() {
87     return serviceImplementationClassName.packageName();
88   }
89 
validate()90   public boolean validate() {
91     AnnotationValue argument =
92         getAnnotationValue(grpcServiceAnnotation(), GRPC_SERVICE_PARAMETER_NAME);
93     return argument.accept(
94         new SimpleAnnotationValueVisitor7<Boolean, AnnotationValue>(false) {
95           @Override
96           public Boolean visitType(TypeMirror type, AnnotationValue value) {
97             return validateGrpcClass(type, value);
98           }
99         },
100         argument);
101   }
102 
103   private AnnotationMirror grpcServiceAnnotation() {
104     return getAnnotationMirror(serviceImplementation, GrpcService.class).get();
105   }
106 
107   /** Returns the gRPC service class declared by {@link GrpcService#grpcClass()}. */
108   protected final TypeElement grpcClass() {
109     AnnotationValue argument =
110         getAnnotationValue(grpcServiceAnnotation(), GRPC_SERVICE_PARAMETER_NAME);
111     return GET_TYPE_ELEMENT_FROM_VALUE.visit(argument, argument);
112   }
113 
114   /**
115    * Returns the annotation spec for the {@code @Generated} annotation to add to any
116    * type generated by this processor.
117    */
118   protected final Optional<AnnotationSpec> generatedAnnotation() {
119     return generatedAnnotationSpec(
120         elements,
121         sourceVersion,
122         GrpcService.class,
123         String.format(
124             "@%s annotation on %s",
125             GrpcService.class.getCanonicalName(), serviceImplementationClassName));
126   }
127 
128   /**
129    * Returns the annotation spec for a {@link ForGrpcService} annotation whose value is the
130    * gRPC-generated service class.
131    */
132   protected final AnnotationSpec forGrpcService() {
133     return AnnotationSpec.builder(ForGrpcService.class)
134         .addMember("value", "$T.class", grpcClass())
135         .build();
136   }
137 
138   protected final String subcomponentServiceDefinitionMethodName() {
139     return UPPER_CAMEL.to(LOWER_CAMEL, simpleServiceName()) + "ServiceDefinition";
140   }
141 
142   private String simpleServiceName() {
143     return grpcClass().getSimpleName().toString().replaceFirst("Grpc$", "");
144   }
145 
146   private TypeElement serviceImplBase(TypeMirror service) {
147     ClassName serviceClassName = ClassName.get(MoreTypes.asTypeElement(service));
148     ClassName serviceImplBaseName = serviceClassName.nestedClass(simpleServiceName() + "ImplBase");
149     return elements.getTypeElement(serviceImplBaseName.toString());
150   }
151 
152   private boolean validateGrpcClass(TypeMirror type, AnnotationValue value) {
153     TypeElement serviceImplBase = serviceImplBase(type);
154     if (serviceImplBase == null || !types.isSubtype(serviceImplBase.asType(), bindableService())) {
155       messager.printMessage(
156           Kind.ERROR,
157           String.format("%s is not a gRPC service class", type),
158           serviceImplementation,
159           grpcServiceAnnotation(),
160           value);
161       return false;
162     }
163     if (!(types.isSubtype(serviceImplementation.asType(), serviceImplBase.asType()))) {
164       messager.printMessage(
165           Kind.ERROR,
166           String.format(
167               "%s must extend %s", serviceImplementation, serviceImplBase.getQualifiedName()),
168           serviceImplementation,
169           grpcServiceAnnotation(),
170           value);
171       return false;
172     }
173     return true;
174   }
175 
176   private TypeMirror bindableService() {
177     return elements.getTypeElement(IoGrpc.BINDABLE_SERVICE.toString()).asType();
178   }
179 
180   static final AnnotationValueVisitor<TypeElement, AnnotationValue> GET_TYPE_ELEMENT_FROM_VALUE =
181       new SimpleAnnotationValueVisitor7<TypeElement, AnnotationValue>() {
182         @Override
183         public TypeElement visitType(TypeMirror t, AnnotationValue p) {
184           return MoreTypes.asTypeElement(t);
185         }
186 
187         @Override
188         protected TypeElement defaultAction(Object o, AnnotationValue p) {
189           throw new IllegalArgumentException("Expected " + p + " to be a class");
190         }
191       };
192 }
193