• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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