| /* |
| * Copyright (C) 2016 The Dagger Authors. |
| * |
| * 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 dagger.grpc.server.processor; |
| |
| import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; |
| import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; |
| import static com.google.auto.common.MoreElements.getAnnotationMirror; |
| import static com.google.common.base.CaseFormat.LOWER_CAMEL; |
| import static com.google.common.base.CaseFormat.UPPER_CAMEL; |
| |
| import com.google.auto.common.MoreTypes; |
| import com.google.common.base.Joiner; |
| import com.squareup.javapoet.AnnotationSpec; |
| import com.squareup.javapoet.ClassName; |
| import dagger.grpc.server.ForGrpcService; |
| import dagger.grpc.server.GrpcService; |
| import dagger.grpc.server.processor.SourceGenerator.IoGrpc; |
| import java.util.Optional; |
| import javax.annotation.processing.Messager; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.AnnotationValue; |
| import javax.lang.model.element.AnnotationValueVisitor; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.SimpleAnnotationValueVisitor7; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic.Kind; |
| |
| class GrpcServiceModel { |
| |
| private static final String GRPC_SERVICE_PARAMETER_NAME = "grpcClass"; |
| |
| private final Types types; |
| private final Elements elements; |
| private final SourceVersion sourceVersion; |
| private final Messager messager; |
| final TypeElement serviceImplementation; |
| final ClassName serviceImplementationClassName; |
| final ClassName serviceDefinitionTypeName; |
| final ClassName proxyModuleName; |
| final ClassName serviceDefinitionTypeFactoryName; |
| final ClassName serviceModuleName; |
| final ClassName unscopedServiceModuleName; |
| |
| GrpcServiceModel(ProcessingEnvironment processingEnv, TypeElement serviceImplementation) { |
| this.types = processingEnv.getTypeUtils(); |
| this.elements = processingEnv.getElementUtils(); |
| this.sourceVersion = processingEnv.getSourceVersion(); |
| this.messager = processingEnv.getMessager(); |
| this.serviceImplementation = serviceImplementation; |
| this.serviceImplementationClassName = ClassName.get(serviceImplementation); |
| this.serviceDefinitionTypeName = peerClassWithSuffix("ServiceDefinition"); |
| this.serviceDefinitionTypeFactoryName = serviceDefinitionTypeName.nestedClass("Factory"); |
| this.proxyModuleName = peerClassWithSuffix("GrpcProxyModule"); |
| this.serviceModuleName = peerClassWithSuffix("GrpcServiceModule"); |
| this.unscopedServiceModuleName = peerClassWithSuffix("UnscopedGrpcServiceModule"); |
| } |
| |
| /** |
| * Returns the name of a top-level class in the same package as the service implementation |
| * class, whose name is the simple name of the service implementation class and its enclosing |
| * classes, joined with underscores, and appended with {@code suffix}. |
| */ |
| private ClassName peerClassWithSuffix(String suffix) { |
| return serviceImplementationClassName.peerClass( |
| Joiner.on('_').join(serviceImplementationClassName.simpleNames()) + suffix); |
| } |
| |
| String packageName() { |
| return serviceImplementationClassName.packageName(); |
| } |
| |
| public boolean validate() { |
| AnnotationValue argument = |
| getAnnotationValue(grpcServiceAnnotation(), GRPC_SERVICE_PARAMETER_NAME); |
| return argument.accept( |
| new SimpleAnnotationValueVisitor7<Boolean, AnnotationValue>(false) { |
| @Override |
| public Boolean visitType(TypeMirror type, AnnotationValue value) { |
| return validateGrpcClass(type, value); |
| } |
| }, |
| argument); |
| } |
| |
| private AnnotationMirror grpcServiceAnnotation() { |
| return getAnnotationMirror(serviceImplementation, GrpcService.class).get(); |
| } |
| |
| /** Returns the gRPC service class declared by {@link GrpcService#grpcClass()}. */ |
| protected final TypeElement grpcClass() { |
| AnnotationValue argument = |
| getAnnotationValue(grpcServiceAnnotation(), GRPC_SERVICE_PARAMETER_NAME); |
| return GET_TYPE_ELEMENT_FROM_VALUE.visit(argument, argument); |
| } |
| |
| /** |
| * Returns the annotation spec for the {@code @Generated} annotation to add to any |
| * type generated by this processor. |
| */ |
| protected final Optional<AnnotationSpec> generatedAnnotation() { |
| return generatedAnnotationSpec( |
| elements, |
| sourceVersion, |
| GrpcService.class, |
| String.format( |
| "@%s annotation on %s", |
| GrpcService.class.getCanonicalName(), serviceImplementationClassName)); |
| } |
| |
| /** |
| * Returns the annotation spec for a {@link ForGrpcService} annotation whose value is the |
| * gRPC-generated service class. |
| */ |
| protected final AnnotationSpec forGrpcService() { |
| return AnnotationSpec.builder(ForGrpcService.class) |
| .addMember("value", "$T.class", grpcClass()) |
| .build(); |
| } |
| |
| protected final String subcomponentServiceDefinitionMethodName() { |
| return UPPER_CAMEL.to(LOWER_CAMEL, simpleServiceName()) + "ServiceDefinition"; |
| } |
| |
| private String simpleServiceName() { |
| return grpcClass().getSimpleName().toString().replaceFirst("Grpc$", ""); |
| } |
| |
| private TypeElement serviceImplBase(TypeMirror service) { |
| ClassName serviceClassName = ClassName.get(MoreTypes.asTypeElement(service)); |
| ClassName serviceImplBaseName = serviceClassName.nestedClass(simpleServiceName() + "ImplBase"); |
| return elements.getTypeElement(serviceImplBaseName.toString()); |
| } |
| |
| private boolean validateGrpcClass(TypeMirror type, AnnotationValue value) { |
| TypeElement serviceImplBase = serviceImplBase(type); |
| if (serviceImplBase == null || !types.isSubtype(serviceImplBase.asType(), bindableService())) { |
| messager.printMessage( |
| Kind.ERROR, |
| String.format("%s is not a gRPC service class", type), |
| serviceImplementation, |
| grpcServiceAnnotation(), |
| value); |
| return false; |
| } |
| if (!(types.isSubtype(serviceImplementation.asType(), serviceImplBase.asType()))) { |
| messager.printMessage( |
| Kind.ERROR, |
| String.format( |
| "%s must extend %s", serviceImplementation, serviceImplBase.getQualifiedName()), |
| serviceImplementation, |
| grpcServiceAnnotation(), |
| value); |
| return false; |
| } |
| return true; |
| } |
| |
| private TypeMirror bindableService() { |
| return elements.getTypeElement(IoGrpc.BINDABLE_SERVICE.toString()).asType(); |
| } |
| |
| static final AnnotationValueVisitor<TypeElement, AnnotationValue> GET_TYPE_ELEMENT_FROM_VALUE = |
| new SimpleAnnotationValueVisitor7<TypeElement, AnnotationValue>() { |
| @Override |
| public TypeElement visitType(TypeMirror t, AnnotationValue p) { |
| return MoreTypes.asTypeElement(t); |
| } |
| |
| @Override |
| protected TypeElement defaultAction(Object o, AnnotationValue p) { |
| throw new IllegalArgumentException("Expected " + p + " to be a class"); |
| } |
| }; |
| } |