Skip to content

Commit

Permalink
reorganize EnvironmentVariableProcessor
Browse files Browse the repository at this point in the history
  • Loading branch information
sigpwned committed Jan 4, 2025
1 parent d6ee135 commit f14c5b5
Showing 1 changed file with 95 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,20 @@ private void processComponent(TypeElement component) {
getMessager().printMessage(Diagnostic.Kind.NOTE,
"Found @Component: " + component.getQualifiedName());

// Analyze the component to find all relevant injection sites.
final List<DaggerInjectionSite> injectionSites = getRelevantInjectionSites(component);
// Analyze the component to find all injection sites decorated with this processor's qualifier
// annotation, @EnvironmentVariable.
final List<DaggerInjectionSite> injectionSites = computeRelevantInjectionSites(component);

// Compute per-parameter metadata. A "parameter" is the logical parameter here, so all injection
// sites that share the same input.
final ParameterMetadataService parameterMetadataService =
createParameterMetadataService(injectionSites);

// Print warnings for any injection sites that are effectively required but are treated as
// Emit warnings for any injection sites that are effectively required but are treated as
// nullable or have a default value.
emitCompilerWarnings(injectionSites, parameterMetadataService);

// Collect all injection sites by representation.
// Determine all the various and sundry representations to generate bindings for
final Set<RepresentationKey> representations =
this.computeRepresentationsToGenerate(injectionSites);

Expand All @@ -147,55 +148,61 @@ private void processComponent(TypeElement component) {
}

/**
* Writes the given source code to a file. The file is created in the same package as the
* component, with the given class name.
*
* @param qualifiedClassName the qualified class name
* @param sourceCode the source code
* Analyzes the given component and returns all relevant injection sites. An injection site is
* considered relevant if it has a qualifier annotation that matches the expected qualifier
* annotation for this processor.
*
* @throws IOException if an I/O error occurs
* @param component the component to analyze
* @return all relevant injection sites
*/
private void writeSourceCode(final String qualifiedClassName, final String sourceCode,
Element... dependentElements) throws IOException {
// TODO Is this the right set of elements?
final JavaFileObject jfo = getFiler().createSourceFile(qualifiedClassName, dependentElements);
try (Writer w = jfo.openWriter()) {
w.write(sourceCode);
}
private List<DaggerInjectionSite> computeRelevantInjectionSites(TypeElement component) {
return new DaggerComponentAnalyzer(getProcessingEnv())
.analyzeComponent(component).getInjectionSites().stream()
.filter(d -> d.getQualifier().isPresent()).filter(d -> getTypes()
.isSameType(d.getQualifier().orElseThrow().getAnnotationType(), getQualifierType()))
.collect(toList());
}

/**
* Determine the unique set of representations that need to be generated. This is the union of all
* representations in the given injection sites, plus any additional representations that are
* required to support the conversion of the representation to code, e.g., a string
* representation.
*
* @param injectionSites the injection sites
* @return the set of representations to generate
*/
private Set<RepresentationKey> computeRepresentationsToGenerate(
@FunctionalInterface
public static interface ParameterMetadataService {
public ParameterMetadata getParameterMetadata(ParameterKey key);
}

private ParameterMetadataService createParameterMetadataService(
List<DaggerInjectionSite> injectionSites) {
final Set<RepresentationKey> representations = injectionSites.stream()
.map(dis -> RepresentationKey.fromInjectionSite(dis)).collect(toCollection(HashSet::new));
final Map<ParameterKey, List<DaggerInjectionSite>> injectionSitesByParameter =
injectionSites.stream().map(dis -> Map.entry(ParameterKey.fromInjectionSite(dis), dis))
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));

for (RepresentationKey representation : MoreSets.copyOf(representations)) {
if (getTypes().isSameType(representation.getType(), getStringType()))
continue;
final Map<ParameterKey, ParameterMetadata> metadataForParameters = new HashMap<>();
for (Map.Entry<ParameterKey, List<DaggerInjectionSite>> entry : injectionSitesByParameter
.entrySet()) {
final ParameterKey key = entry.getKey();
final List<DaggerInjectionSite> parameterInjectionSites = entry.getValue();

final RepresentationKey representationAsString = toStringRepresentation(representation);
// Group injection sites by requiredness
final Map<Boolean, List<DaggerInjectionSite>> injectionSitesByRequired =
parameterInjectionSites.stream().collect(Collectors.groupingBy(
dis -> EnvironmentVariables.isRequired(dis.isNullable(),
EnvironmentVariables
.extractEnvironmentVariableDefaultValue(dis.getQualifier().orElseThrow())),
toList()));

if (!representations.contains(representationAsString))
representations.add(representationAsString);
}
// Check for conflicting requiredness
if (injectionSitesByRequired.size() > 1) {
getMessager().printMessage(Diagnostic.Kind.WARNING,
"Conflicting requiredness for environment variable " + key.getName()
+ ", will be treated as required");
// TODO Print the conflicting dependencies, with element and annotation references
}

return unmodifiableSet(representations);
}
// Determine if the parameter is required
final boolean required = injectionSitesByRequired.keySet().stream().anyMatch(b -> b == true);

private static final Comparator<RepresentationKey> REPRESENTATION_KEY_COMPARATOR =
Comparator.comparing(RepresentationKey::getName)
.thenComparing(Comparator.comparing(x -> x.getDefaultValue().orElse(null),
Comparator.nullsFirst(Comparator.naturalOrder())))
.thenComparing(x -> x.getType().toString());
metadataForParameters.put(key, new ParameterMetadata(required));
}
return metadataForParameters::get;
}

