/* * -\-\- * DataEnum * -- * Copyright (c) 2017 Spotify AB * -- * 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.spotify.dataenum.processor.parser; import com.spotify.dataenum.dataenum_case; import com.spotify.dataenum.function.Function; import com.spotify.dataenum.processor.data.Parameter; import com.spotify.dataenum.processor.data.Value; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.AnnotationSpec.Builder; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; final class ValueParser { private ValueParser() {} static Value parse(Element element, ProcessingEnvironment processingEnv) { Messager messager = processingEnv.getMessager(); if (element.getKind() != ElementKind.METHOD) { messager.printMessage( Diagnostic.Kind.ERROR, String.format( "Value specs must be methods, found %s: %s", element.getKind().toString().toLowerCase(), element), element); return null; } ExecutableElement methodElement = (ExecutableElement) element; if (!isValueSpecMarker(methodElement.getReturnType(), processingEnv)) { messager.printMessage( Diagnostic.Kind.ERROR, String.format( "Value specs must return dataenum_case, found %s: %s", methodElement.getReturnType(), element), element); return null; } if (methodElement.getTypeParameters().size() != 0) { messager.printMessage( Diagnostic.Kind.ERROR, String.format( "Type parameters must be specified on the top-level interface, found: %s", element), element); return null; } if (methodElement.isVarArgs()) { messager.printMessage( Diagnostic.Kind.ERROR, String.format("Vararg parameters not permitted: %s", element), element); return null; } List<Parameter> parameters = new ArrayList<>(); for (VariableElement parameterElement : methodElement.getParameters()) { String parameterName = parameterElement.getSimpleName().toString(); TypeName parameterType = TypeName.get(parameterElement.asType()); boolean nullable = isAnnotationPresent(parameterElement, ValueParser::isNullableAnnotation); boolean redacted = isAnnotationPresent(parameterElement, ValueParser::isRedactedAnnotation); Element parameterTypeElement = processingEnv.getTypeUtils().asElement(parameterElement.asType()); boolean isEnum = parameterTypeElement != null && parameterTypeElement.getKind() == ElementKind.ENUM; parameters.add(new Parameter(parameterName, parameterType, nullable, redacted, isEnum)); } String javadoc = processingEnv.getElementUtils().getDocComment(element); if (javadoc != null) { javadoc = javadoc.trim(); } String valueSimpleName = methodElement.getSimpleName().toString(); return new Value( valueSimpleName, javadoc, parameters, parseMethodAnnotations(methodElement, messager)); } private static Iterable<AnnotationSpec> parseMethodAnnotations( ExecutableElement methodElement, Messager messager) { ArrayList<AnnotationSpec> annotations = new ArrayList<>(); for (AnnotationMirror annotationMirror : methodElement.getAnnotationMirrors()) { TypeName annotationTypeName = ClassName.get(annotationMirror.getAnnotationType().asElement().asType()); if (!(annotationTypeName instanceof ClassName)) { messager.printMessage( Kind.ERROR, "Annotation is not a class; this shouldn't happen", methodElement, annotationMirror); continue; } Builder builder = AnnotationSpec.builder(((ClassName) annotationTypeName)); for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) { builder.addMember(entry.getKey().getSimpleName().toString(), entry.getValue().toString()); } annotations.add(builder.build()); } return annotations; } private static boolean isAnnotationPresent( VariableElement parameterElement, Function<AnnotationMirror, Boolean> criterion) { for (AnnotationMirror annotation : parameterElement.getAnnotationMirrors()) { if (criterion.apply(annotation)) { return true; } } return false; } private static boolean isValueSpecMarker( TypeMirror returnType, ProcessingEnvironment processingEnvironment) { Types types = processingEnvironment.getTypeUtils(); Elements elements = processingEnvironment.getElementUtils(); return types.isSameType( returnType, elements.getTypeElement(dataenum_case.class.getCanonicalName()).asType()); } private static boolean isNullableAnnotation(AnnotationMirror annotation) { return "Nullable".contentEquals(annotation.getAnnotationType().asElement().getSimpleName()); } private static boolean isRedactedAnnotation(AnnotationMirror annotationMirror) { return "Redacted" .contentEquals(annotationMirror.getAnnotationType().asElement().getSimpleName()); } }