blob: b8bfc85cfdce90686cbf3a3fe0ab95ce4ee33d27 [file] [log] [blame]
/*
* 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.internal.codegen.binding;
import static androidx.room.compiler.processing.XTypeKt.isArray;
import static androidx.room.compiler.processing.compat.XConverters.toJavac;
import static dagger.internal.codegen.binding.SourceFiles.classFileName;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasAnnotationValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasArrayValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasByteValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasCharValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasDoubleValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasEnumValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasFloatValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasLongValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasShortValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasStringValue;
import static dagger.internal.codegen.xprocessing.XAnnotationValues.hasTypeValue;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XTypes.asArray;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XAnnotationValue;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableMap;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import dagger.internal.codegen.xprocessing.XElements;
import javax.lang.model.element.AnnotationValue;
/**
* Returns an expression creating an instance of the visited annotation type. Its parameter must be
* a class as generated by {@link dagger.internal.codegen.writing.AnnotationCreatorGenerator}.
*
* <p>Note that {@link AnnotationValue#toString()} is the source-code representation of the value
* <em>when used in an annotation</em>, which is not always the same as the representation needed
* when creating the value in a method body.
*
* <p>For example, inside an annotation, a nested array of {@code int}s is simply {@code {1, 2, 3}},
* but in code it would have to be {@code new int[] {1, 2, 3}}.
*/
public final class AnnotationExpression {
private final XAnnotation annotation;
private final ClassName creatorClass;
AnnotationExpression(XAnnotation annotation) {
this.annotation = annotation;
this.creatorClass = getAnnotationCreatorClassName(annotation.getType().getTypeElement());
}
/**
* Returns an expression that calls static methods on the annotation's creator class to create an
* annotation instance equivalent the annotation passed to the constructor.
*/
CodeBlock getAnnotationInstanceExpression() {
return getAnnotationInstanceExpression(annotation);
}
private CodeBlock getAnnotationInstanceExpression(XAnnotation annotation) {
ImmutableMap<String, XType> valueTypesByName =
annotation.getType().getTypeElement().getDeclaredMethods().stream()
.filter(method -> method.getParameters().isEmpty())
.collect(toImmutableMap(XElements::getSimpleName, XMethodElement::getReturnType));
return CodeBlock.of(
"$T.$L($L)",
creatorClass,
createMethodName(annotation.getType().getTypeElement()),
makeParametersCodeBlock(
annotation.getAnnotationValues().stream()
.map(
value -> {
String name =
value.getName(); // SUPPRESS_GET_NAME_CHECK: This is not XElement
return getValueExpression(value, valueTypesByName.get(name));
})
.collect(toImmutableList())));
}
/**
* Returns the name of the generated class that contains the static {@code create} methods for an
* annotation type.
*/
public static ClassName getAnnotationCreatorClassName(XTypeElement annotationType) {
ClassName annotationTypeName = annotationType.getClassName();
return annotationTypeName
.topLevelClassName()
.peerClass(classFileName(annotationTypeName) + "Creator");
}
public static String createMethodName(XTypeElement annotationType) {
return "create" + getSimpleName(annotationType);
}
/**
* Returns an expression that evaluates to a {@code value} of a given type on an {@code
* annotation}.
*/
CodeBlock getValueExpression(XAnnotationValue value, XType valueType) {
return isArray(valueType)
? CodeBlock.of(
"new $T[] $L",
asArray(valueType).getComponentType().getRawType().getTypeName(),
visit(value))
: visit(value);
}
private CodeBlock visit(XAnnotationValue value) {
if (hasEnumValue(value)) {
return CodeBlock.of(
"$T.$L",
value.asEnum().getEnclosingElement().getClassName(),
getSimpleName(value.asEnum()));
} else if (hasAnnotationValue(value)) {
return getAnnotationInstanceExpression(value.asAnnotation());
} else if (hasTypeValue(value)) {
return CodeBlock.of("$T.class", value.asType().getTypeName());
} else if (hasStringValue(value)) {
return CodeBlock.of("$S", value.asString());
} else if (hasByteValue(value)) {
return CodeBlock.of("(byte) $L", value.asByte());
} else if (hasCharValue(value)) {
// TODO(bcorso): This relies on AnnotationValue.toString() to properly output escaped
// characters like '\n'. See https://github.com/square/javapoet/issues/698.
return CodeBlock.of("$L", toJavac(value));
} else if (hasDoubleValue(value)) {
return CodeBlock.of("$LD", value.asDouble());
} else if (hasFloatValue(value)) {
return CodeBlock.of("$LF", value.asFloat());
} else if (hasLongValue(value)) {
return CodeBlock.of("$LL", value.asLong());
} else if (hasShortValue(value)) {
return CodeBlock.of("(short) $L", value.asShort());
} else if (hasArrayValue(value)) {
return CodeBlock.of(
"{$L}",
value.asAnnotationValueList().stream().map(this::visit).collect(toParametersCodeBlock()));
} else {
return CodeBlock.of("$L", value.getValue());
}
}
}