blob: 019295f6af7f7433995f71f58e8ad7bf24d4944f [file] [log] [blame]
/*
* Copyright 2013 Google LLC
*
* 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 com.google.auto.factory.processor;
import com.google.auto.value.AutoValue;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeMirror;
/**
* A value object representing a factory to be generated.
*
* @author Gregory Kick
*/
@AutoValue
abstract class FactoryDescriptor {
private static final CharMatcher invalidIdentifierCharacters =
new CharMatcher() {
@Override
public boolean matches(char c) {
return !Character.isJavaIdentifierPart(c);
}
};
abstract PackageAndClass name();
abstract TypeMirror extendingType();
abstract ImmutableSet<TypeMirror> implementingTypes();
abstract boolean publicType();
abstract ImmutableSet<FactoryMethodDescriptor> methodDescriptors();
abstract ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors();
abstract boolean allowSubclasses();
abstract ImmutableMap<Key, ProviderField> providers();
final AutoFactoryDeclaration declaration() {
// There is always at least one method descriptor.
return methodDescriptors().iterator().next().declaration();
}
private static class UniqueNameSet {
private final Set<String> uniqueNames = new HashSet<>();
/**
* Generates a unique name using {@code base}. If {@code base} has not yet been added, it will
* be returned as-is. If your {@code base} is healthy, this will always return {@code base}.
*/
String getUniqueName(CharSequence base) {
String name = base.toString();
for (int differentiator = 2; !uniqueNames.add(name); differentiator++) {
name = base.toString() + differentiator;
}
return name;
}
}
static FactoryDescriptor create(
PackageAndClass name,
TypeMirror extendingType,
ImmutableSet<TypeMirror> implementingTypes,
boolean publicType,
ImmutableSet<FactoryMethodDescriptor> methodDescriptors,
ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors,
boolean allowSubclasses) {
ImmutableSetMultimap.Builder<Key, Parameter> parametersForProviders =
ImmutableSetMultimap.builder();
for (FactoryMethodDescriptor descriptor : methodDescriptors) {
for (Parameter parameter : descriptor.providedParameters()) {
parametersForProviders.put(parameter.key(), parameter);
}
}
ImmutableMap.Builder<Key, ProviderField> providersBuilder = ImmutableMap.builder();
UniqueNameSet uniqueNames = new UniqueNameSet();
parametersForProviders
.build()
.asMap()
.forEach(
(key, parameters) -> {
switch (parameters.size()) {
case 0:
throw new AssertionError();
case 1:
Parameter parameter = Iterables.getOnlyElement(parameters);
providersBuilder.put(
key,
ProviderField.create(
uniqueNames.getUniqueName(parameter.name() + "Provider"),
key,
parameter.nullable()));
break;
default:
String providerName =
uniqueNames.getUniqueName(
invalidIdentifierCharacters.replaceFrom(key.toString(), '_')
+ "Provider");
Optional<AnnotationMirror> nullable =
parameters.stream()
.map(Parameter::nullable)
.flatMap(Streams::stream)
.findFirst();
providersBuilder.put(key, ProviderField.create(providerName, key, nullable));
break;
}
});
ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
duplicateMethodDescriptors =
createDuplicateMethodDescriptorsBiMap(
methodDescriptors, implementationMethodDescriptors);
ImmutableSet<FactoryMethodDescriptor> deduplicatedMethodDescriptors =
getDeduplicatedMethodDescriptors(methodDescriptors, duplicateMethodDescriptors);
ImmutableSet<ImplementationMethodDescriptor> deduplicatedImplementationMethodDescriptors =
Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values())
.immutableCopy();
return new AutoValue_FactoryDescriptor(
name,
extendingType,
implementingTypes,
publicType,
deduplicatedMethodDescriptors,
deduplicatedImplementationMethodDescriptors,
allowSubclasses,
providersBuilder.build());
}
/**
* Creates a bi-map of duplicate {@link ImplementationMethodDescriptor}s by their respective
* {@link FactoryMethodDescriptor}.
*/
private static ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
createDuplicateMethodDescriptorsBiMap(
ImmutableSet<FactoryMethodDescriptor> factoryMethodDescriptors,
ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors) {
ImmutableBiMap.Builder<FactoryMethodDescriptor, ImplementationMethodDescriptor> builder =
ImmutableBiMap.builder();
for (FactoryMethodDescriptor factoryMethodDescriptor : factoryMethodDescriptors) {
for (ImplementationMethodDescriptor implementationMethodDescriptor :
implementationMethodDescriptors) {
boolean areDuplicateMethodDescriptors =
areDuplicateMethodDescriptors(factoryMethodDescriptor, implementationMethodDescriptor);
if (areDuplicateMethodDescriptors) {
builder.put(factoryMethodDescriptor, implementationMethodDescriptor);
break;
}
}
}
return builder.build();
}
/**
* Returns a set of deduplicated {@link FactoryMethodDescriptor}s from the set of original
* descriptors and the bi-map of duplicate descriptors.
*
* <p>Modifies the duplicate {@link FactoryMethodDescriptor}s such that they are overriding and
* reflect properties from the {@link ImplementationMethodDescriptor} they are implementing.
*/
private static ImmutableSet<FactoryMethodDescriptor> getDeduplicatedMethodDescriptors(
ImmutableSet<FactoryMethodDescriptor> methodDescriptors,
ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
duplicateMethodDescriptors) {
ImmutableSet.Builder<FactoryMethodDescriptor> deduplicatedMethodDescriptors =
ImmutableSet.builder();
for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
ImplementationMethodDescriptor duplicateMethodDescriptor =
duplicateMethodDescriptors.get(methodDescriptor);
FactoryMethodDescriptor newMethodDescriptor =
(duplicateMethodDescriptor != null)
? methodDescriptor.toBuilder()
.overridingMethod(true)
.publicMethod(duplicateMethodDescriptor.publicMethod())
.returnType(duplicateMethodDescriptor.returnType())
.exceptions(duplicateMethodDescriptor.exceptions())
.build()
: methodDescriptor;
deduplicatedMethodDescriptors.add(newMethodDescriptor);
}
return deduplicatedMethodDescriptors.build();
}
/**
* Returns true if the given {@link FactoryMethodDescriptor} and
* {@link ImplementationMethodDescriptor} are duplicates.
*
* <p>Descriptors are duplicates if they have the same name and if they have the same passed types
* in the same order.
*/
private static boolean areDuplicateMethodDescriptors(
FactoryMethodDescriptor factory, ImplementationMethodDescriptor implementation) {
if (!factory.name().equals(implementation.name())) {
return false;
}
// Descriptors are identical if they have the same passed types in the same order.
return Iterables.elementsEqual(
Iterables.transform(factory.passedParameters(), Parameter::type),
Iterables.transform(implementation.passedParameters(), Parameter::type));
}
}