• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.writing;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkState;
21 import static dagger.internal.codegen.binding.SourceFiles.classFileName;
22 import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement;
23 import static java.lang.String.format;
24 
25 import com.google.common.base.CharMatcher;
26 import com.google.common.base.Splitter;
27 import com.google.common.collect.ImmutableMap;
28 import com.google.common.collect.ImmutableMultimap;
29 import com.google.common.collect.Iterables;
30 import com.google.common.collect.Multimaps;
31 import com.squareup.javapoet.ClassName;
32 import dagger.internal.codegen.base.ComponentCreatorKind;
33 import dagger.internal.codegen.base.UniqueNameSet;
34 import dagger.internal.codegen.binding.BindingGraph;
35 import dagger.internal.codegen.binding.ComponentCreatorDescriptor;
36 import dagger.internal.codegen.binding.ComponentDescriptor;
37 import dagger.internal.codegen.binding.KeyFactory;
38 import dagger.spi.model.ComponentPath;
39 import dagger.spi.model.Key;
40 import java.util.Collection;
41 import java.util.Iterator;
42 import java.util.LinkedHashMap;
43 import java.util.Map;
44 import javax.inject.Inject;
45 
46 /**
47  * Holds the unique simple names for all components, keyed by their {@link ComponentPath} and {@link
48  * Key} of the subcomponent builder.
49  */
50 public final class ComponentNames {
51   /** Returns the class name for the root component. */
getRootComponentClassName(ComponentDescriptor componentDescriptor)52   public static ClassName getRootComponentClassName(ComponentDescriptor componentDescriptor) {
53     checkState(!componentDescriptor.isSubcomponent());
54     ClassName componentName = componentDescriptor.typeElement().getClassName();
55     return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName));
56   }
57 
58   private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.');
59 
60   private final ClassName rootName;
61   private final ImmutableMap<ComponentPath, String> namesByPath;
62   private final ImmutableMap<ComponentPath, String> creatorNamesByPath;
63   private final ImmutableMultimap<Key, ComponentPath> pathsByCreatorKey;
64 
65   @Inject
ComponentNames(@opLevel BindingGraph graph, KeyFactory keyFactory)66   ComponentNames(@TopLevel BindingGraph graph, KeyFactory keyFactory) {
67     this.rootName = getRootComponentClassName(graph.componentDescriptor());
68     this.namesByPath = namesByPath(graph);
69     this.creatorNamesByPath = creatorNamesByPath(namesByPath, graph);
70     this.pathsByCreatorKey = pathsByCreatorKey(keyFactory, graph);
71   }
72 
73   /** Returns the simple component name for the given {@link ComponentDescriptor}. */
get(ComponentPath componentPath)74   ClassName get(ComponentPath componentPath) {
75     return componentPath.atRoot()
76         ? rootName
77         : rootName.nestedClass(namesByPath.get(componentPath) + "Impl");
78   }
79 
80   /**
81    * Returns the component descriptor for the component with the given subcomponent creator {@link
82    * Key}.
83    */
getSubcomponentCreatorName(ComponentPath componentPath, Key creatorKey)84   ClassName getSubcomponentCreatorName(ComponentPath componentPath, Key creatorKey) {
85     checkArgument(pathsByCreatorKey.containsKey(creatorKey));
86     // First, find the subcomponent path corresponding to the subcomponent creator key.
87     // The key may correspond to multiple paths, so we need to find the one under this component.
88     ComponentPath subcomponentPath =
89         pathsByCreatorKey.get(creatorKey).stream()
90             .filter(path -> path.parent().equals(componentPath))
91             .collect(onlyElement());
92     return getCreatorName(subcomponentPath);
93   }
94 
95   /**
96    * Returns the simple name for the subcomponent creator implementation for the given {@link
97    * ComponentDescriptor}.
98    */
getCreatorName(ComponentPath componentPath)99   ClassName getCreatorName(ComponentPath componentPath) {
100     checkArgument(creatorNamesByPath.containsKey(componentPath));
101     return rootName.nestedClass(creatorNamesByPath.get(componentPath));
102   }
103 
creatorNamesByPath( ImmutableMap<ComponentPath, String> namesByPath, BindingGraph graph)104   private static ImmutableMap<ComponentPath, String> creatorNamesByPath(
105       ImmutableMap<ComponentPath, String> namesByPath, BindingGraph graph) {
106     ImmutableMap.Builder<ComponentPath, String> builder = ImmutableMap.builder();
107     graph
108         .componentDescriptorsByPath()
109         .forEach(
110             (componentPath, componentDescriptor) -> {
111               if (componentPath.atRoot()) {
112                 ComponentCreatorKind creatorKind =
113                     componentDescriptor
114                         .creatorDescriptor()
115                         .map(ComponentCreatorDescriptor::kind)
116                         .orElse(ComponentCreatorKind.BUILDER);
117                 builder.put(componentPath, creatorKind.typeName());
118               } else if (componentDescriptor.creatorDescriptor().isPresent()) {
119                 ComponentCreatorDescriptor creatorDescriptor =
120                     componentDescriptor.creatorDescriptor().get();
121                 String componentName = namesByPath.get(componentPath);
122                 builder.put(componentPath, componentName + creatorDescriptor.kind().typeName());
123               }
124             });
125     return builder.build();
126   }
127 
namesByPath(BindingGraph graph)128   private static ImmutableMap<ComponentPath, String> namesByPath(BindingGraph graph) {
129     Map<ComponentPath, String> componentPathsBySimpleName = new LinkedHashMap<>();
130     Multimaps.index(graph.componentDescriptorsByPath().keySet(), ComponentNames::simpleName)
131         .asMap()
132         .values()
133         .stream()
134         .map(ComponentNames::disambiguateConflictingSimpleNames)
135         .forEach(componentPathsBySimpleName::putAll);
136     componentPathsBySimpleName.remove(graph.componentPath());
137     return ImmutableMap.copyOf(componentPathsBySimpleName);
138   }
139 
pathsByCreatorKey( KeyFactory keyFactory, BindingGraph graph)140   private static ImmutableMultimap<Key, ComponentPath> pathsByCreatorKey(
141       KeyFactory keyFactory, BindingGraph graph) {
142     ImmutableMultimap.Builder<Key, ComponentPath> builder = ImmutableMultimap.builder();
143     graph
144         .componentDescriptorsByPath()
145         .forEach(
146             (componentPath, componentDescriptor) -> {
147               if (componentDescriptor.creatorDescriptor().isPresent()) {
148                 Key creatorKey =
149                     keyFactory.forSubcomponentCreator(
150                         componentDescriptor.creatorDescriptor().get().typeElement().getType());
151                 builder.put(creatorKey, componentPath);
152               }
153             });
154     return builder.build();
155   }
156 
disambiguateConflictingSimpleNames( Collection<ComponentPath> componentsWithConflictingNames)157   private static ImmutableMap<ComponentPath, String> disambiguateConflictingSimpleNames(
158       Collection<ComponentPath> componentsWithConflictingNames) {
159     // If there's only 1 component there's nothing to disambiguate so return the simple name.
160     if (componentsWithConflictingNames.size() == 1) {
161       ComponentPath componentPath = Iterables.getOnlyElement(componentsWithConflictingNames);
162       return ImmutableMap.of(componentPath, simpleName(componentPath));
163     }
164 
165     // There are conflicting simple names, so disambiguate them with a unique prefix.
166     // We keep them small to fix https://github.com/google/dagger/issues/421.
167     UniqueNameSet nameSet = new UniqueNameSet();
168     ImmutableMap.Builder<ComponentPath, String> uniqueNames = ImmutableMap.builder();
169     for (ComponentPath componentPath : componentsWithConflictingNames) {
170       String simpleName = simpleName(componentPath);
171       String basePrefix = uniquingPrefix(componentPath);
172       uniqueNames.put(
173           componentPath, format("%s_%s", nameSet.getUniqueName(basePrefix), simpleName));
174     }
175     return uniqueNames.build();
176   }
177 
simpleName(ComponentPath componentPath)178   private static String simpleName(ComponentPath componentPath) {
179     return componentPath.currentComponent().className().simpleName();
180   }
181 
182   /** Returns a prefix that could make the component's simple name more unique. */
uniquingPrefix(ComponentPath componentPath)183   private static String uniquingPrefix(ComponentPath componentPath) {
184     ClassName component = componentPath.currentComponent().className();
185 
186     if (component.enclosingClassName() != null) {
187       return CharMatcher.javaLowerCase().removeFrom(component.enclosingClassName().simpleName());
188     }
189 
190     // Not in a normally named class. Prefix with the initials of the elements leading here.
191     Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(component.canonicalName()).iterator();
192     StringBuilder b = new StringBuilder();
193 
194     while (pieces.hasNext()) {
195       String next = pieces.next();
196       if (pieces.hasNext()) {
197         b.append(next.charAt(0));
198       }
199     }
200 
201     // Note that a top level class in the root package will be prefixed "$_".
202     return b.length() > 0 ? b.toString() : "$";
203   }
204 }
205