| ronshapiro | 61bdbb3 | 2018-12-21 12:01:20 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 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.internal.codegen; |
| 18 | |
| 19 | import static com.google.common.base.Preconditions.checkArgument; |
| 20 | import static com.squareup.javapoet.MethodSpec.methodBuilder; |
| 21 | import static dagger.internal.codegen.Accessibility.isRawTypePubliclyAccessible; |
| 22 | import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock; |
| 23 | import static javax.lang.model.element.Modifier.PUBLIC; |
| 24 | import static javax.lang.model.element.Modifier.STATIC; |
| 25 | |
| 26 | import com.google.auto.value.AutoValue; |
| 27 | import com.google.common.collect.ImmutableList; |
| 28 | import com.google.common.collect.ImmutableMap; |
| 29 | import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| 30 | import com.google.errorprone.annotations.CheckReturnValue; |
| 31 | import com.squareup.javapoet.ClassName; |
| 32 | import com.squareup.javapoet.CodeBlock; |
| 33 | import com.squareup.javapoet.MethodSpec; |
| 34 | import com.squareup.javapoet.ParameterSpec; |
| 35 | import com.squareup.javapoet.TypeName; |
| 36 | import com.squareup.javapoet.TypeVariableName; |
| 37 | import java.util.List; |
| 38 | import java.util.Optional; |
| 39 | import javax.lang.model.element.ExecutableElement; |
| 40 | import javax.lang.model.element.Parameterizable; |
| 41 | import javax.lang.model.element.VariableElement; |
| 42 | import javax.lang.model.type.DeclaredType; |
| 43 | import javax.lang.model.type.TypeMirror; |
| 44 | |
| 45 | /** |
| 46 | * A static method that implements provision and/or injection in one step: |
| 47 | * |
| 48 | * <ul> |
| 49 | * <li>methods that invoke {@code @Inject} constructors and do members injection if necessary |
| 50 | * <li>methods that call {@code @Provides} module methods |
| 51 | * <li>methods that perform members injection |
| 52 | * </ul> |
| 53 | * |
| 54 | * <p>Note that although this type uses {@code @AutoValue}, it uses instance equality. It uses |
| 55 | * {@code @AutoValue} to avoid the boilerplate of writing a correct builder, but is not intended to |
| 56 | * actually be a value type. |
| 57 | */ |
| 58 | @AutoValue |
| 59 | abstract class InjectionMethod { |
| 60 | abstract String name(); |
| 61 | |
| 62 | abstract boolean varargs(); |
| 63 | |
| 64 | abstract ImmutableList<TypeVariableName> typeVariables(); |
| 65 | |
| 66 | abstract ImmutableMap<ParameterSpec, TypeMirror> parameters(); |
| 67 | |
| 68 | abstract Optional<TypeMirror> returnType(); |
| 69 | |
| 70 | abstract Optional<DeclaredType> nullableAnnotation(); |
| 71 | |
| 72 | abstract ImmutableList<TypeMirror> exceptions(); |
| 73 | |
| 74 | abstract CodeBlock methodBody(); |
| 75 | |
| 76 | abstract ClassName enclosingClass(); |
| 77 | |
| 78 | MethodSpec toMethodSpec() { |
| 79 | MethodSpec.Builder builder = |
| 80 | methodBuilder(name()) |
| 81 | .addModifiers(PUBLIC, STATIC) |
| 82 | .varargs(varargs()) |
| 83 | .addTypeVariables(typeVariables()) |
| 84 | .addParameters(parameters().keySet()) |
| 85 | .addCode(methodBody()); |
| 86 | returnType().map(TypeName::get).ifPresent(builder::returns); |
| 87 | nullableAnnotation() |
| 88 | .ifPresent(nullableType -> CodeBlocks.addAnnotation(builder, nullableType)); |
| 89 | exceptions().stream().map(TypeName::get).forEach(builder::addException); |
| 90 | return builder.build(); |
| 91 | } |
| 92 | |
| 93 | CodeBlock invoke(List<CodeBlock> arguments, ClassName requestingClass) { |
| 94 | checkArgument(arguments.size() == parameters().size()); |
| 95 | CodeBlock.Builder invocation = CodeBlock.builder(); |
| 96 | if (!enclosingClass().equals(requestingClass)) { |
| 97 | invocation.add("$T.", enclosingClass()); |
| 98 | } |
| 99 | return invocation.add("$L($L)", name(), makeParametersCodeBlock(arguments)).build(); |
| 100 | } |
| 101 | |
| 102 | @Override |
| 103 | public final int hashCode() { |
| 104 | return System.identityHashCode(this); |
| 105 | } |
| 106 | |
| 107 | @Override |
| 108 | public final boolean equals(Object obj) { |
| 109 | return this == obj; |
| 110 | } |
| 111 | |
| 112 | static Builder builder(DaggerElements elements) { |
| 113 | Builder builder = new AutoValue_InjectionMethod.Builder(); |
| 114 | builder.elements = elements; |
| 115 | builder.varargs(false).exceptions(ImmutableList.of()).nullableAnnotation(Optional.empty()); |
| 116 | return builder; |
| 117 | } |
| 118 | |
| 119 | @CanIgnoreReturnValue |
| 120 | @AutoValue.Builder |
| 121 | abstract static class Builder { |
| 122 | private final UniqueNameSet parameterNames = new UniqueNameSet(); |
| 123 | private final CodeBlock.Builder methodBody = CodeBlock.builder(); |
| 124 | private DaggerElements elements; |
| 125 | |
| 126 | abstract ImmutableMap.Builder<ParameterSpec, TypeMirror> parametersBuilder(); |
| 127 | abstract ImmutableList.Builder<TypeVariableName> typeVariablesBuilder(); |
| 128 | abstract Builder name(String name); |
| 129 | abstract Builder varargs(boolean varargs); |
| 130 | abstract Builder returnType(TypeMirror returnType); |
| 131 | abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions); |
| 132 | abstract Builder nullableAnnotation(Optional<DeclaredType> nullableAnnotation); |
| 133 | abstract Builder methodBody(CodeBlock methodBody); |
| 134 | |
| 135 | final CodeBlock.Builder methodBodyBuilder() { |
| 136 | return methodBody; |
| 137 | } |
| 138 | |
| 139 | abstract Builder enclosingClass(ClassName enclosingClass); |
| 140 | |
| 141 | /** |
| 142 | * Adds a parameter for the given name and type. If another parameter has already been added |
| 143 | * with the same name, the name is disambiguated. |
| 144 | */ |
| 145 | ParameterSpec addParameter(String name, TypeMirror type) { |
| 146 | ParameterSpec parameter = |
| 147 | ParameterSpec.builder(TypeName.get(type), parameterNames.getUniqueName(name)).build(); |
| 148 | parametersBuilder().put(parameter, type); |
| 149 | return parameter; |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Calls {@link #copyParameter(VariableElement)} for each parameter of of {@code method}, and |
| 154 | * concatenates the results of each call, {@link CodeBlocks#makeParametersCodeBlock(Iterable) |
| 155 | * separated with commas}. |
| 156 | */ |
| 157 | CodeBlock copyParameters(ExecutableElement method) { |
| 158 | ImmutableList.Builder<CodeBlock> argumentsBuilder = ImmutableList.builder(); |
| 159 | for (VariableElement parameter : method.getParameters()) { |
| 160 | argumentsBuilder.add(copyParameter(parameter)); |
| 161 | } |
| 162 | varargs(method.isVarArgs()); |
| 163 | return makeParametersCodeBlock(argumentsBuilder.build()); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Adds {@code parameter} as a parameter of this method, using a publicly accessible version of |
| 168 | * the parameter's type. Returns a {@link CodeBlock} of the usage of this parameter within the |
| 169 | * injection method's {@link #methodBody()}. |
| 170 | */ |
| 171 | CodeBlock copyParameter(VariableElement parameter) { |
| 172 | TypeMirror elementType = parameter.asType(); |
| 173 | boolean useObject = !isRawTypePubliclyAccessible(elementType); |
| 174 | TypeMirror publicType = useObject ? objectType() : elementType; |
| 175 | ParameterSpec parameterSpec = addParameter(parameter.getSimpleName().toString(), publicType); |
| 176 | return useObject |
| 177 | ? CodeBlock.of("($T) $N", elementType, parameterSpec) |
| 178 | : CodeBlock.of("$N", parameterSpec); |
| 179 | } |
| 180 | |
| 181 | private TypeMirror objectType() { |
| 182 | return elements.getTypeElement(Object.class).asType(); |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * Adds each type parameter of {@code parameterizable} as a type parameter of this injection |
| 187 | * method. |
| 188 | */ |
| 189 | Builder copyTypeParameters(Parameterizable parameterizable) { |
| 190 | parameterizable.getTypeParameters().stream() |
| 191 | .map(TypeVariableName::get) |
| 192 | .forEach(typeVariablesBuilder()::add); |
| 193 | return this; |
| 194 | } |
| 195 | |
| 196 | Builder copyThrows(ExecutableElement element) { |
| 197 | exceptions(element.getThrownTypes()); |
| 198 | return this; |
| 199 | } |
| 200 | |
| 201 | @CheckReturnValue |
| 202 | final InjectionMethod build() { |
| 203 | return methodBody(methodBody.build()).buildInternal(); |
| 204 | } |
| 205 | |
| 206 | abstract InjectionMethod buildInternal(); |
| 207 | } |
| 208 | } |