| /* |
| * 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; |
| |
| import static java.lang.Character.isUpperCase; |
| import static java.lang.String.format; |
| |
| import com.google.common.base.CharMatcher; |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.ImmutableBiMap; |
| import com.google.common.collect.ImmutableListMultimap; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Multimaps; |
| import dagger.model.Key; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeMirror; |
| |
| /** |
| * Holds the unique simple names for all subcomponents, keyed by their {@link ComponentDescriptor} |
| * and {@link Key} of the subcomponent builder. |
| */ |
| final class SubcomponentNames { |
| private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); |
| private final ImmutableMap<ComponentDescriptor, String> namesByDescriptor; |
| private final ImmutableMap<Key, String> namesByKey; |
| |
| SubcomponentNames(BindingGraph graph, KeyFactory keyFactory) { |
| this.namesByDescriptor = namesByDescriptor(graph); |
| this.namesByKey = namesByKey(keyFactory, namesByDescriptor); |
| } |
| |
| /** Returns the simple component name for the given {@link ComponentDescriptor}. */ |
| String get(ComponentDescriptor componentDescriptor) { |
| return namesByDescriptor.get(componentDescriptor); |
| } |
| |
| /** Returns the simple component name for the given subcomponent builder {@link Key}. */ |
| String get(Key key) { |
| return namesByKey.get(key); |
| } |
| |
| private static ImmutableMap<ComponentDescriptor, String> namesByDescriptor(BindingGraph graph) { |
| ImmutableListMultimap<String, ComponentDescriptor> componentDescriptorsBySimpleName = |
| Multimaps.index( |
| graph.componentDescriptors(), |
| componentDescriptor -> componentDescriptor.typeElement().getSimpleName().toString()); |
| ImmutableMap<ComponentDescriptor, Namer> componentNamers = |
| qualifiedNames(graph.componentDescriptors()); |
| Map<ComponentDescriptor, String> subcomponentImplSimpleNames = new LinkedHashMap<>(); |
| componentDescriptorsBySimpleName |
| .asMap() |
| .values() |
| .forEach( |
| components -> |
| subcomponentImplSimpleNames.putAll( |
| disambiguateConflictingSimpleNames(components, componentNamers))); |
| subcomponentImplSimpleNames.remove(graph.componentDescriptor()); |
| return ImmutableMap.copyOf(subcomponentImplSimpleNames); |
| } |
| |
| private static ImmutableMap<Key, String> namesByKey( |
| KeyFactory keyFactory, ImmutableMap<ComponentDescriptor, String> subcomponentNames) { |
| ImmutableMap.Builder<Key, String> builder = ImmutableMap.builder(); |
| subcomponentNames.forEach( |
| (component, name) -> |
| component |
| .creatorDescriptor() |
| .ifPresent( |
| creatorDescriptor -> { |
| TypeMirror creatorType = creatorDescriptor.typeElement().asType(); |
| builder.put(keyFactory.forSubcomponentCreator(creatorType), name); |
| })); |
| return builder.build(); |
| } |
| |
| private static ImmutableBiMap<ComponentDescriptor, String> disambiguateConflictingSimpleNames( |
| Collection<ComponentDescriptor> components, |
| ImmutableMap<ComponentDescriptor, Namer> componentNamers) { |
| Map<String, ComponentDescriptor> generatedSimpleNames = new LinkedHashMap<>(); |
| |
| // Let's see if we can get away with using simpleName() everywhere. |
| for (ComponentDescriptor component : components) { |
| Namer namer = componentNamers.get(component); |
| if (generatedSimpleNames.containsKey(namer.simpleName())) { |
| break; |
| } |
| generatedSimpleNames.put(namer.simpleName(), component); |
| } |
| |
| if (generatedSimpleNames.size() != components.size()) { |
| // Simple approach didn't work out, let's use more complicated names. |
| // We keep them small to fix https://github.com/google/dagger/issues/421. |
| generatedSimpleNames.clear(); |
| UniqueNameSet nameSet = new UniqueNameSet(); |
| for (ComponentDescriptor component : components) { |
| Namer namer = componentNamers.get(component); |
| String simpleName = namer.simpleName(); |
| String basePrefix = namer.uniquingPrefix(); |
| generatedSimpleNames.put( |
| format("%s_%s", nameSet.getUniqueName(basePrefix), simpleName), component); |
| } |
| } |
| return ImmutableBiMap.copyOf(generatedSimpleNames).inverse(); |
| } |
| |
| private static ImmutableMap<ComponentDescriptor, Namer> qualifiedNames( |
| Iterable<ComponentDescriptor> componentDescriptors) { |
| ImmutableMap.Builder<ComponentDescriptor, Namer> builder = ImmutableMap.builder(); |
| for (ComponentDescriptor component : componentDescriptors) { |
| builder.put(component, new Namer(component.typeElement())); |
| } |
| return builder.build(); |
| } |
| |
| private static final class Namer { |
| final TypeElement typeElement; |
| |
| Namer(TypeElement typeElement) { |
| this.typeElement = typeElement; |
| } |
| |
| String simpleName() { |
| return typeElement.getSimpleName().toString(); |
| } |
| |
| /** Returns a prefix that could make {@link #simpleName()} more unique. */ |
| String uniquingPrefix() { |
| 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() : "$"; |
| } |
| } |
| } |