/* * Copyright (C) 2015 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen.writing; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.binding.SourceFiles.classFileName; import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement; import static java.lang.String.format; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Multimaps; import com.squareup.javapoet.ClassName; import dagger.internal.codegen.base.ComponentCreatorKind; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentCreatorDescriptor; import dagger.internal.codegen.binding.ComponentDescriptor; import dagger.internal.codegen.binding.KeyFactory; import dagger.spi.model.ComponentPath; import dagger.spi.model.Key; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import javax.inject.Inject; /** * Holds the unique simple names for all components, keyed by their {@link ComponentPath} and {@link * Key} of the subcomponent builder. */ public final class ComponentNames { /** Returns the class name for the root component. */ public static ClassName getRootComponentClassName(ComponentDescriptor componentDescriptor) { checkState(!componentDescriptor.isSubcomponent()); ClassName componentName = componentDescriptor.typeElement().getClassName(); return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName)); } private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); private final ClassName rootName; private final ImmutableMap namesByPath; private final ImmutableMap creatorNamesByPath; private final ImmutableMultimap pathsByCreatorKey; @Inject ComponentNames(@TopLevel BindingGraph graph, KeyFactory keyFactory) { this.rootName = getRootComponentClassName(graph.componentDescriptor()); this.namesByPath = namesByPath(graph); this.creatorNamesByPath = creatorNamesByPath(namesByPath, graph); this.pathsByCreatorKey = pathsByCreatorKey(keyFactory, graph); } /** Returns the simple component name for the given {@link ComponentDescriptor}. */ ClassName get(ComponentPath componentPath) { return componentPath.atRoot() ? rootName : rootName.nestedClass(namesByPath.get(componentPath) + "Impl"); } /** * Returns the component descriptor for the component with the given subcomponent creator {@link * Key}. */ ClassName getSubcomponentCreatorName(ComponentPath componentPath, Key creatorKey) { checkArgument(pathsByCreatorKey.containsKey(creatorKey)); // First, find the subcomponent path corresponding to the subcomponent creator key. // The key may correspond to multiple paths, so we need to find the one under this component. ComponentPath subcomponentPath = pathsByCreatorKey.get(creatorKey).stream() .filter(path -> path.parent().equals(componentPath)) .collect(onlyElement()); return getCreatorName(subcomponentPath); } /** * Returns the simple name for the subcomponent creator implementation for the given {@link * ComponentDescriptor}. */ ClassName getCreatorName(ComponentPath componentPath) { checkArgument(creatorNamesByPath.containsKey(componentPath)); return rootName.nestedClass(creatorNamesByPath.get(componentPath)); } private static ImmutableMap creatorNamesByPath( ImmutableMap namesByPath, BindingGraph graph) { ImmutableMap.Builder builder = ImmutableMap.builder(); graph .componentDescriptorsByPath() .forEach( (componentPath, componentDescriptor) -> { if (componentPath.atRoot()) { ComponentCreatorKind creatorKind = componentDescriptor .creatorDescriptor() .map(ComponentCreatorDescriptor::kind) .orElse(ComponentCreatorKind.BUILDER); builder.put(componentPath, creatorKind.typeName()); } else if (componentDescriptor.creatorDescriptor().isPresent()) { ComponentCreatorDescriptor creatorDescriptor = componentDescriptor.creatorDescriptor().get(); String componentName = namesByPath.get(componentPath); builder.put(componentPath, componentName + creatorDescriptor.kind().typeName()); } }); return builder.build(); } private static ImmutableMap namesByPath(BindingGraph graph) { Map componentPathsBySimpleName = new LinkedHashMap<>(); Multimaps.index(graph.componentDescriptorsByPath().keySet(), ComponentNames::simpleName) .asMap() .values() .stream() .map(ComponentNames::disambiguateConflictingSimpleNames) .forEach(componentPathsBySimpleName::putAll); componentPathsBySimpleName.remove(graph.componentPath()); return ImmutableMap.copyOf(componentPathsBySimpleName); } private static ImmutableMultimap pathsByCreatorKey( KeyFactory keyFactory, BindingGraph graph) { ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); graph .componentDescriptorsByPath() .forEach( (componentPath, componentDescriptor) -> { if (componentDescriptor.creatorDescriptor().isPresent()) { Key creatorKey = keyFactory.forSubcomponentCreator( componentDescriptor.creatorDescriptor().get().typeElement().getType()); builder.put(creatorKey, componentPath); } }); return builder.build(); } private static ImmutableMap disambiguateConflictingSimpleNames( Collection componentsWithConflictingNames) { // If there's only 1 component there's nothing to disambiguate so return the simple name. if (componentsWithConflictingNames.size() == 1) { ComponentPath componentPath = Iterables.getOnlyElement(componentsWithConflictingNames); return ImmutableMap.of(componentPath, simpleName(componentPath)); } // There are conflicting simple names, so disambiguate them with a unique prefix. // We keep them small to fix https://github.com/google/dagger/issues/421. UniqueNameSet nameSet = new UniqueNameSet(); ImmutableMap.Builder uniqueNames = ImmutableMap.builder(); for (ComponentPath componentPath : componentsWithConflictingNames) { String simpleName = simpleName(componentPath); String basePrefix = uniquingPrefix(componentPath); uniqueNames.put( componentPath, format("%s_%s", nameSet.getUniqueName(basePrefix), simpleName)); } return uniqueNames.build(); } private static String simpleName(ComponentPath componentPath) { return componentPath.currentComponent().className().simpleName(); } /** Returns a prefix that could make the component's simple name more unique. */ private static String uniquingPrefix(ComponentPath componentPath) { ClassName component = componentPath.currentComponent().className(); if (component.enclosingClassName() != null) { return CharMatcher.javaLowerCase().removeFrom(component.enclosingClassName().simpleName()); } // Not in a normally named class. Prefix with the initials of the elements leading here. Iterator pieces = QUALIFIED_NAME_SPLITTER.split(component.canonicalName()).iterator(); StringBuilder b = new StringBuilder(); while (pieces.hasNext()) { String next = pieces.next(); if (pieces.hasNext()) { b.append(next.charAt(0)); } } // Note that a top level class in the root package will be prefixed "$_". return b.length() > 0 ? b.toString() : "$"; } }