This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 46cf946b6f Type derive (simpler) (#11380)
46cf946b6f is described below

commit 46cf946b6fcd5ff58fa208aa5b018128aec88695
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Mar 19 14:42:58 2026 +0100

    Type derive (simpler) (#11380)
    
    Instead to "smear" this feature across Maven and Resolver classes, for 
start let's keep it "confined" with single class: the TypeDeriver.
    
    Later we can see where to go further with it.
    
    This PR also includes bugfix, where Maven `DefaultType` implements 
`ArtifactType`, while in reality it does not (violates contract by returning 
`null` when no classifier present).
---
 .../internal/ArtifactDescriptorReaderDelegate.java |   3 +-
 .../internal/MavenSessionBuilderSupplier.java      |   6 +-
 .../repository/internal/type/DefaultType.java      |  44 ++++--
 .../repository/internal/type/TypeDeriver.java      | 147 +++++++++++++++++++
 .../DefaultRepositorySystemSessionFactory.java     |   1 +
 .../resolver/DefaultArtifactDescriptorReader.java  |   6 +
 .../impl/resolver/MavenSessionBuilderSupplier.java |   6 +-
 .../maven/impl/resolver/type/DefaultType.java      |  56 ++++++--
 .../impl/resolver/type/DefaultTypeProvider.java    |   3 +
 .../maven/impl/resolver/type/TypeDeriver.java      | 153 ++++++++++++++++++++
 .../impl/resolver/type}/TypeRegistryAdapter.java   |  23 +--
 .../maven/impl/resolver/type/TypeDeriverTest.java  | 156 +++++++++++++++++++++
 12 files changed, 568 insertions(+), 36 deletions(-)

diff --git 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
index 068c697e79..5ccf74b589 100644
--- 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
+++ 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/ArtifactDescriptorReaderDelegate.java
@@ -96,7 +96,8 @@ public void populateResult(RepositorySystemSession session, 
ArtifactDescriptorRe
     private Dependency convert(org.apache.maven.model.Dependency dependency, 
ArtifactTypeRegistry stereotypes) {
         ArtifactType stereotype = stereotypes.get(dependency.getType());
         if (stereotype == null) {
-            stereotype = new DefaultType(dependency.getType(), Language.NONE, 
dependency.getType(), null, false);
+            stereotype = new DefaultType(dependency.getType(), Language.NONE, 
dependency.getType(), null, false)
+                    .toArtifactType();
         }
 
         boolean system = dependency.getSystemPath() != null
diff --git 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
index 0ee5153321..5d1159f09e 100644
--- 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
+++ 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/MavenSessionBuilderSupplier.java
@@ -25,6 +25,7 @@
 import org.apache.maven.repository.internal.artifact.FatArtifactTraverser;
 import 
org.apache.maven.repository.internal.scopes.Maven4ScopeManagerConfiguration;
 import org.apache.maven.repository.internal.type.DefaultTypeProvider;
+import org.apache.maven.repository.internal.type.TypeDeriver;
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession.CloseableSession;
 import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
@@ -114,7 +115,8 @@ protected DependencyGraphTransformer 
getDependencyGraphTransformer() {
                 new ConflictResolver(
                         new ConfigurableVersionSelector(), new 
ManagedScopeSelector(getScopeManager()),
                         new SimpleOptionalitySelector(), new 
ManagedScopeDeriver(getScopeManager())),
-                new ManagedDependencyContextRefiner(getScopeManager()));
+                new ManagedDependencyContextRefiner(getScopeManager()),
+                new TypeDeriver());
     }
 
     /**
@@ -128,7 +130,7 @@ protected DependencyGraphTransformer 
getDependencyGraphTransformer() {
      */
     protected ArtifactTypeRegistry getArtifactTypeRegistry() {
         DefaultArtifactTypeRegistry stereotypes = new 
DefaultArtifactTypeRegistry();
-        new DefaultTypeProvider().types().forEach(stereotypes::add);
+        new DefaultTypeProvider().types().forEach(t -> 
stereotypes.add(t.toArtifactType()));
         return stereotypes;
     }
 
diff --git 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
index 016688ef4c..842b779d8b 100644
--- 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
+++ 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/DefaultType.java
@@ -42,7 +42,7 @@
  * @deprecated since 4.0.0, use {@code maven-api-impl} jar instead
  */
 @Deprecated(since = "4.0.0")
-public class DefaultType implements Type, ArtifactType {
+public class DefaultType implements Type {
     private final String id;
     private final Language language;
     private final String extension;
@@ -80,11 +80,6 @@ public String id() {
         return id;
     }
 
-    @Override
-    public String getId() {
-        return id();
-    }
-
     @Override
     public Language getLanguage() {
         return language;
@@ -105,13 +100,44 @@ public boolean isIncludesDependencies() {
         return this.includesDependencies;
     }
 
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
     @Override
     public Set<PathType> getPathTypes() {
         return this.pathTypes;
     }
 
-    @Override
-    public Map<String, String> getProperties() {
-        return properties;
+    public ArtifactType toArtifactType() {
+        return new ArtifactTypeAdapter(this);
+    }
+
+    private static class ArtifactTypeAdapter implements ArtifactType {
+        private final DefaultType type;
+
+        private ArtifactTypeAdapter(DefaultType type) {
+            this.type = type;
+        }
+
+        @Override
+        public String getId() {
+            return type.id();
+        }
+
+        @Override
+        public String getExtension() {
+            return type.getExtension();
+        }
+
+        @Override
+        public String getClassifier() {
+            return type.getClassifier() == null ? "" : type.getClassifier();
+        }
+
+        @Override
+        public Map<String, String> getProperties() {
+            return type.getProperties();
+        }
     }
 }
diff --git 
a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/TypeDeriver.java
 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/TypeDeriver.java
new file mode 100644
index 0000000000..2120b4a587
--- /dev/null
+++ 
b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/type/TypeDeriver.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.maven.repository.internal.type;
+
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.api.Type;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.ArtifactProperties;
+import org.eclipse.aether.artifact.ArtifactType;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.DependencyGraphTransformationContext;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Type deriver, that handles special case of "processor" type: if a 
dependency node is of this type, all of its
+ * children need to be remapped to certain processor type as well, to end up 
on proper path type.
+ *
+ * @since 4.0.0
+ * @deprecated since 4.0.0, this is internal detail of Maven.
+ */
+@Deprecated(since = "4.0.0")
+public class TypeDeriver implements DependencyGraphTransformer {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public DependencyNode transformGraph(DependencyNode root, 
DependencyGraphTransformationContext context) {
+        if (logger.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            root.accept(new DependencyGraphDumper(
+                    l -> sb.append(l).append("\n"),
+                    DependencyGraphDumper.defaultsWith(
+                            
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+            logger.debug("TYPES: Before transform:\n {}", sb);
+        }
+        root.accept(new 
TypeDeriverVisitor(context.getSession().getArtifactTypeRegistry()));
+        if (logger.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            root.accept(new DependencyGraphDumper(
+                    l -> sb.append(l).append("\n"),
+                    DependencyGraphDumper.defaultsWith(
+                            
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+            logger.debug("TYPES: After transform:\n {}", sb);
+        }
+        return root;
+    }
+
+    private static class TypeDeriverVisitor implements DependencyVisitor {
+        private final ArtifactTypeRegistry registry;
+        private final ArtifactType jar;
+        private final ArtifactType classpathJar;
+        private final ArtifactType modularJar;
+        private final ArtifactType processor;
+        private final ArtifactType classpathProcessor;
+        private final ArtifactType modularProcessor;
+        private final Set<String> needsDerive;
+        private final ArrayDeque<ArtifactType> stack;
+
+        private TypeDeriverVisitor(ArtifactTypeRegistry registry) {
+            this.registry = requireNonNull(registry);
+            this.jar = requireType(Type.JAR);
+            this.classpathJar = requireType(Type.CLASSPATH_JAR);
+            this.modularJar = requireType(Type.MODULAR_JAR);
+            this.processor = requireType(Type.PROCESSOR);
+            this.classpathProcessor = requireType(Type.CLASSPATH_PROCESSOR);
+            this.modularProcessor = requireType(Type.MODULAR_PROCESSOR);
+            this.needsDerive = Set.of(Type.PROCESSOR, 
Type.CLASSPATH_PROCESSOR, Type.MODULAR_PROCESSOR);
+            this.stack = new ArrayDeque<>();
+        }
+
+        private ArtifactType requireType(String id) {
+            return requireNonNull(registry.get(id), "Type " + id + " not found 
but is required");
+        }
+
+        @Override
+        public boolean visitEnter(DependencyNode node) {
+            ArtifactType currentType = jar;
+            if (node.getArtifact() != null) {
+                if 
(node.getArtifact().getProperties().containsKey(ArtifactProperties.TYPE)) {
+                    currentType = registry.get(node.getArtifact()
+                            .getProperty(
+                                    ArtifactProperties.TYPE, 
node.getArtifact().getExtension()));
+                    if (currentType == null) {
+                        currentType = jar;
+                    }
+                }
+                if (!stack.isEmpty()) {
+                    ArtifactType parentType = stack.peek();
+                    if (needsDerive.contains(parentType.getId())) {
+                        Artifact artifact = node.getArtifact();
+                        Map<String, String> props = new 
HashMap<>(artifact.getProperties());
+                        ArtifactType derived = derive(parentType, currentType);
+                        props.putAll(derived.getProperties());
+                        node.setArtifact(artifact.setProperties(props));
+                    }
+                }
+            }
+            stack.push(currentType);
+            return true;
+        }
+
+        @Override
+        public boolean visitLeave(DependencyNode node) {
+            stack.pop();
+            return true;
+        }
+
+        private ArtifactType derive(ArtifactType parentType, ArtifactType 
currentType) {
+            ArtifactType result = currentType;
+            if (jar.getId().equals(currentType.getId())) {
+                result = processor;
+            } else if (classpathJar.getId().equals(currentType.getId())) {
+                result = classpathProcessor;
+            } else if (modularJar.getId().equals(currentType.getId())) {
+                result = modularProcessor;
+            }
+            return result;
+        }
+    }
+}
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
 
b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index 541c8364ed..56e1566a01 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++ 
b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -40,6 +40,7 @@
 import org.apache.maven.eventspy.internal.EventSpyDispatcher;
 import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.impl.resolver.MavenSessionBuilderSupplier;
+import org.apache.maven.impl.resolver.type.TypeRegistryAdapter;
 import org.apache.maven.internal.xml.XmlPlexusConfiguration;
 import org.apache.maven.model.ModelBase;
 import org.apache.maven.resolver.RepositorySystemSessionFactory;
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java
index 10c58e0fb8..4d0b65c594 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java
@@ -27,6 +27,7 @@
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.maven.api.Language;
 import org.apache.maven.api.RemoteRepository;
 import org.apache.maven.api.di.Inject;
 import org.apache.maven.api.di.Named;
@@ -50,6 +51,7 @@
 import org.apache.maven.impl.RequestTraceHelper;
 import org.apache.maven.impl.model.ModelProblemUtils;
 import org.apache.maven.impl.resolver.artifact.MavenArtifactProperties;
+import org.apache.maven.impl.resolver.type.DefaultType;
 import org.eclipse.aether.RepositoryEvent;
 import org.eclipse.aether.RepositoryEvent.EventType;
 import org.eclipse.aether.RepositoryException;
@@ -382,6 +384,10 @@ private void populateResult(InternalSession session, 
ArtifactDescriptorResult re
 
     private Dependency convert(org.apache.maven.api.model.Dependency 
dependency, ArtifactTypeRegistry stereotypes) {
         ArtifactType stereotype = stereotypes.get(dependency.getType());
+        if (stereotype == null) {
+            stereotype = new DefaultType(dependency.getType(), Language.NONE, 
dependency.getType(), null, false)
+                    .toArtifactType();
+        }
 
         boolean system = dependency.getSystemPath() != null
                 && !dependency.getSystemPath().isEmpty();
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java
index 3145953644..f7b4c237dd 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/MavenSessionBuilderSupplier.java
@@ -26,6 +26,7 @@
 import org.apache.maven.impl.resolver.scopes.Maven3ScopeManagerConfiguration;
 import org.apache.maven.impl.resolver.scopes.Maven4ScopeManagerConfiguration;
 import org.apache.maven.impl.resolver.type.DefaultTypeProvider;
+import org.apache.maven.impl.resolver.type.TypeDeriver;
 import org.eclipse.aether.RepositorySystem;
 import org.eclipse.aether.RepositorySystemSession.CloseableSession;
 import org.eclipse.aether.RepositorySystemSession.SessionBuilder;
@@ -110,7 +111,8 @@ protected DependencyGraphTransformer 
getDependencyGraphTransformer() {
                 new ConflictResolver(
                         new ConfigurableVersionSelector(), new 
ManagedScopeSelector(getScopeManager()),
                         new SimpleOptionalitySelector(), new 
ManagedScopeDeriver(getScopeManager())),
-                new ManagedDependencyContextRefiner(getScopeManager()));
+                new ManagedDependencyContextRefiner(getScopeManager()),
+                new TypeDeriver());
     }
 
     /**
@@ -124,7 +126,7 @@ protected DependencyGraphTransformer 
getDependencyGraphTransformer() {
      */
     protected ArtifactTypeRegistry getArtifactTypeRegistry() {
         DefaultArtifactTypeRegistry stereotypes = new 
DefaultArtifactTypeRegistry();
-        new DefaultTypeProvider().types().forEach(stereotypes::add);
+        new DefaultTypeProvider().types().forEach(t -> 
stereotypes.add(t.toArtifactType()));
         return stereotypes;
     }
 
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultType.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultType.java
index 52ec4ba87f..f9773f2a54 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultType.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultType.java
@@ -36,11 +36,11 @@
 import static java.util.Objects.requireNonNull;
 
 /**
- * Default implementation of {@link Type} and Resolver {@link ArtifactType}.
+ * Default implementation of {@link Type} and adapter for Resolver {@link 
ArtifactType}.
  *
  * @since 4.0.0
  */
-public class DefaultType implements Type, ArtifactType {
+public class DefaultType implements Type {
     private final String id;
     private final Language language;
     private final String extension;
@@ -78,11 +78,6 @@ public String id() {
         return id;
     }
 
-    @Override
-    public String getId() {
-        return id();
-    }
-
     @Override
     public Language getLanguage() {
         return language;
@@ -103,14 +98,13 @@ public boolean isIncludesDependencies() {
         return this.includesDependencies;
     }
 
-    @Override
-    public Set<PathType> getPathTypes() {
-        return this.pathTypes;
+    public Map<String, String> getProperties() {
+        return properties;
     }
 
     @Override
-    public Map<String, String> getProperties() {
-        return properties;
+    public Set<PathType> getPathTypes() {
+        return this.pathTypes;
     }
 
     @Override
@@ -124,4 +118,42 @@ public String toString() {
                 + pathTypes + ", properties="
                 + properties + ']';
     }
+
+    /**
+     * Adapts this instance to Resolver {@link ArtifactType}.
+     * <p>
+     * Note: one notable difference exists, the {@link #getClassifier()} 
method behavior.
+     * Once that harmonized, this adapting can go away.
+     */
+    public ArtifactType toArtifactType() {
+        return new ArtifactTypeAdapter(this);
+    }
+
+    private static class ArtifactTypeAdapter implements ArtifactType {
+        private final DefaultType type;
+
+        private ArtifactTypeAdapter(DefaultType type) {
+            this.type = type;
+        }
+
+        @Override
+        public String getId() {
+            return type.id();
+        }
+
+        @Override
+        public String getExtension() {
+            return type.getExtension();
+        }
+
+        @Override
+        public String getClassifier() {
+            return type.getClassifier() == null ? "" : type.getClassifier();
+        }
+
+        @Override
+        public Map<String, String> getProperties() {
+            return type.getProperties();
+        }
+    }
 }
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultTypeProvider.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultTypeProvider.java
index 9140c0bb79..b0eead6840 100644
--- 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultTypeProvider.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/DefaultTypeProvider.java
@@ -27,6 +27,9 @@
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.spi.TypeProvider;
 
+/**
+ * Maven 4 default {@link TypeProvider} implementation.
+ */
 @Named
 public class DefaultTypeProvider implements TypeProvider {
     @SuppressWarnings({"rawtypes", "unchecked"})
diff --git 
a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeDeriver.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeDeriver.java
new file mode 100644
index 0000000000..2a83ed83b1
--- /dev/null
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeDeriver.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.maven.impl.resolver.type;
+
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.maven.api.Type;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.ArtifactProperties;
+import org.eclipse.aether.artifact.ArtifactType;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.DependencyGraphTransformationContext;
+import org.eclipse.aether.collection.DependencyGraphTransformer;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Type deriver, that handles special cases of "processor" (annotation 
processor) node transitive dependencies: all
+ * children of "processor" type are "redirected" to corresponding processor 
subtypes:
+ * <ul>
+ *     <li>{@code jar -> processor}</li>
+ *     <li>{@code classpathJar -> classpathProcessor}</li>
+ *     <li>{@code modularJar -> modularProcessor}</li>
+ * </ul>
+ *
+ * Maven 4 introduces new types to describe intent of dependencies, and the 
"processor" new type (and it's subtypes)
+ * will add processors and their dependencies to proper processor paths, as 
modern Java versions require.
+ *
+ * @since 4.0.0
+ */
+public class TypeDeriver implements DependencyGraphTransformer {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public DependencyNode transformGraph(DependencyNode root, 
DependencyGraphTransformationContext context) {
+        if (logger.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            root.accept(new DependencyGraphDumper(
+                    l -> sb.append(l).append("\n"),
+                    DependencyGraphDumper.defaultsWith(
+                            
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+            logger.debug("TYPES: Before transform:\n {}", sb);
+        }
+        root.accept(new 
TypeDeriverVisitor(context.getSession().getArtifactTypeRegistry()));
+        if (logger.isDebugEnabled()) {
+            StringBuilder sb = new StringBuilder();
+            root.accept(new DependencyGraphDumper(
+                    l -> sb.append(l).append("\n"),
+                    DependencyGraphDumper.defaultsWith(
+                            
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+            logger.debug("TYPES: After transform:\n {}", sb);
+        }
+        return root;
+    }
+
+    private static class TypeDeriverVisitor implements DependencyVisitor {
+        private final ArtifactTypeRegistry registry;
+        private final ArtifactType jar;
+        private final ArtifactType classpathJar;
+        private final ArtifactType modularJar;
+        private final ArtifactType processor;
+        private final ArtifactType classpathProcessor;
+        private final ArtifactType modularProcessor;
+        private final Set<String> needsDerive;
+        private final ArrayDeque<ArtifactType> stack;
+
+        private TypeDeriverVisitor(ArtifactTypeRegistry registry) {
+            this.registry = requireNonNull(registry);
+            this.jar = requireType(Type.JAR);
+            this.classpathJar = requireType(Type.CLASSPATH_JAR);
+            this.modularJar = requireType(Type.MODULAR_JAR);
+            this.processor = requireType(Type.PROCESSOR);
+            this.classpathProcessor = requireType(Type.CLASSPATH_PROCESSOR);
+            this.modularProcessor = requireType(Type.MODULAR_PROCESSOR);
+            this.needsDerive = Set.of(Type.PROCESSOR, 
Type.CLASSPATH_PROCESSOR, Type.MODULAR_PROCESSOR);
+            this.stack = new ArrayDeque<>();
+        }
+
+        private ArtifactType requireType(String id) {
+            return requireNonNull(registry.get(id), "Type " + id + " not found 
but is required");
+        }
+
+        @Override
+        public boolean visitEnter(DependencyNode node) {
+            ArtifactType currentType = jar;
+            if (node.getArtifact() != null) {
+                if 
(node.getArtifact().getProperties().containsKey(ArtifactProperties.TYPE)) {
+                    currentType = registry.get(node.getArtifact()
+                            .getProperty(
+                                    ArtifactProperties.TYPE, 
node.getArtifact().getExtension()));
+                    if (currentType == null) {
+                        currentType = jar;
+                    }
+                }
+                if (!stack.isEmpty()) {
+                    ArtifactType parentType = stack.peek();
+                    if (needsDerive.contains(parentType.getId())) {
+                        Artifact artifact = node.getArtifact();
+                        Map<String, String> props = new 
HashMap<>(artifact.getProperties());
+                        ArtifactType derived = derive(parentType, currentType);
+                        props.putAll(derived.getProperties());
+                        node.setArtifact(artifact.setProperties(props));
+                    }
+                }
+            }
+            stack.push(currentType);
+            return true;
+        }
+
+        @Override
+        public boolean visitLeave(DependencyNode node) {
+            stack.pop();
+            return true;
+        }
+
+        private ArtifactType derive(ArtifactType parentType, ArtifactType 
currentType) {
+            ArtifactType result = currentType;
+            if (jar.getId().equals(currentType.getId())) {
+                result = processor;
+            } else if (classpathJar.getId().equals(currentType.getId())) {
+                result = classpathProcessor;
+            } else if (modularJar.getId().equals(currentType.getId())) {
+                result = modularProcessor;
+            }
+            return result;
+        }
+    }
+}
diff --git 
a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeRegistryAdapter.java
similarity index 70%
rename from 
impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
rename to 
impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeRegistryAdapter.java
index b802e8a44c..31f2542dda 100644
--- 
a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/TypeRegistryAdapter.java
+++ 
b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/type/TypeRegistryAdapter.java
@@ -16,21 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.internal.aether;
+package org.apache.maven.impl.resolver.type;
 
 import org.apache.maven.api.PathType;
 import org.apache.maven.api.Type;
 import org.apache.maven.api.services.TypeRegistry;
-import org.apache.maven.impl.resolver.type.DefaultType;
 import org.eclipse.aether.artifact.ArtifactType;
 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
 
 import static java.util.Objects.requireNonNull;
 
-class TypeRegistryAdapter implements ArtifactTypeRegistry {
+/**
+ * Adapter between Maven {@link TypeRegistry} and Resolver {@link 
ArtifactTypeRegistry}.
+ */
+public class TypeRegistryAdapter implements ArtifactTypeRegistry {
     private final TypeRegistry typeRegistry;
 
-    TypeRegistryAdapter(TypeRegistry typeRegistry) {
+    public TypeRegistryAdapter(TypeRegistry typeRegistry) {
         this.typeRegistry = requireNonNull(typeRegistry, "typeRegistry");
     }
 
@@ -41,11 +43,12 @@ public ArtifactType get(String typeId) {
             return artifactType;
         }
         return new DefaultType(
-                type.id(),
-                type.getLanguage(),
-                type.getExtension(),
-                type.getClassifier(),
-                type.isIncludesDependencies(),
-                type.getPathTypes().toArray(new PathType[0]));
+                        type.id(),
+                        type.getLanguage(),
+                        type.getExtension(),
+                        type.getClassifier(),
+                        type.isIncludesDependencies(),
+                        type.getPathTypes().toArray(new PathType[0]))
+                .toArtifactType();
     }
 }
diff --git 
a/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/type/TypeDeriverTest.java
 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/type/TypeDeriverTest.java
new file mode 100644
index 0000000000..2426b4536c
--- /dev/null
+++ 
b/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/type/TypeDeriverTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.maven.impl.resolver.type;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.maven.api.Type;
+import org.apache.maven.api.services.TypeRegistry;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.ArtifactProperties;
+import org.eclipse.aether.artifact.ArtifactType;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DefaultDependencyNode;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import 
org.eclipse.aether.internal.impl.collect.DefaultDependencyGraphTransformationContext;
+import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static java.util.Objects.requireNonNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class TypeDeriverTest {
+    private final ArtifactTypeRegistry typeRegistry = new 
TypeRegistryAdapter(new TypeRegistry() {
+        private final Map<String, Type> types =
+                new 
DefaultTypeProvider().types().stream().collect(Collectors.toMap(DefaultType::id,
 t -> t));
+
+        @Override
+        public Optional<Type> lookup(String id) {
+            return Optional.ofNullable(types.get(id));
+        }
+    });
+    private final TypeDeriver subject = new TypeDeriver();
+
+    @Test
+    void project() throws Exception {
+        RepositorySystemSession session = mock(RepositorySystemSession.class);
+        when(session.getArtifactTypeRegistry()).thenReturn(typeRegistry);
+
+        ArtifactType jar = requireNonNull(typeRegistry.get(Type.JAR));
+        ArtifactType modularJar = 
requireNonNull(typeRegistry.get(Type.MODULAR_JAR));
+        ArtifactType processor = 
requireNonNull(typeRegistry.get(Type.PROCESSOR));
+
+        // root: "the project"
+        DefaultDependencyNode node = new DefaultDependencyNode(new 
DefaultArtifact("project:project:1.0", jar));
+
+        // direct: a plain JAR dependency
+        DefaultDependencyNode d1 =
+                new DefaultDependencyNode(new Dependency(new 
DefaultArtifact("deps:lib-a:1.0", jar), "compile"));
+        // direct: a plain JAR dependency
+        DefaultDependencyNode d2 =
+                new DefaultDependencyNode(new Dependency(new 
DefaultArtifact("deps:lib-b:1.0", jar), "compile"));
+        // direct: a processor dependency
+        DefaultDependencyNode d3 = new DefaultDependencyNode(
+                new Dependency(new DefaultArtifact("deps:processor:1.0", 
processor), "compile"));
+
+        // transitive: processor depends on JAR
+        DefaultDependencyNode d31 =
+                new DefaultDependencyNode(new Dependency(new 
DefaultArtifact("tdeps:lib-a:1.0", jar), "compile"));
+        // transitive: processor depends on modularJar
+        DefaultDependencyNode d32 = new DefaultDependencyNode(
+                new Dependency(new DefaultArtifact("tdeps:lib-b:1.0", 
modularJar), "compile"));
+        d3.setChildren(List.of(d31, d32));
+
+        node.setChildren(List.of(d1, d2, d3));
+
+        node.accept(new DependencyGraphDumper(
+                System.out::println,
+                DependencyGraphDumper.defaultsWith(
+                        
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+
+        DependencyNode transformed =
+                subject.transformGraph(node, new 
DefaultDependencyGraphTransformationContext(session));
+
+        Assertions.assertNotNull(transformed);
+        transformed.accept(new DependencyGraphDumper(
+                System.out::println,
+                DependencyGraphDumper.defaultsWith(
+                        
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+
+        Assertions.assertEquals(Type.MODULAR_PROCESSOR, 
d32.getArtifact().getProperty(ArtifactProperties.TYPE, ""));
+    }
+
+    @Test
+    void plugin() throws Exception {
+        RepositorySystemSession session = mock(RepositorySystemSession.class);
+        when(session.getArtifactTypeRegistry()).thenReturn(typeRegistry);
+
+        ArtifactType mavenPlugin = 
requireNonNull(typeRegistry.get(Type.MAVEN_PLUGIN));
+        ArtifactType jar = requireNonNull(typeRegistry.get(Type.JAR));
+        ArtifactType classpathJar = 
requireNonNull(typeRegistry.get(Type.CLASSPATH_JAR));
+        ArtifactType modularJar = 
requireNonNull(typeRegistry.get(Type.MODULAR_JAR));
+        ArtifactType processor = 
requireNonNull(typeRegistry.get(Type.PROCESSOR));
+
+        // root: "the plugin"
+        DefaultDependencyNode node = new DefaultDependencyNode(new 
DefaultArtifact("plugin:plugin:1.0", mavenPlugin));
+
+        // direct: a plain JAR dependency
+        DefaultDependencyNode d1 =
+                new DefaultDependencyNode(new Dependency(new 
DefaultArtifact("deps:lib-a:1.0", jar), "compile"));
+        // direct: a plain JAR dependency
+        DefaultDependencyNode d2 =
+                new DefaultDependencyNode(new Dependency(new 
DefaultArtifact("deps:lib-b:1.0", jar), "compile"));
+        // direct: a processor dependency
+        DefaultDependencyNode d3 = new DefaultDependencyNode(
+                new Dependency(new DefaultArtifact("deps:processor:1.0", 
processor), "compile"));
+
+        // transitive: processor depends on classpathJar
+        DefaultDependencyNode d31 = new DefaultDependencyNode(
+                new Dependency(new DefaultArtifact("tdeps:lib-a:1.0", 
classpathJar), "compile"));
+        // transitive: processor depends on modularJar
+        DefaultDependencyNode d32 = new DefaultDependencyNode(
+                new Dependency(new DefaultArtifact("tdeps:lib-b:1.0", 
modularJar), "compile"));
+        d3.setChildren(List.of(d31, d32));
+
+        node.setChildren(List.of(d1, d2, d3));
+
+        node.accept(new DependencyGraphDumper(
+                System.out::println,
+                DependencyGraphDumper.defaultsWith(
+                        
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+
+        DependencyNode transformed =
+                subject.transformGraph(node, new 
DefaultDependencyGraphTransformationContext(session));
+
+        Assertions.assertNotNull(transformed);
+        transformed.accept(new DependencyGraphDumper(
+                System.out::println,
+                DependencyGraphDumper.defaultsWith(
+                        
List.of(DependencyGraphDumper.artifactProperties(List.of(ArtifactProperties.TYPE))))));
+
+        Assertions.assertEquals(Type.MODULAR_PROCESSOR, 
d32.getArtifact().getProperty(ArtifactProperties.TYPE, ""));
+    }
+}


Reply via email to