diff --git a/pico/processor/pom.xml b/pico/processor/pom.xml index 1316255e2d7..91988a258c3 100644 --- a/pico/processor/pom.xml +++ b/pico/processor/pom.xml @@ -57,6 +57,11 @@ hamcrest-all test + + org.mockito + mockito-core + test + diff --git a/pico/processor/src/main/java/io/helidon/pico/processor/ActiveProcessorUtils.java b/pico/processor/src/main/java/io/helidon/pico/processor/ActiveProcessorUtils.java index d066cd3e666..d82515ca722 100644 --- a/pico/processor/src/main/java/io/helidon/pico/processor/ActiveProcessorUtils.java +++ b/pico/processor/src/main/java/io/helidon/pico/processor/ActiveProcessorUtils.java @@ -117,7 +117,10 @@ public void error(String message, out(System.Logger.Level.ERROR, Diagnostic.Kind.ERROR, message, null); } - void out(System.Logger.Level level, Diagnostic.Kind kind, String message, Throwable t) { + void out(System.Logger.Level level, + Diagnostic.Kind kind, + String message, + Throwable t) { if (logger.isLoggable(level)) { logger.log(level, getClass().getSimpleName() + ": " + message, t); } @@ -191,10 +194,6 @@ Optional toTypeInfo(TypeElement element, return typeInfoCreatorProvider.createTypeInfo(element, mirror, processingEnv, isOneWeCareAbout); } - System.Logger.Level loggerLevel() { - return (Options.isOptionEnabled(Options.TAG_DEBUG)) ? System.Logger.Level.INFO : System.Logger.Level.DEBUG; - } - RoundEnvironment roundEnv() { return roundEnv; } diff --git a/pico/processor/src/main/java/io/helidon/pico/processor/CustomAnnotationProcessor.java b/pico/processor/src/main/java/io/helidon/pico/processor/CustomAnnotationProcessor.java index 3ba0a81953e..b6b5bf93bca 100644 --- a/pico/processor/src/main/java/io/helidon/pico/processor/CustomAnnotationProcessor.java +++ b/pico/processor/src/main/java/io/helidon/pico/processor/CustomAnnotationProcessor.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -77,9 +78,7 @@ public CustomAnnotationProcessor() { } static List initialize() { - // note: it is important to use this class' CL since maven will not give us the "right" one. - List creators = HelidonServiceLoader.create(ServiceLoader.load( - CustomAnnotationTemplateCreator.class, CustomAnnotationTemplateCreator.class.getClassLoader())).asList(); + List creators = HelidonServiceLoader.create(loader()).asList(); creators.forEach(creator -> { try { Set annoTypes = creator.annoTypes(); @@ -260,6 +259,19 @@ List toArgs(Element typeToProcess) { return result; } + private static ServiceLoader loader() { + try { + // note: it is important to use this class' CL since maven will not give us the "right" one. + return ServiceLoader.load( + CustomAnnotationTemplateCreator.class, CustomAnnotationTemplateCreator.class.getClassLoader()); + } catch (ServiceConfigurationError e) { + // see issue #6261 - running inside the IDE? + // this version will use the thread ctx classloader + System.getLogger(CustomAnnotationProcessor.class.getName()).log(System.Logger.Level.WARNING, e.getMessage(), e); + return ServiceLoader.load(CustomAnnotationTemplateCreator.class); + } + } + private static TypeElement toEnclosingClassTypeElement(Element typeToProcess) { while (typeToProcess != null && !(typeToProcess instanceof TypeElement)) { typeToProcess = typeToProcess.getEnclosingElement(); diff --git a/pico/processor/src/main/java/io/helidon/pico/processor/PicoAnnotationProcessor.java b/pico/processor/src/main/java/io/helidon/pico/processor/PicoAnnotationProcessor.java index f7132da064a..54f7f30590c 100644 --- a/pico/processor/src/main/java/io/helidon/pico/processor/PicoAnnotationProcessor.java +++ b/pico/processor/src/main/java/io/helidon/pico/processor/PicoAnnotationProcessor.java @@ -16,6 +16,8 @@ package io.helidon.pico.processor; +import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -40,6 +42,7 @@ import io.helidon.common.types.AnnotationAndValueDefault; import io.helidon.common.types.TypeInfo; import io.helidon.common.types.TypeName; +import io.helidon.common.types.TypeNameDefault; import io.helidon.common.types.TypedElementInfo; import io.helidon.pico.api.Activator; import io.helidon.pico.api.Contract; @@ -51,9 +54,11 @@ import io.helidon.pico.api.ServiceInfoBasics; import io.helidon.pico.runtime.Dependencies; import io.helidon.pico.tools.ActivatorCreatorCodeGen; +import io.helidon.pico.tools.ActivatorCreatorCodeGenDefault; import io.helidon.pico.tools.ActivatorCreatorConfigOptionsDefault; import io.helidon.pico.tools.ActivatorCreatorDefault; import io.helidon.pico.tools.ActivatorCreatorRequest; +import io.helidon.pico.tools.ActivatorCreatorRequestDefault; import io.helidon.pico.tools.ActivatorCreatorResponse; import io.helidon.pico.tools.InterceptionPlan; import io.helidon.pico.tools.InterceptorCreatorProvider; @@ -68,6 +73,7 @@ import jakarta.annotation.PreDestroy; import jakarta.inject.Inject; +import static io.helidon.builder.processor.tools.BeanUtils.isBuiltInJavaType; import static io.helidon.builder.processor.tools.BuilderTypeTools.createTypeNameFromElement; import static io.helidon.common.types.TypeNameDefault.createFromTypeName; import static io.helidon.pico.processor.ActiveProcessorUtils.MAYBE_ANNOTATIONS_CLAIMED_BY_THIS_PROCESSOR; @@ -81,6 +87,10 @@ import static io.helidon.pico.processor.GeneralProcessorUtils.toScopeNames; import static io.helidon.pico.processor.GeneralProcessorUtils.toServiceTypeHierarchy; import static io.helidon.pico.processor.GeneralProcessorUtils.toWeight; +import static io.helidon.pico.processor.ProcessingTracker.DEFAULT_SCRATCH_FILE_NAME; +import static io.helidon.pico.processor.ProcessingTracker.initializeFrom; +import static io.helidon.pico.tools.CodeGenFiler.scratchClassOutputPath; +import static io.helidon.pico.tools.CodeGenFiler.targetClassOutputPath; import static io.helidon.pico.tools.TypeTools.createTypedElementInfoFromElement; import static io.helidon.pico.tools.TypeTools.toAccess; import static java.util.Objects.requireNonNull; @@ -112,6 +122,7 @@ public class PicoAnnotationProcessor extends BaseAnnotationProcessor { private final Set allElementsOfInterestInThisModule = new LinkedHashSet<>(); private final Map typeInfoToCreateActivatorsForInThisModule = new LinkedHashMap<>(); + private ProcessingTracker tracker; private CreatorHandler creator; private boolean autoAddInterfaces; @@ -149,10 +160,7 @@ public void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.autoAddInterfaces = Options.isOptionEnabled(Options.TAG_AUTO_ADD_NON_CONTRACT_INTERFACES); this.creator = new CreatorHandler(getClass().getSimpleName(), processingEnv, utils()); -// if (BaseAnnotationProcessor.ENABLED) { -// // we are is simulation mode when the base one is operating... -// this.creator.activateSimulationMode(); -// } + this.tracker = initializeFrom(trackerStatePath(), processingEnv); } @Override @@ -165,7 +173,6 @@ public boolean process(Set annotations, } ServicesToProcess.onBeginProcessing(utils(), getSupportedAnnotationTypes(), roundEnv); -// ServicesToProcess.addOnDoneRunnable(CreatorHandler.reporting()); try { // build the model @@ -243,6 +250,7 @@ protected Set supportedElementTargetAnnotations() { * Code generate these {@link io.helidon.pico.api.Activator}'s ad {@link io.helidon.pico.api.ModuleComponent}'s. * * @param services the services to code generate + * @throws ToolsException if there is problem code generating sources or resources */ protected void doFiler(ServicesToProcess services) { ActivatorCreatorCodeGen codeGen = ActivatorCreatorDefault.createActivatorCreatorCodeGen(services).orElse(null); @@ -257,8 +265,28 @@ protected void doFiler(ServicesToProcess services) { .build(); ActivatorCreatorRequest req = ActivatorCreatorDefault .createActivatorCreatorRequest(services, codeGen, configOptions, creator.filer(), false); + Set allActivatorTypeNames = tracker.remainingTypeNames().stream() + .map(TypeNameDefault::createFromTypeName) + .collect(Collectors.toSet()); + if (!allActivatorTypeNames.isEmpty()) { + req = ActivatorCreatorRequestDefault.toBuilder(req) + .codeGen(ActivatorCreatorCodeGenDefault.toBuilder(req.codeGen()) + .allModuleActivatorTypeNames(allActivatorTypeNames) + .build()) + .build(); + } ActivatorCreatorResponse res = creator.createModuleActivators(req); - if (!res.success()) { + if (res.success()) { + res.activatorTypeNamesPutInComponentModule() + .forEach(it -> tracker.processing(it.name())); + if (processingOver) { + try { + tracker.close(); + } catch (IOException e) { + throw new ToolsException(e.getMessage(), e); + } + } + } else { ToolsException exc = new ToolsException("Error during codegen", res.error().orElse(null)); utils().error(exc.getMessage(), exc); // should not get here since the error above should halt further processing @@ -504,14 +532,11 @@ private void gatherContracts(Set contracts, if (fqProviderTypeName != null) { if (!genericTypeName.generic()) { providerForSet.add(genericTypeName); - - Optional moduleName = filterModuleName(typeInfo.moduleNameOf(genericTypeName)); - moduleName.ifPresent(externalModuleNamesRequired::add); - if (moduleName.isPresent()) { - externalContracts.add(genericTypeName); - } else { - contracts.add(genericTypeName); - } + extractModuleAndContract(contracts, + externalContracts, + externalModuleNamesRequired, + typeInfo, + genericTypeName); } // if we are dealing with a Provider<> then we should add those too as module dependencies @@ -529,13 +554,11 @@ private void gatherContracts(Set contracts, || !isTypeAnInterface || AnnotationAndValueDefault.findFirst(Contract.class, typeInfo.annotations()).isPresent(); if (isTypeAContract) { - Optional moduleName = filterModuleName(typeInfo.moduleNameOf(genericTypeName)); - moduleName.ifPresent(externalModuleNamesRequired::add); - if (moduleName.isPresent()) { - externalContracts.add(genericTypeName); - } else { - contracts.add(genericTypeName); - } + extractModuleAndContract(contracts, + externalContracts, + externalModuleNamesRequired, + typeInfo, + genericTypeName); } } } @@ -574,6 +597,20 @@ private void gatherContracts(Set contracts, true)); } + private void extractModuleAndContract(Set contracts, + Set externalContracts, + Set externalModuleNamesRequired, + TypeInfo typeInfo, + TypeName genericTypeName) { + Optional moduleName = filterModuleName(typeInfo.moduleNameOf(genericTypeName)); + moduleName.ifPresent(externalModuleNamesRequired::add); + if (moduleName.isPresent() || isBuiltInJavaType(genericTypeName)) { + externalContracts.add(genericTypeName); + } else { + contracts.add(genericTypeName); + } + } + private Optional filterModuleName(Optional moduleName) { String name = moduleName.orElse(null); if (name != null && (name.startsWith("java.") || name.startsWith("jdk"))) { @@ -727,4 +764,8 @@ private void gatherTypeInfosToProcessInThisModule(Map result }); } + private Path trackerStatePath() { + return scratchClassOutputPath(targetClassOutputPath(processingEnv.getFiler())).resolve(DEFAULT_SCRATCH_FILE_NAME); + } + } diff --git a/pico/processor/src/main/java/io/helidon/pico/processor/ProcessingTracker.java b/pico/processor/src/main/java/io/helidon/pico/processor/ProcessingTracker.java new file mode 100644 index 00000000000..3bad1593ac2 --- /dev/null +++ b/pico/processor/src/main/java/io/helidon/pico/processor/ProcessingTracker.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * 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 io.helidon.pico.processor; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +import io.helidon.pico.tools.ToolsException; + +/** + * This class adds persistent tracking (typically under ./target/XXX) to allow seamless full and/or incremental processing of + * types to be tracked over repeated compilation cycles over time. It is expected to be integrated into a host annotation + * processor implementation. + *

+ * For example, when incremental processing occurs, the elements passed to process in all rounds will just be a subset of + * all of the annotated services since the compiler (from the IDE) only recompiles the files that have been changed. This is + * typically different from how maven invokes compilation (doing a full compile where all types will be seen in the round). The + * {@link PicoAnnotationProcessor}, for example, would see this reduced subset of types in the round and would otherwise have + * created a {@link io.helidon.pico.api.ModuleComponent} only representative of the reduced subset of classes. This would be + * incorrect and lead to an invalid module component source file to have been generated. + *

+ * We use this tracker to persist the list of generated activators much in the same way that + * {@code META-INF/services} are tracked. A target scratch directory (i.e., target/pico in this case) is used instead - in order + * to keep it out of the build jar. + *

+ * Usage: + *

    + *
  1. {@link #initializeFrom} - during the APT initialization phase
  2. + *
  3. {@link #processing(String)} - during each processed type that the annotation processor visits in the round
  4. + *
  5. {@link #removedTypeNames()} or {@link #remainingTypeNames()} as needed - to see the changes over time
  6. + *
  7. {@link #close()} - during final lifecycle of the APT in order to persist state to be (re)written out to disk
  8. + *
+ * + * @see PicoAnnotationProcessor + */ +class ProcessingTracker implements AutoCloseable { + static final String DEFAULT_SCRATCH_FILE_NAME = "activators.lst"; + + private final Path path; + private final Set allTypeNames; + private final TypeElementFinder typeElementFinder; + private final Set foundOrProcessed = new LinkedHashSet<>(); + + /** + * Creates an instance using the given path to keep persistent state. + * + * @param persistentScratchPath the fully qualified path to carry the state + * @param allLines all lines read at initialization + * @param typeElementFinder the type element finder (e.g., {@link ProcessingEnvironment#getElementUtils}) + */ + ProcessingTracker(Path persistentScratchPath, + List allLines, + TypeElementFinder typeElementFinder) { + this.path = persistentScratchPath; + this.allTypeNames = new LinkedHashSet<>(allLines); + this.typeElementFinder = typeElementFinder; + } + + public static ProcessingTracker initializeFrom(Path persistentScratchPath, + ProcessingEnvironment processingEnv) { + List allLines = List.of(); + File file = persistentScratchPath.toFile(); + if (file.exists() && file.canRead()) { + try { + allLines = Files.readAllLines(persistentScratchPath, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new ToolsException(e.getMessage(), e); + } + } + return new ProcessingTracker(persistentScratchPath, allLines, toTypeElementFinder(processingEnv)); + } + + public ProcessingTracker processing(String typeName) { + foundOrProcessed.add(Objects.requireNonNull(typeName)); + return this; + } + + public Set allTypeNamesFromInitialization() { + return allTypeNames; + } + + public Set removedTypeNames() { + Set typeNames = new LinkedHashSet<>(allTypeNamesFromInitialization()); + typeNames.removeAll(remainingTypeNames()); + return typeNames; + } + + public Set remainingTypeNames() { + Set typeNames = new LinkedHashSet<>(allTypeNamesFromInitialization()); + typeNames.addAll(foundOrProcessed); + typeNames.removeIf(typeName -> !found(typeName)); + return typeNames; + } + + @Override + public void close() throws IOException { + Path parent = path.getParent(); + if (parent == null) { + throw new ToolsException("bad path: " + path); + } + Files.createDirectories(parent); + Files.write(path, remainingTypeNames(), StandardCharsets.UTF_8); + } + + private boolean found(String typeName) { + return (typeElementFinder.apply(typeName) != null); + } + + private static TypeElementFinder toTypeElementFinder(ProcessingEnvironment processingEnv) { + return typeName -> processingEnv.getElementUtils().getTypeElement(typeName); + } + + @FunctionalInterface + interface TypeElementFinder extends Function { + } + +} diff --git a/pico/processor/src/test/java/io/helidon/pico/processor/ProcessingTrackerTest.java b/pico/processor/src/test/java/io/helidon/pico/processor/ProcessingTrackerTest.java new file mode 100644 index 00000000000..c06928094eb --- /dev/null +++ b/pico/processor/src/test/java/io/helidon/pico/processor/ProcessingTrackerTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * 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 io.helidon.pico.processor; + +import java.util.List; + +import javax.lang.model.element.TypeElement; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.mockito.Mockito.mock; + +class ProcessingTrackerTest { + + @Test + void noDelta() { + List typeNames = List.of("a", "b", "c"); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> mock(TypeElement.class)); + assertThat(tracker.removedTypeNames().size(), + is(0)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "b", "c")); + } + + @Test + void incrementalCompilation() { + List typeNames = List.of("a", "b", "c"); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> mock(TypeElement.class)); + tracker.processing("b"); + + assertThat(tracker.removedTypeNames().size(), + is(0)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "b", "c")); + } + + @Test + void incrementalCompilationWithFilesRemoved() { + List typeNames = List.of("a", "b", "c"); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> (typeName.equals("b") ? null : mock(TypeElement.class))); + + assertThat(tracker.removedTypeNames().size(), + is(1)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "c")); + } + + @Test + void incrementalCompilationWithFilesAddedAndRemoved() { + List typeNames = List.of("a"); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> mock(TypeElement.class)); + tracker.processing("b"); + tracker.processing("a"); + + assertThat(tracker.removedTypeNames().size(), + is(0)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "b")); + } + + @Test + void cleanCompilation() { + List typeNames = List.of(); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> mock(TypeElement.class)); + tracker.processing("a"); + tracker.processing("b"); + tracker.processing("c"); + + assertThat(tracker.removedTypeNames().size(), + is(0)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "b", "c")); + } + + @Test + void fullCompilationWithFilesAdded() { + List typeNames = List.of("a"); + ProcessingTracker tracker = new ProcessingTracker(null, typeNames, + typeName -> mock(TypeElement.class)); + tracker.processing("a"); + tracker.processing("b"); + tracker.processing("c"); + + assertThat(tracker.removedTypeNames().size(), + is(0)); + assertThat(tracker.remainingTypeNames(), + containsInAnyOrder("a", "b", "c")); + } + +} diff --git a/pico/tests/interception/src/main/java/io/helidon/pico/tests/interception/TheOtherService.java b/pico/tests/interception/src/main/java/io/helidon/pico/tests/interception/TheOtherService.java index 3690106f14c..f9863ab8f33 100644 --- a/pico/tests/interception/src/main/java/io/helidon/pico/tests/interception/TheOtherService.java +++ b/pico/tests/interception/src/main/java/io/helidon/pico/tests/interception/TheOtherService.java @@ -19,7 +19,7 @@ import jakarta.inject.Singleton; @Singleton -class TheOtherService implements OtherContract{ +class TheOtherService implements OtherContract { private boolean throwException; @Modify diff --git a/pico/tests/resources-pico/pom.xml b/pico/tests/resources-pico/pom.xml index f2234d1b4a6..7f7d185cf5a 100644 --- a/pico/tests/resources-pico/pom.xml +++ b/pico/tests/resources-pico/pom.xml @@ -94,7 +94,7 @@ -Apico.autoAddNonContractInterfaces=true -Apico.allowListedInterceptorAnnotations=io.helidon.pico.tests.pico.interceptor.TestNamed - -Apico.application.pre.create=true + -Apico.application.pre.create=false -Apico.mapApplicationToSingletonScope=true -Apico.debug=${pico.debug} @@ -145,10 +145,7 @@ -Apico.debug=${pico.debug} -Apico.autoAddNonContractInterfaces=true - -Apico.application.pre.create=true - - - + -Apico.application.pre.create=false NAMED diff --git a/pico/tests/resources-pico/src/main/java/module-info.java b/pico/tests/resources-pico/src/main/java/module-info.java index 96a9a0ac8e8..714aeae2c47 100644 --- a/pico/tests/resources-pico/src/main/java/module-info.java +++ b/pico/tests/resources-pico/src/main/java/module-info.java @@ -33,5 +33,4 @@ exports io.helidon.pico.tests.pico.tbox; provides io.helidon.pico.api.ModuleComponent with io.helidon.pico.tests.pico.Pico$$Module; - provides io.helidon.pico.api.Application with io.helidon.pico.tests.pico.Pico$$Application; } diff --git a/pico/tests/resources-pico/src/test/resources/expected/module-info.java._pico_ b/pico/tests/resources-pico/src/test/resources/expected/module-info.java._pico_ index 32608ecea9b..b156cd28f92 100644 --- a/pico/tests/resources-pico/src/test/resources/expected/module-info.java._pico_ +++ b/pico/tests/resources-pico/src/test/resources/expected/module-info.java._pico_ @@ -33,7 +33,6 @@ module io.helidon.pico.tests.pico { exports io.helidon.pico.tests.pico.tbox; provides io.helidon.pico.api.ModuleComponent with io.helidon.pico.tests.pico.Pico$$Module; - provides io.helidon.pico.api.Application with io.helidon.pico.tests.pico.Pico$$Application; // pico external contract usage - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") requires test1; requires test2; @@ -44,4 +43,6 @@ module io.helidon.pico.tests.pico { uses io.helidon.pico.api.OptionallyNamed; // pico contract usage - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") exports io.helidon.pico.tests.pico.provider; + // pico application - Generated(value = "io.helidon.pico.tools.ApplicationCreatorDefault", comments = "version=1") + provides io.helidon.pico.api.Application with io.helidon.pico.tests.pico.Pico$$Application; } diff --git a/pico/tests/resources-pico/src/test/resources/expected/tests-module-info.java._pico_ b/pico/tests/resources-pico/src/test/resources/expected/tests-module-info.java._pico_ index 6aef3a52f72..52b16aea6cc 100644 --- a/pico/tests/resources-pico/src/test/resources/expected/tests-module-info.java._pico_ +++ b/pico/tests/resources-pico/src/test/resources/expected/tests-module-info.java._pico_ @@ -3,12 +3,12 @@ module io.helidon.pico.tests.pico/test { exports io.helidon.pico.tests.pico; // pico module - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") provides io.helidon.pico.api.ModuleComponent with io.helidon.pico.tests.pico.Pico$$TestModule; - // pico application - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") - provides io.helidon.pico.api.Application with io.helidon.pico.tests.pico.Pico$$TestApplication; // pico external contract usage - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") uses io.helidon.pico.api.Resettable; uses io.helidon.pico.tests.pico.stacking.Intercepted; uses io.helidon.pico.tests.pico.stacking.InterceptedImpl; // pico services - Generated(value = "io.helidon.pico.tools.ActivatorCreatorDefault", comments = "version=1") requires transitive io.helidon.pico.runtime; + // pico application - Generated(value = "io.helidon.pico.tools.ApplicationCreatorDefault", comments = "version=1") + provides io.helidon.pico.api.Application with io.helidon.pico.tests.pico.Pico$$TestApplication; } diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorCodeGen.java b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorCodeGen.java index 01c2d200d62..580dc95c986 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorCodeGen.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorCodeGen.java @@ -188,4 +188,13 @@ public interface ActivatorCreatorCodeGen { @ConfiguredOption(DEFAULT_CLASS_PREFIX_NAME) String classPrefixName(); + /** + * Used in conjunction with {@link ActivatorCreatorConfigOptions#isModuleCreated()}. If a module is created and this set is + * populated then this set will be used to represent all {@link io.helidon.pico.api.Activator} type names that should be code + * generated for this {@link io.helidon.pico.api.ModuleComponent}. + * + * @return all module activator type names known for this given module being processed + */ + Set allModuleActivatorTypeNames(); + } diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorDefault.java b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorDefault.java index f2fc6069626..7bf8bbff38e 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorDefault.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorDefault.java @@ -27,6 +27,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -139,7 +140,7 @@ ActivatorCreatorResponse codegen(ActivatorCreatorRequest req, CodeGenPaths codeGenPaths = req.codeGenPaths().orElse(null); Map serviceTypeToIsAbstractType = req.codeGen().serviceTypeIsAbstractTypes(); List activatorTypeNames = new ArrayList<>(); - List activatorTypeNamesPutInModule = new ArrayList<>(); + Set activatorTypeNamesPutInModule = new TreeSet<>(req.codeGen().allModuleActivatorTypeNames()); Map activatorDetails = new LinkedHashMap<>(); for (TypeName serviceTypeName : req.serviceTypeNames()) { try { @@ -164,6 +165,7 @@ ActivatorCreatorResponse codegen(ActivatorCreatorRequest req, } } builder.serviceTypeNames(activatorTypeNames) + .activatorTypeNamesPutInComponentModule(activatorTypeNamesPutInModule) .serviceTypeDetails(activatorDetails); ModuleDetail moduleDetail; @@ -207,7 +209,7 @@ ActivatorCreatorResponse codegen(ActivatorCreatorRequest req, } private ModuleDetail toModuleDetail(ActivatorCreatorRequest req, - List activatorTypeNamesPutInModule, + Set activatorTypeNamesPutInModule, TypeName moduleTypeName, TypeName applicationTypeName, boolean isApplicationCreated, @@ -628,7 +630,7 @@ String toModuleBody(ActivatorCreatorRequest req, String packageName, String className, String moduleName, - List activatorTypeNames) { + Set activatorTypeNames) { String template = templateHelper().safeLoadTemplate(req.templateName(), SERVICE_PROVIDER_MODULE_HBS); Map subst = new HashMap<>(); diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorResponse.java b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorResponse.java index 5bc1c06b027..319ec8a87b9 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorResponse.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/ActivatorCreatorResponse.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Optional; +import java.util.Set; import io.helidon.builder.Builder; import io.helidon.builder.Singular; @@ -37,13 +38,20 @@ public interface ActivatorCreatorResponse extends GeneralCreatorResponse { ActivatorCreatorConfigOptions getConfigOptions(); /** - * return The interceptors that were generated. + * Return the interceptors that were generated. * * @return interceptors generated */ @Singular Map serviceTypeInterceptorPlans(); + /** + * The activator types placed in the generated {@link io.helidon.pico.api.ModuleComponent}. + * + * @return the activator type names placed in the module component + */ + Set activatorTypeNamesPutInComponentModule(); + /** * The module-info detail, if a module was created. * diff --git a/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java b/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java index 950e1fdbad8..76000e05c78 100644 --- a/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java +++ b/pico/tools/src/main/java/io/helidon/pico/tools/CodeGenFiler.java @@ -191,7 +191,13 @@ public void codegenMetaInfServices(CodeGenPaths paths, } } - private static Path targetClassOutputPath(Filer filer) { + /** + * Returns the target class output directory. + * + * @param filer the filer + * @return the path to the target class output directory + */ + public static Path targetClassOutputPath(Filer filer) { if (filer instanceof AbstractFilerMessager.DirectFilerMessager) { CodeGenPaths paths = ((AbstractFilerMessager.DirectFilerMessager) filer).codeGenPaths(); return Path.of(paths.outputPath().orElseThrow()); @@ -209,7 +215,13 @@ private static Path targetClassOutputPath(Filer filer) { } } - private static Path scratchClassOutputPath(Path targetOutputPath) { + /** + * Returns the path to the target scratch directory for Pico. + * + * @param targetOutputPath the target class output path + * @return the pico target scratch path + */ + public static Path scratchClassOutputPath(Path targetOutputPath) { Path fileName = targetOutputPath.getFileName(); Path parent = targetOutputPath.getParent(); if (fileName == null || parent == null) {