/**
* Prints compiler warnings for injection sites with local constraints that do not match global
Expand Down Expand Up @@ -244,62 +251,38 @@ private void emitCompilerWarnings(List<DaggerInjectionSite> injectionSites,
}

/**
* Analyzes the given component and returns all relevant injection sites. An injection site is
* considered relevant if it has a qualifier annotation that matches the expected qualifier
* annotation for this processor.
* Determine the unique set of representations that need to be generated. This is the union of all
* representations in the given injection sites, plus any additional representations that are
* required to support the conversion of the representation to code, e.g., a string
* representation.
*
* @param component the component to analyze
* @return all relevant injection sites
* @param injectionSites the injection sites
* @return the set of representations to generate
*/
private List<DaggerInjectionSite> getRelevantInjectionSites(TypeElement component) {
return new DaggerComponentAnalyzer(getProcessingEnv())
.analyzeComponent(component).getInjectionSites().stream()
.filter(d -> d.getQualifier().isPresent()).filter(d -> getTypes()
.isSameType(d.getQualifier().orElseThrow().getAnnotationType(), getQualifierType()))
.collect(toList());
}

private ParameterMetadataService createParameterMetadataService(
private Set<RepresentationKey> computeRepresentationsToGenerate(
List<DaggerInjectionSite> injectionSites) {
final Map<ParameterKey, List<DaggerInjectionSite>> injectionSitesByParameter =
injectionSites.stream().map(dis -> Map.entry(ParameterKey.fromInjectionSite(dis), dis))
.collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));

final Map<ParameterKey, ParameterMetadata> metadataForParameters = new HashMap<>();
for (Map.Entry<ParameterKey, List<DaggerInjectionSite>> entry : injectionSitesByParameter
.entrySet()) {
final ParameterKey key = entry.getKey();
final List<DaggerInjectionSite> parameterInjectionSites = entry.getValue();

// Group injection sites by requiredness
final Map<Boolean, List<DaggerInjectionSite>> injectionSitesByRequired =
parameterInjectionSites.stream().collect(Collectors.groupingBy(
dis -> EnvironmentVariables.isRequired(dis.isNullable(),
EnvironmentVariables
.extractEnvironmentVariableDefaultValue(dis.getQualifier().orElseThrow())),
toList()));
final Set<RepresentationKey> representations = injectionSites.stream()
.map(dis -> RepresentationKey.fromInjectionSite(dis)).collect(toCollection(HashSet::new));

// Check for conflicting requiredness
if (injectionSitesByRequired.size() > 1) {
getMessager().printMessage(Diagnostic.Kind.WARNING,
"Conflicting requiredness for environment variable " + key.getName()
+ ", will be treated as required");
// TODO Print the conflicting dependencies, with element and annotation references
}
for (RepresentationKey representation : MoreSets.copyOf(representations)) {
if (getTypes().isSameType(representation.getType(), getStringType()))
continue;

// Determine if the parameter is required
final boolean required = injectionSitesByRequired.keySet().stream().anyMatch(b -> b == true);
final RepresentationKey representationAsString = toStringRepresentation(representation);

metadataForParameters.put(key, new ParameterMetadata(required));
if (!representations.contains(representationAsString))
representations.add(representationAsString);
}
return metadataForParameters::get;
}

@FunctionalInterface
public static interface ParameterMetadataService {
public ParameterMetadata getParameterMetadata(ParameterKey key);
return unmodifiableSet(representations);
}

private static final Comparator<RepresentationKey> REPRESENTATION_KEY_COMPARATOR =
Comparator.comparing(RepresentationKey::getName)
.thenComparing(Comparator.comparing(x -> x.getDefaultValue().orElse(null),
Comparator.nullsFirst(Comparator.naturalOrder())))
.thenComparing(x -> x.getType().toString());

/**
* Generates the source code for the given representations.
*
Expand Down Expand Up @@ -459,6 +442,29 @@ private String generateSourceCode(String componentPackageName, String moduleClas
return result.toString();
}

/**
* Writes the given source code to a file. The file is created in the same package as the
* component, with the given class name.
*
* @param qualifiedClassName the qualified class name
* @param sourceCode the source code
*
* @throws IOException if an I/O error occurs
*/
private void writeSourceCode(final String qualifiedClassName, final String sourceCode,
Element... dependentElements) throws IOException {
// TODO Is this the right set of elements?
final JavaFileObject jfo = getFiler().createSourceFile(qualifiedClassName, dependentElements);
try (Writer w = jfo.openWriter()) {
w.write(sourceCode);
}
}

private RepresentationKey toStringRepresentation(RepresentationKey key) {
return new RepresentationKey(getStringType(), key.getName(),
key.getDefaultValue().orElse(null));
}

private transient TypeMirror qualifierType;

private TypeMirror getQualifierType() {
Expand All @@ -468,11 +474,6 @@ private TypeMirror getQualifierType() {
return qualifierType;
}

private RepresentationKey toStringRepresentation(RepresentationKey key) {
return new RepresentationKey(getStringType(), key.getName(),
key.getDefaultValue().orElse(null));
}

private transient TypeMirror stringType;

private TypeMirror getStringType() {
Expand Down

0 comments on commit f14c5b5

Please sign in to comment.