1 /*
2  * Copyright (C) 2015 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 com.google.common.collect.Sets.immutableEnumSet;
20 import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes;
21 import static dagger.internal.codegen.base.ElementFormatter.elementToString;
22 import static javax.lang.model.element.ElementKind.PARAMETER;
23 import static javax.lang.model.type.TypeKind.DECLARED;
24 import static javax.lang.model.type.TypeKind.EXECUTABLE;
25 
26 import com.google.auto.common.MoreElements;
27 import com.google.auto.common.MoreTypes;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.ImmutableSet;
30 import dagger.internal.codegen.base.Formatter;
31 import javax.inject.Inject;
32 import javax.lang.model.element.Element;
33 import javax.lang.model.element.TypeElement;
34 import javax.lang.model.type.TypeKind;
35 
36 /**
37  * Formats a {@link BindingDeclaration} into a {@link String} suitable for use in error messages.
38  */
39 public final class BindingDeclarationFormatter extends Formatter<BindingDeclaration> {
40   private static final ImmutableSet<TypeKind> FORMATTABLE_ELEMENT_TYPE_KINDS =
41       immutableEnumSet(EXECUTABLE, DECLARED);
42 
43   private final MethodSignatureFormatter methodSignatureFormatter;
44 
45   @Inject
BindingDeclarationFormatter(MethodSignatureFormatter methodSignatureFormatter)46   BindingDeclarationFormatter(MethodSignatureFormatter methodSignatureFormatter) {
47     this.methodSignatureFormatter = methodSignatureFormatter;
48   }
49 
50   /**
51    * Returns {@code true} for declarations that this formatter can format. Specifically bindings
52    * from subcomponent declarations or those with {@linkplain BindingDeclaration#bindingElement()
53    * binding elements} that are methods, constructors, or types.
54    */
canFormat(BindingDeclaration bindingDeclaration)55   public boolean canFormat(BindingDeclaration bindingDeclaration) {
56     if (bindingDeclaration instanceof SubcomponentDeclaration) {
57       return true;
58     }
59     if (bindingDeclaration.bindingElement().isPresent()) {
60       Element bindingElement = bindingDeclaration.bindingElement().get();
61       return bindingElement.getKind().equals(PARAMETER)
62           || FORMATTABLE_ELEMENT_TYPE_KINDS.contains(bindingElement.asType().getKind());
63     }
64     // TODO(dpb): validate whether what this is doing is correct
65     return false;
66   }
67 
68   @Override
format(BindingDeclaration bindingDeclaration)69   public String format(BindingDeclaration bindingDeclaration) {
70     if (bindingDeclaration instanceof SubcomponentDeclaration) {
71       return formatSubcomponentDeclaration((SubcomponentDeclaration) bindingDeclaration);
72     }
73 
74     if (bindingDeclaration.bindingElement().isPresent()) {
75       Element bindingElement = bindingDeclaration.bindingElement().get();
76       if (bindingElement.getKind().equals(PARAMETER)) {
77         return elementToString(bindingElement);
78       }
79 
80       switch (bindingElement.asType().getKind()) {
81         case EXECUTABLE:
82           return methodSignatureFormatter.format(
83               MoreElements.asExecutable(bindingElement),
84               bindingDeclaration
85                   .contributingModule()
86                   .map(module -> MoreTypes.asDeclared(module.asType())));
87 
88         case DECLARED:
89           return stripCommonTypePrefixes(bindingElement.asType().toString());
90 
91         default:
92           throw new IllegalArgumentException(
93               "Formatting unsupported for element: " + bindingElement);
94       }
95     }
96 
97     return String.format(
98         "Dagger-generated binding for %s",
99         stripCommonTypePrefixes(bindingDeclaration.key().toString()));
100   }
101 
formatSubcomponentDeclaration(SubcomponentDeclaration subcomponentDeclaration)102   private String formatSubcomponentDeclaration(SubcomponentDeclaration subcomponentDeclaration) {
103     ImmutableList<TypeElement> moduleSubcomponents =
104         subcomponentDeclaration.moduleAnnotation().subcomponents();
105     int index = moduleSubcomponents.indexOf(subcomponentDeclaration.subcomponentType());
106     StringBuilder annotationValue = new StringBuilder();
107     if (moduleSubcomponents.size() != 1) {
108       annotationValue.append("{");
109     }
110     annotationValue.append(
111         formatArgumentInList(
112             index,
113             moduleSubcomponents.size(),
114             subcomponentDeclaration.subcomponentType().getQualifiedName() + ".class"));
115     if (moduleSubcomponents.size() != 1) {
116       annotationValue.append("}");
117     }
118 
119     return String.format(
120         "@%s(subcomponents = %s) for %s",
121         subcomponentDeclaration.moduleAnnotation().annotationName(),
122         annotationValue,
123         subcomponentDeclaration.contributingModule().get());
124   }
125 }
126