1 /* 2 * Copyright (C) 2014 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.binding; 18 19 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; 20 import static com.google.common.base.Preconditions.checkArgument; 21 import static com.google.common.base.Preconditions.checkState; 22 import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 24 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; 25 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 26 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 27 28 import androidx.room.compiler.processing.XAnnotation; 29 import androidx.room.compiler.processing.XExecutableElement; 30 import androidx.room.compiler.processing.XExecutableParameterElement; 31 import androidx.room.compiler.processing.XExecutableType; 32 import androidx.room.compiler.processing.XMethodElement; 33 import androidx.room.compiler.processing.XMethodType; 34 import androidx.room.compiler.processing.XType; 35 import androidx.room.compiler.processing.XTypeElement; 36 import androidx.room.compiler.processing.XVariableElement; 37 import com.google.common.collect.ImmutableList; 38 import com.google.common.collect.Streams; 39 import com.squareup.javapoet.ClassName; 40 import dagger.internal.codegen.base.Formatter; 41 import dagger.internal.codegen.xprocessing.XAnnotations; 42 import dagger.internal.codegen.xprocessing.XTypes; 43 import java.util.Iterator; 44 import java.util.Optional; 45 import javax.inject.Inject; 46 47 /** Formats the signature of an {@link XExecutableElement} suitable for use in error messages. */ 48 public final class MethodSignatureFormatter extends Formatter<XExecutableElement> { 49 private static final ClassName JET_BRAINS_NOT_NULL = 50 ClassName.get("org.jetbrains.annotations", "NotNull"); 51 private static final ClassName JET_BRAINS_NULLABLE = 52 ClassName.get("org.jetbrains.annotations", "Nullable"); 53 54 private final InjectionAnnotations injectionAnnotations; 55 56 @Inject MethodSignatureFormatter(InjectionAnnotations injectionAnnotations)57 MethodSignatureFormatter(InjectionAnnotations injectionAnnotations) { 58 this.injectionAnnotations = injectionAnnotations; 59 } 60 61 /** 62 * A formatter that uses the type where the method is declared for the annotations and name of the 63 * method, but the method's resolved type as a member of {@code type} for the key. 64 */ typedFormatter(XType type)65 public Formatter<XMethodElement> typedFormatter(XType type) { 66 checkArgument(isDeclared(type)); 67 return new Formatter<XMethodElement>() { 68 @Override 69 public String format(XMethodElement method) { 70 return MethodSignatureFormatter.this.format( 71 method, 72 method.asMemberOf(type), 73 closestEnclosingTypeElement(method), 74 /* includeReturnType= */ true); 75 } 76 }; 77 } 78 79 @Override 80 public String format(XExecutableElement method) { 81 return format(method, Optional.empty()); 82 } 83 84 /** 85 * Formats an ExecutableElement as if it were contained within the container, if the container is 86 * present. 87 */ 88 public String format(XExecutableElement method, Optional<XType> container) { 89 return format(method, container, /* includeReturnType= */ true); 90 } 91 92 private String format( 93 XExecutableElement method, Optional<XType> container, boolean includeReturnType) { 94 return container.isPresent() 95 ? format( 96 method, 97 method.asMemberOf(container.get()), 98 container.get().getTypeElement(), 99 includeReturnType) 100 : format( 101 method, 102 method.getExecutableType(), 103 closestEnclosingTypeElement(method), 104 includeReturnType); 105 } 106 107 private String format( 108 XExecutableElement method, 109 XExecutableType methodType, 110 XTypeElement container, 111 boolean includeReturnType) { 112 StringBuilder builder = new StringBuilder(); 113 ImmutableList<String> formattedAnnotations = formatedAnnotations(method); 114 if (!formattedAnnotations.isEmpty()) { 115 builder.append(String.join(" ", formattedAnnotations)).append(" "); 116 } 117 if (getSimpleName(method).contentEquals("<init>")) { 118 builder.append(container.getQualifiedName()); 119 } else { 120 if (includeReturnType) { 121 builder.append(nameOfType(((XMethodType) methodType).getReturnType())).append(' '); 122 } 123 builder.append(container.getQualifiedName()).append('.').append(getSimpleName(method)); 124 } 125 builder.append('('); 126 checkState(method.getParameters().size() == methodType.getParameterTypes().size()); 127 Iterator<XExecutableParameterElement> parameters = method.getParameters().iterator(); 128 Iterator<XType> parameterTypes = methodType.getParameterTypes().iterator(); 129 for (int i = 0; parameters.hasNext(); i++) { 130 if (i > 0) { 131 builder.append(", "); 132 } 133 appendParameter(builder, parameters.next(), parameterTypes.next()); 134 } 135 builder.append(')'); 136 return builder.toString(); 137 } 138 139 public String formatWithoutReturnType(XExecutableElement method) { 140 return format(method, Optional.empty(), /* includeReturnType= */ false); 141 } 142 143 private void appendParameter( 144 StringBuilder builder, XVariableElement parameter, XType parameterType) { 145 injectionAnnotations 146 .getQualifier(parameter) 147 .ifPresent(qualifier -> builder.append(formatAnnotation(qualifier)).append(' ')); 148 builder.append(nameOfType(parameterType)); 149 } 150 151 private static String nameOfType(XType type) { 152 return stripCommonTypePrefixes(XTypes.toStableString(type)); 153 } 154 155 private static ImmutableList<String> formatedAnnotations(XExecutableElement executableElement) { 156 Nullability nullability = Nullability.of(executableElement); 157 ImmutableList<String> formattedAnnotations = 158 Streams.concat( 159 executableElement.getAllAnnotations().stream() 160 // Filter out @NotNull annotations added by KAPT to make error messages 161 // consistent 162 .filter(annotation -> !annotation.getClassName().equals(JET_BRAINS_NOT_NULL)) 163 .map(MethodSignatureFormatter::formatAnnotation), 164 nullability.nullableAnnotations().stream() 165 // Filter out @NotNull annotations added by KAPT to make error messages 166 // consistent 167 .filter(annotation -> !annotation.equals(JET_BRAINS_NOT_NULL)) 168 .map(annotation -> String.format("@%s", annotation.canonicalName()))) 169 .distinct() 170 .collect(toImmutableList()); 171 if (nullability.isKotlinTypeNullable() 172 && nullability.nullableAnnotations().stream().noneMatch(JET_BRAINS_NULLABLE::equals) 173 && getProcessingEnv(executableElement).findTypeElement(JET_BRAINS_NULLABLE) != null) { 174 formattedAnnotations = 175 ImmutableList.<String>builder() 176 .addAll(formattedAnnotations) 177 .add(String.format("@%s", JET_BRAINS_NULLABLE.canonicalName())) 178 .build(); 179 } 180 return formattedAnnotations; 181 } 182 183 private static String formatAnnotation(XAnnotation annotation) { 184 return stripCommonTypePrefixes(XAnnotations.toString(annotation)); 185 } 186 } 187