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-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new 3b15101d6 Remote repository intent (#1680)
3b15101d6 is described below

commit 3b15101d6f33edfd52a43f3f49cfd597c74294fb
Author: Tamas Cservenak <[email protected]>
AuthorDate: Thu Nov 20 17:48:34 2025 +0100

    Remote repository intent (#1680)
    
    Add new API to reflect the "intent" with given remote repository. Also, 
_within session_ remote repositories may be used as keys (ie in a map), but 
then use of new method IS MUST.
    
    (This _immediately_ revealed bug in 2.0.13: triggered #1667; not after 
fixes got merged)
---
 .../aether/repository/RemoteRepository.java        | 127 +++++++++++++++++----
 .../impl/DefaultRemoteRepositoryManager.java       |   7 +-
 .../internal/impl/DefaultRepositorySystem.java     |  14 ++-
 .../RemoteRepositoryFilterSourceSupport.java       |  23 +---
 4 files changed, 126 insertions(+), 45 deletions(-)

diff --git 
a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RemoteRepository.java
 
b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RemoteRepository.java
index 99cec7d2e..8b359cea4 100644
--- 
a/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RemoteRepository.java
+++ 
b/maven-resolver-api/src/main/java/org/eclipse/aether/repository/RemoteRepository.java
@@ -30,8 +30,32 @@ import static java.util.Objects.requireNonNull;
 
 /**
  * A repository on a remote server.
+ * <p>
+ * If use of instances of this class are meant to be used as keys, see {@link 
#toBareRemoteRepository()} method.
  */
 public final class RemoteRepository implements ArtifactRepository {
+    /**
+     * The intent this repository is to be used for. Newly created 
repositories are usually "bare", and before
+     * their actual use (caller or repository system) adapts them, equip with 
auth/proxy info and even mirrors, if
+     * environment is configured for them. Note: "bare" does not always mean 
"without authentication", as client
+     * code may create with all required properties, but {@link 
org.eclipse.aether.RepositorySystem} will process
+     * them anyway, marking their "intent".
+     * <p>
+     * <em>Important consequence:</em> the change of {@link Intent} on 
repository may affect the use cases when
+     * they are used as keys (they are suitable for that). To use {@link 
RemoteRepository} instances as key,
+     * you should use instances returned by method {@link 
#toBareRemoteRepository()}, that returns "normalized"
+     * repository instances usable as keys. Also, in "key usage case" two 
instances of remote repository are
+     * considered equal if following stands: {@code 
Objects.equals(r1.toBareRemoteRepository(), r2.toBareRemoteRepository())}.
+     *
+     * @see 
org.eclipse.aether.RepositorySystem#newResolutionRepositories(RepositorySystemSession,
 List)
+     * @see 
org.eclipse.aether.RepositorySystem#newDeploymentRepository(RepositorySystemSession,
 RemoteRepository)
+     * @since 2.0.14
+     */
+    public enum Intent {
+        BARE,
+        RESOLUTION,
+        DEPLOYMENT
+    }
 
     private static final Pattern URL_PATTERN =
             
Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*");
@@ -60,6 +84,10 @@ public final class RemoteRepository implements 
ArtifactRepository {
 
     private final boolean blocked;
 
+    private final Intent intent;
+
+    private final int hashCode;
+
     RemoteRepository(Builder builder) {
         if (builder.prototype != null) {
             id = (builder.delta & Builder.ID) != 0 ? builder.id : 
builder.prototype.id;
@@ -80,6 +108,7 @@ public final class RemoteRepository implements 
ArtifactRepository {
             mirroredRepositories = (builder.delta & Builder.MIRRORED) != 0
                     ? copy(builder.mirroredRepositories)
                     : builder.prototype.mirroredRepositories;
+            intent = (builder.delta & Builder.INTENT) != 0 ? builder.intent : 
builder.prototype.intent;
         } else {
             id = builder.id;
             type = builder.type;
@@ -91,17 +120,31 @@ public final class RemoteRepository implements 
ArtifactRepository {
             repositoryManager = builder.repositoryManager;
             blocked = builder.blocked;
             mirroredRepositories = copy(builder.mirroredRepositories);
+            intent = builder.intent;
         }
 
         Matcher m = URL_PATTERN.matcher(url);
         if (m.matches()) {
-            protocol = m.group(1);
-            String host = m.group(5);
-            this.host = (host != null) ? host : "";
+            String h = m.group(5);
+            this.host = (h != null) ? h : "";
+            this.protocol = m.group(1);
         } else {
-            protocol = "";
-            host = "";
+            this.host = "";
+            this.protocol = "";
         }
+
+        this.hashCode = Objects.hash(
+                id,
+                type,
+                url, // host, protocol derived from url
+                releasePolicy,
+                snapshotPolicy,
+                proxy,
+                authentication,
+                mirroredRepositories,
+                repositoryManager,
+                blocked,
+                intent);
     }
 
     private static List<RemoteRepository> copy(List<RemoteRepository> repos) {
@@ -111,10 +154,12 @@ public final class RemoteRepository implements 
ArtifactRepository {
         return Collections.unmodifiableList(Arrays.asList(repos.toArray(new 
RemoteRepository[0])));
     }
 
+    @Override
     public String getId() {
         return id;
     }
 
+    @Override
     public String getContentType() {
         return type;
     }
@@ -203,6 +248,16 @@ public final class RemoteRepository implements 
ArtifactRepository {
         return blocked;
     }
 
+    /**
+     * Returns the intent this repository is prepared for.
+     *
+     * @return The intent this repository is prepared for.
+     * @since 2.0.14
+     */
+    public Intent getIntent() {
+        return intent;
+    }
+
     @Override
     public String toString() {
         StringBuilder buffer = new StringBuilder(256);
@@ -225,6 +280,7 @@ public final class RemoteRepository implements 
ArtifactRepository {
         if (isBlocked()) {
             buffer.append(", blocked");
         }
+        buffer.append(", ").append(getIntent().name()).append(")");
         buffer.append(")");
         return buffer.toString();
     }
@@ -248,26 +304,39 @@ public final class RemoteRepository implements 
ArtifactRepository {
                 && Objects.equals(proxy, that.proxy)
                 && Objects.equals(authentication, that.authentication)
                 && Objects.equals(mirroredRepositories, 
that.mirroredRepositories)
-                && repositoryManager == that.repositoryManager;
+                && repositoryManager == that.repositoryManager
+                && blocked == that.blocked
+                && intent == that.intent;
     }
 
     @Override
     public int hashCode() {
-        int hash = 17;
-        hash = hash * 31 + hash(url);
-        hash = hash * 31 + hash(type);
-        hash = hash * 31 + hash(id);
-        hash = hash * 31 + hash(releasePolicy);
-        hash = hash * 31 + hash(snapshotPolicy);
-        hash = hash * 31 + hash(proxy);
-        hash = hash * 31 + hash(authentication);
-        hash = hash * 31 + hash(mirroredRepositories);
-        hash = hash * 31 + (repositoryManager ? 1 : 0);
-        return hash;
+        return hashCode;
     }
 
-    private static int hash(Object obj) {
-        return obj != null ? obj.hashCode() : 0;
+    /**
+     * Makes "bare" repository out of this instance, usable as keys within one 
single session, by applying following
+     * changes to repository (returns new instance):
+     * <ul>
+     *     <li>sets intent to {@link Intent#BARE}</li>
+     *     <li>nullifies proxy</li>
+     *     <li>nullifies authentication</li>
+     *     <li>nullifies mirrors</li>
+     *     <li>sets repositoryManager to {@code false}</li>
+     * </ul>
+     * These properties are managed by repository system, based on 
configuration. See {@link org.eclipse.aether.RepositorySystem}
+     * and (internal component) {@code 
org.eclipse.aether.impl.RemoteRepositoryManager}.
+     *
+     * @since 2.0.14
+     */
+    public RemoteRepository toBareRemoteRepository() {
+        return new Builder(this)
+                .setIntent(Intent.BARE)
+                .setProxy(null)
+                .setAuthentication(null)
+                .setMirroredRepositories(null)
+                .setRepositoryManager(false)
+                .build();
     }
 
     /**
@@ -286,7 +355,8 @@ public final class RemoteRepository implements 
ArtifactRepository {
                 AUTH = 0x0040,
                 MIRRORED = 0x0080,
                 REPOMAN = 0x0100,
-                BLOCKED = 0x0200;
+                BLOCKED = 0x0200,
+                INTENT = 0x0400;
 
         int delta;
 
@@ -312,6 +382,8 @@ public final class RemoteRepository implements 
ArtifactRepository {
 
         boolean blocked;
 
+        Intent intent = Intent.BARE;
+
         /**
          * Creates a new repository builder.
          *
@@ -545,5 +617,20 @@ public final class RemoteRepository implements 
ArtifactRepository {
             }
             return this;
         }
+
+        /**
+         * Marks the intent for this repository.
+         *
+         * @param intent the intent with this remote repository.
+         * @return This builder for chaining, never {@code null}.
+         * @since 2.0.14
+         */
+        public Builder setIntent(Intent intent) {
+            this.intent = intent;
+            if (prototype != null) {
+                delta(INTENT, this.intent, prototype.intent);
+            }
+            return this;
+        }
     }
 }
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
index 6995ded82..3ad72aade 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.stream.Collectors;
 
 import org.eclipse.aether.RepositoryCache;
 import org.eclipse.aether.RepositorySystemSession;
@@ -160,7 +161,11 @@ public class DefaultRemoteRepositoryManager implements 
RemoteRepositoryManager {
             result.add(repository);
         }
 
-        return result;
+        return result.stream()
+                .map(r -> new RemoteRepository.Builder(r)
+                        .setIntent(RemoteRepository.Intent.RESOLUTION)
+                        .build())
+                .collect(Collectors.toList());
     }
 
     private void logMirror(RepositorySystemSession session, RemoteRepository 
original, RemoteRepository mirror) {
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
index 90003a7c2..542fa5fa4 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java
@@ -441,8 +441,8 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         validateSession(session);
         validateRepositories(repositories);
         repositorySystemValidator.validateRemoteRepositories(session, 
repositories);
-        repositories = remoteRepositoryManager.aggregateRepositories(session, 
new ArrayList<>(), repositories, true);
-        return repositories;
+
+        return remoteRepositoryManager.aggregateRepositories(session, new 
ArrayList<>(), repositories, true);
     }
 
     @Override
@@ -450,12 +450,14 @@ public class DefaultRepositorySystem implements 
RepositorySystem {
         validateSession(session);
         requireNonNull(repository, "repository cannot be null");
         repositorySystemValidator.validateRemoteRepositories(session, 
Collections.singletonList(repository));
-        RemoteRepository.Builder builder = new 
RemoteRepository.Builder(repository);
+
         Authentication auth = 
session.getAuthenticationSelector().getAuthentication(repository);
-        builder.setAuthentication(auth);
         Proxy proxy = session.getProxySelector().getProxy(repository);
-        builder.setProxy(proxy);
-        return builder.build();
+        return new RemoteRepository.Builder(repository)
+                .setAuthentication(auth)
+                .setProxy(proxy)
+                .setIntent(RemoteRepository.Intent.DEPLOYMENT)
+                .build();
     }
 
     @Override
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
index bb4c1e3ff..438c5eaa8 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/filter/RemoteRepositoryFilterSourceSupport.java
@@ -21,7 +21,6 @@ package org.eclipse.aether.internal.impl.filter;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Path;
-import java.util.List;
 
 import org.eclipse.aether.ConfigurationProperties;
 import org.eclipse.aether.RepositorySystemSession;
@@ -79,26 +78,14 @@ public abstract class RemoteRepositoryFilterSourceSupport 
implements RemoteRepos
     }
 
     /**
-     * We use remote repositories as keys, but they may fly in as "bare" or as 
"equipped" (w/ auth and proxy) if caller
-     * used {@link 
org.eclipse.aether.RepositorySystem#newResolutionRepositories(RepositorySystemSession,
 List)} beforehand.
-     * The hash/equalTo method factors in all these as well, but from our 
perspective, they do not matter. So we make all
-     * key remote repositories back to "bare".
-     * Ignored properties of normalized repositories:
-     * <ul>
-     *     <li>proxy - is environment dependent</li>
-     *     <li>authentication - is environment and/or user dependent</li>
-     *     <li>mirrored repositories - is environment dependent (within same 
session does not change)</li>
-     *     <li>repository manager - is environment dependent (within same 
session does not change)</li>
-     * </ul>
+     * We use remote repositories as keys, so normalize them.
+     *
+     * @since 2.0.14
+     * @see RemoteRepository#toBareRemoteRepository()
      */
     protected RemoteRepository normalizeRemoteRepository(
             RepositorySystemSession session, RemoteRepository 
remoteRepository) {
-        return new RemoteRepository.Builder(remoteRepository)
-                .setProxy(null)
-                .setAuthentication(null)
-                .setMirroredRepositories(null)
-                .setRepositoryManager(false)
-                .build();
+        return remoteRepository.toBareRemoteRepository();
     }
 
     /**

Reply via email to