180 lines
7.4 KiB
Java
180 lines
7.4 KiB
Java
/*
|
|
* Copyright (C) 2020 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.hilt.processor.internal;
|
|
|
|
import static java.lang.Character.isUpperCase;
|
|
import static java.lang.String.format;
|
|
import static java.util.Comparator.comparing;
|
|
|
|
import com.google.common.base.CharMatcher;
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.base.Splitter;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableListMultimap;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Iterables;
|
|
import com.google.common.collect.Multimaps;
|
|
import com.squareup.javapoet.ClassName;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import javax.lang.model.element.Name;
|
|
import javax.lang.model.element.TypeElement;
|
|
|
|
/**
|
|
* Utility class for getting the generated component name.
|
|
*
|
|
* <p>This should not be used externally.
|
|
*/
|
|
public final class ComponentNames {
|
|
private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.');
|
|
|
|
private final boolean renameTestComponents;
|
|
private final String destinationPackage;
|
|
private final ImmutableMap<ClassName, String> simpleNameByClassName;
|
|
|
|
public static ComponentNames withoutRenaming() {
|
|
return new ComponentNames(
|
|
/*renameTestComponents=*/ false, /*destinationPackage=*/ null, ImmutableMap.of());
|
|
}
|
|
|
|
public static ComponentNames withRenamingIntoPackage(
|
|
String destinationPackage, ImmutableList<TypeElement> roots) {
|
|
ImmutableMap.Builder<ClassName, String> builder = ImmutableMap.builder();
|
|
ImmutableListMultimap<String, TypeElement> rootsBySimpleName =
|
|
Multimaps.index(roots, typeElement -> typeElement.getSimpleName().toString());
|
|
rootsBySimpleName.asMap().values().stream()
|
|
.map(ComponentNames::disambiguateConflictingSimpleNames)
|
|
.forEach(builder::putAll);
|
|
return new ComponentNames(/*renameTestComponents=*/ true, destinationPackage, builder.build());
|
|
}
|
|
|
|
private ComponentNames(
|
|
boolean renameTestComponents,
|
|
String destinationPackage,
|
|
ImmutableMap<ClassName, String> simpleNameByClassName) {
|
|
this.renameTestComponents = renameTestComponents;
|
|
this.destinationPackage = destinationPackage;
|
|
this.simpleNameByClassName = simpleNameByClassName;
|
|
}
|
|
|
|
/** Returns the name of the generated component wrapper. */
|
|
public ClassName generatedComponentsWrapper(ClassName root) {
|
|
return Processors.append(
|
|
Processors.getEnclosedClassName(maybeRenameComponent(root)), "_HiltComponents");
|
|
}
|
|
|
|
/** Returns the name of the generated component. */
|
|
public ClassName generatedComponent(ClassName root, ClassName component) {
|
|
return generatedComponentsWrapper(root).nestedClass(componentName(component));
|
|
}
|
|
|
|
/**
|
|
* Returns the shortened component name by replacing the ending "Component" with "C" if it exists.
|
|
*
|
|
* <p>This is a hack because nested subcomponents in Dagger generate extremely long class names
|
|
* that hit the 256 character limit.
|
|
*/
|
|
// TODO(bcorso): See if this issue can be fixed in Dagger, e.g. by using static subcomponents.
|
|
private static String componentName(ClassName component) {
|
|
// TODO(bcorso): How do we want to handle collisions across packages? Currently, we only handle
|
|
// collisions across enclosing elements since namespacing by package would likely lead to too
|
|
// long of class names.
|
|
// Note: This uses regex matching so we only match if the name ends in "Component"
|
|
return Processors.getEnclosedName(component).replaceAll("Component$", "C");
|
|
}
|
|
|
|
/**
|
|
* Rewrites the provided HiltAndroidTest-annotated class name using the shared component
|
|
* directory.
|
|
*/
|
|
private ClassName maybeRenameComponent(ClassName className) {
|
|
return (renameTestComponents && !className.equals(ClassNames.DEFAULT_ROOT))
|
|
? ClassName.get(destinationPackage, dedupeSimpleName(className))
|
|
: className;
|
|
}
|
|
|
|
/**
|
|
* Derives a new generated component base name, should the simple names of two roots have
|
|
* conflicting simple names.
|
|
*
|
|
* <p>This is lifted nearly verbatim (albeit with new different struct types) from {@link
|
|
* dagger.internal.codegen.writing.SubcomponentNames}.
|
|
*/
|
|
private String dedupeSimpleName(ClassName className) {
|
|
Preconditions.checkState(
|
|
simpleNameByClassName.containsKey(className),
|
|
"Class name %s not found in simple name map",
|
|
className.canonicalName());
|
|
return simpleNameByClassName.get(className);
|
|
}
|
|
|
|
private static ImmutableMap<ClassName, String> disambiguateConflictingSimpleNames(
|
|
Collection<TypeElement> rootsWithConflictingNames) {
|
|
// If there's only 1 root there's nothing to disambiguate so return the simple name.
|
|
if (rootsWithConflictingNames.size() == 1) {
|
|
TypeElement root = Iterables.getOnlyElement(rootsWithConflictingNames);
|
|
return ImmutableMap.of(ClassName.get(root), root.getSimpleName().toString());
|
|
}
|
|
|
|
// 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.
|
|
// Sorted in order to guarantee determinism if this is invoked by different processors.
|
|
ImmutableList<TypeElement> sortedRootsWithConflictingNames =
|
|
ImmutableList.sortedCopyOf(
|
|
comparing(typeElement -> typeElement.getQualifiedName().toString()),
|
|
rootsWithConflictingNames);
|
|
Set<String> usedNames = new HashSet<>();
|
|
ImmutableMap.Builder<ClassName, String> uniqueNames = ImmutableMap.builder();
|
|
for (TypeElement root : sortedRootsWithConflictingNames) {
|
|
String basePrefix = uniquingPrefix(root);
|
|
String uniqueName = basePrefix;
|
|
for (int differentiator = 2; !usedNames.add(uniqueName); differentiator++) {
|
|
uniqueName = basePrefix + differentiator;
|
|
}
|
|
uniqueNames.put(ClassName.get(root), format("%s_%s", uniqueName, root.getSimpleName()));
|
|
}
|
|
return uniqueNames.build();
|
|
}
|
|
|
|
/** Returns a prefix that could make the component's simple name more unique. */
|
|
private static String uniquingPrefix(TypeElement typeElement) {
|
|
String containerName = typeElement.getEnclosingElement().getSimpleName().toString();
|
|
|
|
// If parent element looks like a class, use its initials as a prefix.
|
|
if (!containerName.isEmpty() && isUpperCase(containerName.charAt(0))) {
|
|
return CharMatcher.javaLowerCase().removeFrom(containerName);
|
|
}
|
|
|
|
// Not in a normally named class. Prefix with the initials of the elements leading here.
|
|
Name qualifiedName = typeElement.getQualifiedName();
|
|
Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(qualifiedName).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() : "$";
|
|
}
|
|
}
|