dagger.internal.codegen.writing.ComponentNames Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dagger-compiler Show documentation
Show all versions of dagger-compiler Show documentation
A fast dependency injector for Android and Java.
/*
* 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.internal.codegen.compileroption.CompilerOptions;
import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.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 top-level generated class. */
public static ClassName getTopLevelClassName(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 CompilerOptions compilerOptions;
private final ClassName topLevelClassName;
private final ImmutableMap namesByPath;
private final ImmutableMap creatorNamesByPath;
private final ImmutableMultimap pathsByCreatorKey;
@Inject
ComponentNames(
CompilerOptions compilerOptions, @TopLevel BindingGraph graph, KeyFactory keyFactory) {
this.compilerOptions = compilerOptions;
this.topLevelClassName = getTopLevelClassName(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 compilerOptions.generatedClassExtendsComponent() && componentPath.atRoot()
? topLevelClassName
: topLevelClassName.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 topLevelClassName.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);
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() : "$";
}
}