/* * 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(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 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 GET_TYPE_ELEMENT_FROM_VALUE = new SimpleAnnotationValueVisitor7() { @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"); } }; }