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