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