diff --git a/rapier-processor-environment-variable/src/main/java/rapier/processor/envvar/EnvironmentVariableProcessor.java b/rapier-processor-environment-variable/src/main/java/rapier/processor/envvar/EnvironmentVariableProcessor.java index 92872b1..798caae 100644 --- a/rapier-processor-environment-variable/src/main/java/rapier/processor/envvar/EnvironmentVariableProcessor.java +++ b/rapier-processor-environment-variable/src/main/java/rapier/processor/envvar/EnvironmentVariableProcessor.java @@ -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 injectionSites = getRelevantInjectionSites(component); + // Analyze the component to find all injection sites decorated with this processor's qualifier + // annotation, @EnvironmentVariable. + final List 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 representations = this.computeRepresentationsToGenerate(injectionSites); @@ -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 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 computeRepresentationsToGenerate( + @FunctionalInterface + public static interface ParameterMetadataService { + public ParameterMetadata getParameterMetadata(ParameterKey key); + } + + private ParameterMetadataService createParameterMetadataService( List injectionSites) { - final Set representations = injectionSites.stream() - .map(dis -> RepresentationKey.fromInjectionSite(dis)).collect(toCollection(HashSet::new)); + final Map> 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 metadataForParameters = new HashMap<>(); + for (Map.Entry> entry : injectionSitesByParameter + .entrySet()) { + final ParameterKey key = entry.getKey(); + final List parameterInjectionSites = entry.getValue(); - final RepresentationKey representationAsString = toStringRepresentation(representation); + // Group injection sites by requiredness + final Map> 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 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 @@ -244,62 +251,38 @@ private void emitCompilerWarnings(List 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 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 computeRepresentationsToGenerate( List injectionSites) { - final Map> injectionSitesByParameter = - injectionSites.stream().map(dis -> Map.entry(ParameterKey.fromInjectionSite(dis), dis)) - .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList()))); - - final Map metadataForParameters = new HashMap<>(); - for (Map.Entry> entry : injectionSitesByParameter - .entrySet()) { - final ParameterKey key = entry.getKey(); - final List parameterInjectionSites = entry.getValue(); - - // Group injection sites by requiredness - final Map> injectionSitesByRequired = - parameterInjectionSites.stream().collect(Collectors.groupingBy( - dis -> EnvironmentVariables.isRequired(dis.isNullable(), - EnvironmentVariables - .extractEnvironmentVariableDefaultValue(dis.getQualifier().orElseThrow())), - toList())); + final Set 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 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. * @@ -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() { @@ -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() {