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 e3d4ed55f Fix for ChainedWorkspaceReader (#1909)
e3d4ed55f is described below
commit e3d4ed55fe19ffaa0463c111484eda4740478e0a
Author: Tamas Cservenak <[email protected]>
AuthorDate: Sun Jun 14 11:10:31 2026 +0200
Fix for ChainedWorkspaceReader (#1909)
The reader instances once set, are immutable, but repository instances
returned by readers may change (their key).
---
.../util/repository/ChainedWorkspaceReader.java | 38 +++--
.../repository/ChainedWorkspaceReaderTest.java | 175 +++++++++++++++++++++
2 files changed, 198 insertions(+), 15 deletions(-)
diff --git
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/ChainedWorkspaceReader.java
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/ChainedWorkspaceReader.java
index c34a60e60..2ecfd872d 100644
---
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/ChainedWorkspaceReader.java
+++
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/repository/ChainedWorkspaceReader.java
@@ -20,10 +20,13 @@ package org.eclipse.aether.util.repository;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.repository.WorkspaceReader;
@@ -35,10 +38,8 @@ import static java.util.Objects.requireNonNull;
* A workspace reader that delegates to a chain of other readers, effectively
aggregating their contents.
*/
public final class ChainedWorkspaceReader implements WorkspaceReader {
-
- private final List<WorkspaceReader> readers = new ArrayList<>();
-
- private WorkspaceRepository repository;
+ private final List<WorkspaceReader> readers;
+ private final AtomicReference<WorkspaceRepository> repository;
/**
* Creates a new workspace reader by chaining the specified readers.
@@ -47,19 +48,19 @@ public final class ChainedWorkspaceReader implements
WorkspaceReader {
* @see #newInstance(WorkspaceReader, WorkspaceReader)
*/
public ChainedWorkspaceReader(WorkspaceReader... readers) {
+ ArrayList<WorkspaceReader> list = new ArrayList<>();
if (readers != null) {
- Collections.addAll(this.readers, readers);
+ Arrays.stream(readers).filter(Objects::nonNull).forEach(list::add);
}
-
StringBuilder buffer = new StringBuilder();
- for (WorkspaceReader reader : this.readers) {
+ for (WorkspaceReader reader : list) {
if (buffer.length() > 0) {
buffer.append('+');
}
buffer.append(reader.getRepository().getContentType());
}
-
- repository = new WorkspaceRepository(buffer.toString(), new
Key(this.readers));
+ this.readers = Collections.unmodifiableList(list);
+ this.repository = new AtomicReference<>(new
WorkspaceRepository(buffer.toString(), new Key(list)));
}
/**
@@ -79,6 +80,7 @@ public final class ChainedWorkspaceReader implements
WorkspaceReader {
return new ChainedWorkspaceReader(reader1, reader2);
}
+ @Override
public File findArtifact(Artifact artifact) {
requireNonNull(artifact, "artifact cannot be null");
File file = null;
@@ -93,6 +95,7 @@ public final class ChainedWorkspaceReader implements
WorkspaceReader {
return file;
}
+ @Override
public List<String> findVersions(Artifact artifact) {
requireNonNull(artifact, "artifact cannot be null");
Collection<String> versions = new LinkedHashSet<>();
@@ -104,22 +107,27 @@ public final class ChainedWorkspaceReader implements
WorkspaceReader {
return Collections.unmodifiableList(new ArrayList<>(versions));
}
+ @Override
public WorkspaceRepository getRepository() {
Key key = new Key(readers);
- if (!key.equals(repository.getKey())) {
- repository = new WorkspaceRepository(repository.getContentType(),
key);
- }
- return repository;
+ return repository.updateAndGet(r -> {
+ if (!key.equals(r.getKey())) {
+ return new WorkspaceRepository(r.getContentType(), key);
+ } else {
+ return r;
+ }
+ });
}
private static class Key {
-
- private final List<Object> keys = new ArrayList<>();
+ private final List<Object> keys;
Key(List<WorkspaceReader> readers) {
+ ArrayList<Object> keys = new ArrayList<>();
for (WorkspaceReader reader : readers) {
keys.add(reader.getRepository().getKey());
}
+ this.keys = keys;
}
@Override
diff --git
a/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/ChainedWorkspaceReaderTest.java
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/ChainedWorkspaceReaderTest.java
new file mode 100644
index 000000000..2de109c1e
--- /dev/null
+++
b/maven-resolver-util/src/test/java/org/eclipse/aether/util/repository/ChainedWorkspaceReaderTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.eclipse.aether.util.repository;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.WorkspaceReader;
+import org.eclipse.aether.repository.WorkspaceRepository;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class ChainedWorkspaceReaderTest {
+ /**
+ * Stable reader, returns always same instance for {@link
#getRepository()}.
+ */
+ private static class StableWorkspaceReader implements WorkspaceReader {
+ private final WorkspaceRepository repository = new
WorkspaceRepository("stable", "stableKey");
+
+ @Override
+ public WorkspaceRepository getRepository() {
+ return repository;
+ }
+
+ @Override
+ public File findArtifact(Artifact artifact) {
+ return null;
+ }
+
+ @Override
+ public List<String> findVersions(Artifact artifact) {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Unstable reader, returns always different instance with different key
for {@link #getRepository()}.
+ */
+ private static class UnstableWorkspaceReader implements WorkspaceReader {
+ private final AtomicInteger count = new AtomicInteger();
+
+ @Override
+ public WorkspaceRepository getRepository() {
+ return new WorkspaceRepository("unstable",
count.getAndIncrement());
+ }
+
+ @Override
+ public File findArtifact(Artifact artifact) {
+ return null;
+ }
+
+ @Override
+ public List<String> findVersions(Artifact artifact) {
+ return Collections.emptyList();
+ }
+ }
+
+ @Test
+ void constructEmpty() {
+ ChainedWorkspaceReader reader = new ChainedWorkspaceReader();
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(repository);
+ assertEquals("", repository.getContentType());
+ }
+
+ @Test
+ void constructNullReader() {
+ ChainedWorkspaceReader reader = new
ChainedWorkspaceReader((WorkspaceReader) null);
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(repository);
+ assertEquals("", repository.getContentType());
+ }
+
+ @Test
+ void constructNullVararg() {
+ ChainedWorkspaceReader reader = new
ChainedWorkspaceReader((WorkspaceReader[]) null);
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(repository);
+ assertEquals("", repository.getContentType());
+ }
+
+ @Test
+ void constructOne() {
+ ChainedWorkspaceReader reader = new ChainedWorkspaceReader(new
StableWorkspaceReader());
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(repository);
+ assertEquals("stable", repository.getContentType());
+ }
+
+ @Test
+ void constructTwo() {
+ ChainedWorkspaceReader reader =
+ new ChainedWorkspaceReader(new StableWorkspaceReader(), new
StableWorkspaceReader());
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(repository);
+ assertEquals("stable+stable", repository.getContentType());
+ }
+
+ @Test
+ void constructMultipleWithNulls() {
+ ChainedWorkspaceReader reader1 =
+ new ChainedWorkspaceReader(null, new StableWorkspaceReader(),
null, new StableWorkspaceReader(), null);
+ ChainedWorkspaceReader reader2 =
+ new ChainedWorkspaceReader(new StableWorkspaceReader(), new
StableWorkspaceReader());
+ WorkspaceRepository repository1 = reader1.getRepository();
+ assertNotNull(repository1);
+ WorkspaceRepository repository2 = reader2.getRepository();
+ assertNotNull(repository2);
+ assertEquals("stable+stable", repository1.getContentType());
+ assertEquals("stable+stable", repository2.getContentType());
+ assertEquals(repository1.getKey(), repository2.getKey());
+ }
+
+ @Test
+ void keyChange() {
+ WorkspaceReader reader =
+ ChainedWorkspaceReader.newInstance(new
StableWorkspaceReader(), new UnstableWorkspaceReader());
+ WorkspaceRepository repository = reader.getRepository();
+ assertNotNull(reader);
+ assertEquals("stable+unstable", repository.getContentType());
+ assertNotEquals(repository, reader.getRepository());
+ }
+
+ @Test
+ void concurrentKeyChange() throws InterruptedException {
+ WorkspaceReader reader =
+ ChainedWorkspaceReader.newInstance(new
StableWorkspaceReader(), new UnstableWorkspaceReader());
+ final int threads = 100;
+ ConcurrentLinkedQueue<WorkspaceRepository> results = new
ConcurrentLinkedQueue<>();
+ CountDownLatch latch = new CountDownLatch(threads);
+ for (int i = 0; i < threads; i++) {
+ new Thread(() -> {
+ try {
+ results.add(reader.getRepository());
+ } finally {
+ latch.countDown();
+ }
+ })
+ .start();
+ }
+ latch.await();
+ // assert no same element present in results
+ assertEquals(new HashSet<>(results).size(), results.size());
+ for (int i = 0; i < threads; i++) {
+ assertEquals("stable+unstable", results.remove().getContentType());
+ }
+ // foolproof: queue must be empty after we checked all elements of it
+ assertEquals(0, results.size());
+ }
+}