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

ctubbsii pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo-classloaders.git


The following commit(s) were added to refs/heads/main by this push:
     new 584e5bd  Fix Manifest/Resource equals/hashCode and add errorprone (#77)
584e5bd is described below

commit 584e5bd40d6d2530f992fa8aae414fe6ca38941b
Author: Christopher Tubbs <[email protected]>
AuthorDate: Tue Feb 24 15:07:13 2026 -0500

    Fix Manifest/Resource equals/hashCode and add errorprone (#77)
    
    * Add errorprone CI checks to GitHub Actions and compat checks for
      Accumulo 4.0
    * Update code to fix items caught by errorprone
    * Rename manifests field to monitoredManifests, just to make it more
      obvious that it is tracking the manifests currently being monitored
    * Fix equals and hashCode methods for Manifest and Resource, to account
      for comment and algorithm fields, respectively
    * Add test for deduplication of resources in a json
---
 .github/workflows/maven-on-demand.yaml             |  2 +
 .github/workflows/maven.yaml                       | 40 +++++++++--
 modules/caching-classloader/pom.xml                |  5 --
 .../classloader/ccl/CachingClassLoaderFactory.java | 22 +++---
 .../accumulo/classloader/ccl/LocalStore.java       |  1 -
 .../classloader/ccl/manifest/Manifest.java         | 13 +---
 .../classloader/ccl/manifest/Resource.java         | 15 ++--
 .../ccl/CachingClassLoaderFactoryTest.java         |  7 +-
 .../accumulo/classloader/ccl/LocalStoreTest.java   | 43 ++++++-----
 .../MiniAccumuloClusterClassLoaderFactoryTest.java | 18 ++---
 .../apache/accumulo/classloader/ccl/TestUtils.java | 12 +++-
 .../accumulo/classloader/ccl/URLTypesTest.java     |  4 +-
 .../classloader/ccl/manifest/ManifestTest.java     | 51 +++++++++++--
 .../classloader/vfs/examples/ExampleIterator.java  |  7 ++
 .../classloader/vfs/examples/ExampleIterator.java  |  7 ++
 .../HdfsURLStreamHandlerProvider.java              |  2 +-
 pom.xml                                            | 83 +++++++++++++++++++++-
 17 files changed, 252 insertions(+), 80 deletions(-)

diff --git a/.github/workflows/maven-on-demand.yaml 
b/.github/workflows/maven-on-demand.yaml
index e7b1cea..c96b447 100644
--- a/.github/workflows/maven-on-demand.yaml
+++ b/.github/workflows/maven-on-demand.yaml
@@ -46,6 +46,8 @@ jobs:
         distribution: temurin
         java-version: 21
         cache: 'maven'
+    - name: Show the first log message
+      run: git log -n1
     - name: Build with Maven
       timeout-minutes: 345
       run: mvn -B -V -e -ntp "-Dstyle.color=always" ${{ 
github.event.inputs.goals }}
diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml
index d992d17..4ac4e71 100644
--- a/.github/workflows/maven.yaml
+++ b/.github/workflows/maven.yaml
@@ -31,8 +31,8 @@ permissions:
   contents: read
 
 jobs:
-  mvn:
-    timeout-minutes: 60
+  # fast build to populate the local maven repository cache
+  fastbuild:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v6
@@ -42,22 +42,50 @@ jobs:
         distribution: temurin
         java-version: 21
         cache: 'maven'
-    - name: Build with Maven (verify javadoc:jar)
-      run: mvn -B -V -e -ntp "-Dstyle.color=always" verify javadoc:jar 
-DskipFormat -DverifyFormat
+    - name: Show the first log message
+      run: git log -n1
+    - name: Build with Maven (Fast Build)
+      timeout-minutes: 20
+      run: mvn -B -V -e -ntp "-Dstyle.color=always" clean package 
dependency:resolve -DskipTests -DskipFormat -DverifyFormat
+      env:
+        MAVEN_OPTS: -Djansi.force=true
+  # more complete builds with tests
+  mvn:
+    needs: fastbuild
+    strategy:
+      matrix:
+        profile:
+          - {name: 'unit-tests',    javaver: 21, args: 'verify -PskipQA 
-DskipTests=false'}
+          - {name: 'qa-checks',     javaver: 21, args: 'verify javadoc:jar 
-DskipTests -Dspotbugs.timeout=3600000'}
+          - {name: 'compat',        javaver: 21, args: 'package -DskipTests 
-Dversion.accumulo=4.0.0-SNAPSHOT -Puse-apache-snapshots 
-Dmaven.compiler.failOnWarning=false'}
+          - {name: 'errorprone',    javaver: 21, args: 'verify 
-Perrorprone,skipQA'}
+      fail-fast: false
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v6
+    - name: Set up JDK ${{ matrix.profile.javaver }}
+      uses: actions/setup-java@v5
+      with:
+        distribution: temurin
+        java-version: ${{ matrix.profile.javaver }}
+        cache: 'maven'
+    - name: Build with Maven (${{ matrix.profile.name }})
+      timeout-minutes: 60
+      run: mvn -B -V -e -ntp "-Dstyle.color=always" -DskipFormat ${{ 
matrix.profile.args }}
       env:
         MAVEN_OPTS: -Djansi.force=true
     - name: Upload unit test results
       if: ${{ failure() }}
       uses: actions/upload-artifact@v6
       with:
-        name: surefire-reports
+        name: surefire-reports-${{ matrix.profile.name }}
         path: ./**/target/surefire-reports/
         if-no-files-found: ignore
     - name: Upload integration test results
       if: ${{ failure() }}
       uses: actions/upload-artifact@v6
       with:
-        name: failsafe-reports
+        name: failsafe-reports-${{ matrix.profile.name }}
         path: ./**/target/failsafe-reports/
         if-no-files-found: ignore
 
diff --git a/modules/caching-classloader/pom.xml 
b/modules/caching-classloader/pom.xml
index 35a7270..bc20416 100644
--- a/modules/caching-classloader/pom.xml
+++ b/modules/caching-classloader/pom.xml
@@ -120,11 +120,6 @@
       <artifactId>jetty-server</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-util</artifactId>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-api</artifactId>
diff --git 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactory.java
 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactory.java
index 2697150..e0d9f25 100644
--- 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactory.java
+++ 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactory.java
@@ -123,7 +123,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
 
   // stores the latest seen manifest for a remote URL; URI types are used here 
for the key
   // instead of URL because URL.hashCode could trigger network activity for 
hostname lookups
-  private final ConcurrentHashMap<URI,Manifest> manifests = new 
ConcurrentHashMap<>();
+  private final ConcurrentHashMap<URI,Manifest> monitoredManifests = new 
ConcurrentHashMap<>();
 
   // to keep this coherent with the manifests, updates to this should be done 
in manifests.compute()
   private final 
DeduplicationCache<DeduplicationCacheKey,LocalStore,URLClassLoader> 
classloaders =
@@ -148,16 +148,17 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
    */
   private void monitor(final URI uri, long interval) {
     LOG.trace("Monitoring manifest {} for changes at {} second intervals", 
uri, interval);
-    executor.schedule(() -> {
+    var unused = executor.schedule(() -> {
       try {
         checkMonitoredUrl(uri, interval);
       } catch (Throwable t) {
         LOG.error("Unhandled exception occurred in manifest monitor thread. 
Removing manifest {}.",
             uri, t);
-        manifests.remove(uri);
+        monitoredManifests.remove(uri);
         throw t;
       }
     }, interval, TimeUnit.SECONDS);
+    assert unused != null;
   }
 
   @Override
@@ -188,7 +189,8 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
     };
     try {
       // check the allowed URLs pattern, getting it ready for first use, and 
warning if it is bad
-      allowedUrlsPattern.get();
+      var unused = allowedUrlsPattern.get();
+      assert unused != null;
     } catch (RuntimeException npe) {
       LOG.warn(
           "Property {} is not set or contains an invalid pattern ()."
@@ -211,7 +213,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
     try {
       // get the current manifest, or create it from the URL if absent; this 
has the side effect of
       // creating and caching a class loader instance if it doesn't exist for 
the computed manifest
-      manifests.compute(manifestURI,
+      monitoredManifests.compute(manifestURI,
           (key, previous) -> computeManifestAndClassLoader(classloader, key, 
previous));
     } catch (RuntimeException e) {
       throw new ContextClassLoaderException(e.getMessage(), e);
@@ -248,7 +250,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
   }
 
   private void checkMonitoredUrl(URI uri, long interval) {
-    Manifest current = manifests.compute(uri, (key, previous) -> {
+    Manifest current = monitoredManifests.compute(uri, (key, previous) -> {
       if (previous == null) {
         // manifest has been removed from the map, no need to check for update
         LOG.debug("Manifest for {} not present, no longer monitoring for 
changes", uri);
@@ -271,7 +273,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
       if (!current.getChecksum().equals(update.getChecksum())) {
         LOG.debug("Context manifest for {} has changed", uri);
         localStore.get().storeContext(update);
-        manifests.put(uri, update);
+        monitoredManifests.put(uri, update);
         nextInterval = update.getMonitorIntervalSeconds();
         classloaderFailures.remove(uri);
       } else {
@@ -280,7 +282,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
       // reschedule this task to run if the manifest exists.
       // Atomically lock on the key and only reschedule if the value is 
present.
       final long finalMonitorInterval = nextInterval;
-      manifests.compute(uri, (k, v) -> {
+      monitoredManifests.compute(uri, (k, v) -> {
         if (v != null) {
           monitor(uri, finalMonitorInterval);
         }
@@ -305,7 +307,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
         // unset the classloader reference so that the failure
         // will return from getClassLoader in the calling thread
         LOG.info("Grace period for failing classloader has elapsed for {}", 
uri);
-        manifests.remove(uri);
+        monitoredManifests.remove(uri);
         classloaderFailures.remove(uri);
       } else {
         LOG.trace("Failing to update classloader for {} within the grace 
period", uri, e);
@@ -315,7 +317,7 @@ public class CachingClassLoaderFactory implements 
ContextClassLoaderFactory {
       // on success or handled exception
       // Atomically lock on the key and only reschedule if the value is 
present.
       final long finalMonitorInterval = nextInterval;
-      manifests.compute(uri, (k, v) -> {
+      monitoredManifests.compute(uri, (k, v) -> {
         if (v != null) {
           monitor(uri, finalMonitorInterval);
         }
diff --git 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/LocalStore.java
 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/LocalStore.java
index cce9c91..7939b0a 100644
--- 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/LocalStore.java
+++ 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/LocalStore.java
@@ -206,7 +206,6 @@ public final class LocalStore {
             LOG.trace("Skipped resource {} while another process or thread is 
downloading it",
                 resource.getLocation());
             waitingOnOtherDownloadsCount++;
-            continue;
           }
         }
         // avoid rapid cycling checking for other downloads to finish; wait 
longer if more downloads
diff --git 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Manifest.java
 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Manifest.java
index deb83c5..c583a55 100644
--- 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Manifest.java
+++ 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Manifest.java
@@ -19,7 +19,6 @@
 package org.apache.accumulo.classloader.ccl.manifest;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.hash;
 import static java.util.Objects.requireNonNull;
 
 import java.io.BufferedInputStream;
@@ -121,7 +120,7 @@ public class Manifest {
 
   @Override
   public int hashCode() {
-    return hash(monitorIntervalSeconds, resources);
+    return toJson().hashCode();
   }
 
   @Override
@@ -129,15 +128,7 @@ public class Manifest {
     if (this == obj) {
       return true;
     }
-    if (obj == null) {
-      return false;
-    }
-    if (getClass() != obj.getClass()) {
-      return false;
-    }
-    Manifest other = (Manifest) obj;
-    return monitorIntervalSeconds == other.monitorIntervalSeconds
-        && Objects.equals(resources, other.resources);
+    return obj instanceof Manifest ? Objects.equals(toJson(), ((Manifest) 
obj).toJson()) : false;
   }
 
   public String getChecksumAlgorithm() {
diff --git 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Resource.java
 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Resource.java
index 56153e0..d4d8d7d 100644
--- 
a/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Resource.java
+++ 
b/modules/caching-classloader/src/main/java/org/apache/accumulo/classloader/ccl/manifest/Resource.java
@@ -76,7 +76,7 @@ public class Resource {
 
   @Override
   public int hashCode() {
-    return Objects.hash(location, checksum);
+    return Objects.hash(getLocation(), getAlgorithm(), getChecksum());
   }
 
   @Override
@@ -84,14 +84,13 @@ public class Resource {
     if (this == obj) {
       return true;
     }
-    if (obj == null) {
-      return false;
+    if (obj instanceof Resource) {
+      Resource other = (Resource) obj;
+      return Objects.equals(getLocation(), other.getLocation())
+          && Objects.equals(getAlgorithm(), other.getAlgorithm())
+          && Objects.equals(getChecksum(), other.getChecksum());
     }
-    if (getClass() != obj.getClass()) {
-      return false;
-    }
-    Resource other = (Resource) obj;
-    return Objects.equals(checksum, other.checksum) && 
Objects.equals(location, other.location);
+    return false;
   }
 
 }
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactoryTest.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactoryTest.java
index 3e724d3..85559b6 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactoryTest.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/CachingClassLoaderFactoryTest.java
@@ -901,8 +901,11 @@ class CachingClassLoaderFactoryTest {
     deleteFuture.get();
     executor.shutdown();
 
-    long workingDirsCount =
-        Files.list(baseCacheDir.resolve(WORKING_DIR)).filter(p -> 
p.toFile().isDirectory()).count();
+    long workingDirsCount;
+    try (var s =
+        Files.list(baseCacheDir.resolve(WORKING_DIR)).filter(p -> 
p.toFile().isDirectory())) {
+      workingDirsCount = s.count();
+    }
     // check that many hard link directories were created; this is 
non-deterministic; at least 50 is
     // nice to see (5 per thread), but depending on performance, each thread 
may not create them
     // that quickly; we should get at least 1 per thread, though
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/LocalStoreTest.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/LocalStoreTest.java
index 0403ee0..5f8611d 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/LocalStoreTest.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/LocalStoreTest.java
@@ -36,13 +36,13 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.LinkedHashSet;
 import java.util.concurrent.CountDownLatch;
-import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 import org.apache.accumulo.classloader.ccl.TestUtils.TestClassInfo;
@@ -65,7 +65,7 @@ public class LocalStoreTest {
   private static Path tempDir;
 
   // a mock URL checker that allows all for test
-  private static final BiConsumer<String,URL> ALLOW_ALL_URLS = (type, url) -> 
{};
+  private static final void allowAllUrls(String type, URL url) {};
 
   private static MiniDFSCluster hdfs;
   private static Server jetty;
@@ -97,6 +97,7 @@ public class LocalStoreTest {
 
     // Put C into Jetty
     var jarCParentDirectory = Path.of(jarCOrigLocation.toURI()).getParent();
+    assertNotNull(jarCParentDirectory);
     jetty = TestUtils.getJetty(jarCParentDirectory);
     final URL jarCNewLocation = jetty.getURI().resolve("TestC.jar").toURL();
 
@@ -135,8 +136,10 @@ public class LocalStoreTest {
   @Test
   public void testPropertyNotSet() {
     // test baseDir not set
-    assertThrows(NullPointerException.class, () -> new LocalStore((Path) null, 
ALLOW_ALL_URLS));
-    assertThrows(NullPointerException.class, () -> new LocalStore((String) 
null, ALLOW_ALL_URLS));
+    assertThrows(NullPointerException.class,
+        () -> new LocalStore((Path) null, LocalStoreTest::allowAllUrls));
+    assertThrows(NullPointerException.class,
+        () -> new LocalStore((String) null, LocalStoreTest::allowAllUrls));
     // test URL checker not set
     assertThrows(NullPointerException.class, () -> new 
LocalStore(baseCacheDir, null));
   }
@@ -149,7 +152,7 @@ public class LocalStoreTest {
     assertEquals("working", WORKING_DIR);
 
     assertFalse(Files.exists(baseCacheDir));
-    var localStore = new LocalStore(baseCacheDir, ALLOW_ALL_URLS);
+    var localStore = new LocalStore(baseCacheDir, 
LocalStoreTest::allowAllUrls);
     assertTrue(Files.exists(baseCacheDir));
     assertTrue(Files.exists(baseCacheDir.resolve(MANIFESTS_DIR)));
     assertTrue(Files.exists(baseCacheDir.resolve(RESOURCES_DIR)));
@@ -162,10 +165,10 @@ public class LocalStoreTest {
   @Test
   public void testCreateBaseDirsMultipleTimes() throws Exception {
     assertFalse(Files.exists(baseCacheDir));
-    assertNotNull(new LocalStore(baseCacheDir, ALLOW_ALL_URLS));
-    assertNotNull(new LocalStore(baseCacheDir, ALLOW_ALL_URLS));
-    assertNotNull(new LocalStore(baseCacheDir, ALLOW_ALL_URLS));
-    assertNotNull(new LocalStore(baseCacheDir, ALLOW_ALL_URLS));
+    assertNotNull(new LocalStore(baseCacheDir, LocalStoreTest::allowAllUrls));
+    assertNotNull(new LocalStore(baseCacheDir, LocalStoreTest::allowAllUrls));
+    assertNotNull(new LocalStore(baseCacheDir, LocalStoreTest::allowAllUrls));
+    assertNotNull(new LocalStore(baseCacheDir, LocalStoreTest::allowAllUrls));
     assertTrue(Files.exists(baseCacheDir));
   }
 
@@ -230,7 +233,7 @@ public class LocalStoreTest {
 
   @Test
   public void testStoreContext() throws Exception {
-    var localStore = new LocalStore(baseCacheDir, ALLOW_ALL_URLS);
+    var localStore = new LocalStore(baseCacheDir, 
LocalStoreTest::allowAllUrls);
     localStore.storeContext(manifest);
 
     // Confirm the 3 jars are cached locally
@@ -244,7 +247,7 @@ public class LocalStoreTest {
 
   @Test
   public void testClassLoader() throws Exception {
-    var localStore = new LocalStore(baseCacheDir, ALLOW_ALL_URLS);
+    var localStore = new LocalStore(baseCacheDir, 
LocalStoreTest::allowAllUrls);
     localStore.storeContext(manifest);
     var cacheKey = new DeduplicationCacheKey(URI.create("loc"), manifest);
     var cl = createClassLoader(cacheKey, localStore);
@@ -256,7 +259,7 @@ public class LocalStoreTest {
 
   @Test
   public void testClassLoaderUpdate() throws Exception {
-    var localStore = new LocalStore(baseCacheDir, ALLOW_ALL_URLS);
+    var localStore = new LocalStore(baseCacheDir, 
LocalStoreTest::allowAllUrls);
     localStore.storeContext(manifest);
     var cacheKey = new DeduplicationCacheKey(URI.create("loc"), manifest);
     final var cl = createClassLoader(cacheKey, localStore);
@@ -304,13 +307,19 @@ public class LocalStoreTest {
     testClassLoads(updatedCl, classD);
   }
 
+  private static long getWorkingDirCount(LocalStore localStore) throws 
IOException {
+    try (var s = 
Files.list(localStore.workingDir()).filter(Files::isDirectory)) {
+      return s.count();
+    }
+  }
+
   @Test
   public void testClassLoaderDirCleanup() throws Exception {
-    var localStore = new LocalStore(baseCacheDir, ALLOW_ALL_URLS);
-    assertEquals(0, 
Files.list(localStore.workingDir()).filter(Files::isDirectory).count());
+    var localStore = new LocalStore(baseCacheDir, 
LocalStoreTest::allowAllUrls);
+    assertEquals(0, getWorkingDirCount(localStore));
     localStore.storeContext(manifest);
     var cacheKey = new DeduplicationCacheKey(URI.create("loc"), manifest);
-    assertEquals(0, 
Files.list(localStore.workingDir()).filter(Files::isDirectory).count());
+    assertEquals(0, getWorkingDirCount(localStore));
 
     final var endBackgroundThread = new CountDownLatch(1);
     var createdClassLoader = new CountDownLatch(1);
@@ -329,12 +338,12 @@ public class LocalStoreTest {
     t.start();
     createdClassLoader.await();
 
-    assertEquals(1, 
Files.list(localStore.workingDir()).filter(Files::isDirectory).count());
+    assertEquals(1, getWorkingDirCount(localStore));
 
     endBackgroundThread.countDown();
 
     // wait for classloader to be garbage collected and its cleaner to run
-    while 
(Files.list(localStore.workingDir()).filter(Files::isDirectory).count() != 0) {
+    while (getWorkingDirCount(localStore) != 0) {
       System.gc();
       Thread.sleep(50);
     }
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/MiniAccumuloClusterClassLoaderFactoryTest.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/MiniAccumuloClusterClassLoaderFactoryTest.java
index 4db228a..ae9a219 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/MiniAccumuloClusterClassLoaderFactoryTest.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/MiniAccumuloClusterClassLoaderFactoryTest.java
@@ -86,7 +86,6 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
     @Override
     public void configureMiniCluster(MiniAccumuloConfigImpl cfg,
         org.apache.hadoop.conf.Configuration coreSite) {
-      cfg.setNumTservers(3);
       cfg.setProperty(Property.TSERV_NATIVEMAP_ENABLED.getKey(), "false");
       cfg.setProperty(Property.GENERAL_CONTEXT_CLASSLOADER_FACTORY.getKey(),
           CachingClassLoaderFactory.class.getName());
@@ -148,7 +147,7 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
 
       List<String> tservers = client.instanceOperations().getTabletServers();
       Collections.sort(tservers);
-      assertEquals(3, tservers.size());
+      assertTrue(tservers.size() >= 2, "Expected at least 2 tservers, but saw 
" + tservers);
 
       final String tableName = names[0];
 
@@ -163,16 +162,17 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
 
       TestIngest.createTable(client, params);
 
-      // Confirm 4 tablets, spread across 3 tablet servers
+      // Confirm 4 tablets, spread across all tablet servers
       client.instanceOperations().waitForBalance();
 
       final List<TabletMetadata> tm = getLocations(((ClientContext) 
client).getAmple(),
           client.tableOperations().tableIdMap().get(tableName));
-      assertEquals(4, tm.size()); // 3 tablets
+      assertEquals(4, tm.size());
 
       final Set<String> tabletLocations = new TreeSet<>();
       tm.forEach(t -> tabletLocations.add(t.getLocation().getHostPort()));
-      assertEquals(3, tabletLocations.size()); // 3 locations
+      assertTrue(tabletLocations.size() >= 2,
+          "Expected at least 2 tablet locations, but saw " + tabletLocations);
 
       // both collections are sorted
       assertIterableEquals(tservers, tabletLocations);
@@ -281,9 +281,11 @@ public class MiniAccumuloClusterClassLoaderFactoryTest 
extends SharedMiniCluster
 
   private Set<Path> getReferencedFiles(Path workingDirPath) throws IOException 
{
     // get all files in subdirectories in working directory
-    return Files.walk(workingDirPath).filter(p -> p.toFile().isFile())
-        .filter(p -> p.getNameCount() == workingDirPath.getNameCount() + 
2).map(Path::getFileName)
-        .collect(Collectors.toSet());
+    try (var s = Files.walk(workingDirPath).filter(p -> p.toFile().isFile())
+        .filter(p -> p.getNameCount() == workingDirPath.getNameCount() + 2)
+        .map(Path::getFileName)) {
+      return s.collect(Collectors.toSet());
+    }
   }
 
   private int countExpectedValues(AccumuloClient client, String table, byte[] 
expectedValue)
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/TestUtils.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/TestUtils.java
index 0a4af81..1ad53ed 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/TestUtils.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/TestUtils.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -38,7 +39,6 @@ import org.apache.hadoop.hdfs.DFSConfigKeys;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.util.resource.PathResource;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
@@ -124,9 +124,15 @@ public class TestUtils {
   }
 
   public static Server getJetty(Path resourceDirectory) throws Exception {
-    PathResource directory = new PathResource(resourceDirectory);
     ResourceHandler handler = new ResourceHandler();
-    handler.setBaseResource(directory);
+    // work with jetty 11 or 12, so we can build with Accumulo 2.1 or 4.0
+    Method m;
+    try {
+      m = handler.getClass().getMethod("setResourceBase", String.class); // 
jetty 11
+    } catch (NoSuchMethodException e) {
+      m = handler.getClass().getMethod("setBaseResourceAsString", 
String.class); // jetty 12
+    }
+    m.invoke(handler, resourceDirectory.toString());
 
     Server jetty = new Server(0);
     jetty.setHandler(handler);
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/URLTypesTest.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/URLTypesTest.java
index cdf64b1..eee04bc 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/URLTypesTest.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/URLTypesTest.java
@@ -55,9 +55,11 @@ public class URLTypesTest {
     URL jarPath = URLTypesTest.class.getResource("/HelloWorld.jar");
     assertNotNull(jarPath);
     var p = Path.of(jarPath.toURI());
+    var parent = p.getParent();
+    assertNotNull(parent);
     final long origFileSize = TestUtils.getFileSize(p);
 
-    Server jetty = TestUtils.getJetty(p.getParent());
+    Server jetty = TestUtils.getJetty(parent);
     LOG.debug("Jetty listening at: {}", jetty.getURI());
     URL httpPath = jetty.getURI().resolve("HelloWorld.jar").toURL();
     assertEquals(origFileSize, TestUtils.getFileSize(httpPath));
diff --git 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/manifest/ManifestTest.java
 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/manifest/ManifestTest.java
index 1519f7a..4f5cbb3 100644
--- 
a/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/manifest/ManifestTest.java
+++ 
b/modules/caching-classloader/src/test/java/org/apache/accumulo/classloader/ccl/manifest/ManifestTest.java
@@ -21,17 +21,22 @@ package org.apache.accumulo.classloader.ccl.manifest;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.io.ByteArrayInputStream;
 import java.nio.file.Path;
+import java.security.SecureRandom;
+import java.util.regex.Pattern;
 
 import org.junit.jupiter.api.Test;
 
 class ManifestTest {
 
+  private static final SecureRandom RAND = new SecureRandom();
+
   private static String mockJson(boolean withComment, boolean 
withMonitorInterval,
       int withResourceCount) {
     final String COMMA = ",";
@@ -55,8 +60,13 @@ class ManifestTest {
         if (i > 0) {
           json.append(COMMA);
         }
-        json.append("{'location': 'file:/home/user/ClassLoaderTestA/" + i + 
".jar'").append(COMMA);
-        json.append("'algorithm': 'MOCK',").append("'checksum': '" + i + "'}");
+        var n = i;
+        if (withResourceCount == 10) {
+          // special value to make all resources the same to test deduplication
+          n = 0;
+        }
+        json.append("{'location': 'file:/home/user/ClassLoaderTestA/" + n + 
".jar'").append(COMMA);
+        json.append("'algorithm': 'MOCK',").append("'checksum': '" + n + "'}");
       }
       json.append("]");
     }
@@ -82,15 +92,16 @@ class ManifestTest {
 
   @Test
   void testDeserializing() throws Exception {
-    var json = mockJson(true, true, 3);
+    int resourceCount = RAND.nextInt(100) + 15;
+    var json = mockJson(true, true, resourceCount);
     try (var in = new ByteArrayInputStream(json.getBytes(UTF_8))) {
       var manifest = Manifest.fromStream(in);
       assertEquals("an optional comment", manifest.getComment());
       assertEquals(5, manifest.getMonitorIntervalSeconds());
-      assertEquals(3, manifest.getResources().size());
+      assertEquals(resourceCount, manifest.getResources().size());
       var resources = manifest.getResources();
       var iter = resources.iterator();
-      for (int i = 0; i < 3; i++) {
+      for (int i = 0; i < resourceCount; i++) {
         var r = iter.next();
         assertEquals(i + ".jar", r.getFileName());
         assertEquals("MOCK", r.getAlgorithm());
@@ -126,4 +137,34 @@ class ManifestTest {
       assertNull(Manifest.fromStream(in));
     }
   }
+
+  @Test
+  void testDeserializationWithDeduplication() throws Exception {
+    Pattern numExpectedJar = Pattern.compile("0[.]jar");
+    var json = mockJson(true, true, 1);
+    var jsonWithDuplicates = mockJson(true, true, 10);
+    assertNotEquals(json, jsonWithDuplicates);
+    assertEquals(1, numExpectedJar.matcher(json).results().count());
+    assertEquals(10, 
numExpectedJar.matcher(jsonWithDuplicates).results().count());
+    final Manifest manifest;
+    try (var in = new ByteArrayInputStream(json.getBytes(UTF_8))) {
+      manifest = Manifest.fromStream(in);
+    }
+    final Manifest manifestFromDuplicates;
+    try (var in = new 
ByteArrayInputStream(jsonWithDuplicates.getBytes(UTF_8))) {
+      manifestFromDuplicates = Manifest.fromStream(in);
+    }
+    assertEquals(manifest, manifestFromDuplicates);
+    assertEquals(manifest.toJson(), manifestFromDuplicates.toJson());
+    assertEquals("an optional comment", manifest.getComment());
+    assertEquals(5, manifest.getMonitorIntervalSeconds());
+    assertEquals(1, manifest.getResources().size());
+    var resources = manifest.getResources();
+    var iter = resources.iterator();
+    var r = iter.next();
+    assertEquals("0.jar", r.getFileName());
+    assertEquals("MOCK", r.getAlgorithm());
+    assertEquals(String.valueOf(0), r.getChecksum());
+    assertFalse(iter.hasNext());
+  }
 }
diff --git 
a/modules/example-iterators-a/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
 
b/modules/example-iterators-a/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
index f630aea..c9647c7 100644
--- 
a/modules/example-iterators-a/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
+++ 
b/modules/example-iterators-a/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
@@ -38,31 +38,38 @@ public class ExampleIterator implements 
SortedKeyValueIterator<Key,Value> {
 
   protected SortedKeyValueIterator<Key,Value> source;
 
+  @Override
   public ExampleIterator deepCopy(IteratorEnvironment env) {
     throw new UnsupportedOperationException();
   }
 
+  @Override
   public Key getTopKey() {
     return source.getTopKey();
   }
 
+  @Override
   public Value getTopValue() {
     return new Value("foo".getBytes(UTF_8));
   }
 
+  @Override
   public boolean hasTop() {
     return source.hasTop();
   }
 
+  @Override
   public void init(SortedKeyValueIterator<Key,Value> source, 
Map<String,String> options,
       IteratorEnvironment env) {
     this.source = source;
   }
 
+  @Override
   public void next() throws IOException {
     source.next();
   }
 
+  @Override
   public void seek(Range range, Collection<ByteSequence> columnFamilies, 
boolean inclusive)
       throws IOException {
     source.seek(range, columnFamilies, inclusive);
diff --git 
a/modules/example-iterators-b/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
 
b/modules/example-iterators-b/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
index 868bd89..9845ab6 100644
--- 
a/modules/example-iterators-b/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
+++ 
b/modules/example-iterators-b/src/main/java/org/apache/accumulo/classloader/vfs/examples/ExampleIterator.java
@@ -38,31 +38,38 @@ public class ExampleIterator implements 
SortedKeyValueIterator<Key,Value> {
 
   protected SortedKeyValueIterator<Key,Value> source;
 
+  @Override
   public ExampleIterator deepCopy(IteratorEnvironment env) {
     throw new UnsupportedOperationException();
   }
 
+  @Override
   public Key getTopKey() {
     return source.getTopKey();
   }
 
+  @Override
   public Value getTopValue() {
     return new Value("bar".getBytes(UTF_8));
   }
 
+  @Override
   public boolean hasTop() {
     return source.hasTop();
   }
 
+  @Override
   public void init(SortedKeyValueIterator<Key,Value> source, 
Map<String,String> options,
       IteratorEnvironment env) {
     this.source = source;
   }
 
+  @Override
   public void next() throws IOException {
     source.next();
   }
 
+  @Override
   public void seek(Range range, Collection<ByteSequence> columnFamilies, 
boolean inclusive)
       throws IOException {
     source.seek(range, columnFamilies, inclusive);
diff --git 
a/modules/hdfs-urlstreamhandler-provider/src/main/java/org/apache/accumulo/classloader/hdfsurlstreamhandlerprovider/HdfsURLStreamHandlerProvider.java
 
b/modules/hdfs-urlstreamhandler-provider/src/main/java/org/apache/accumulo/classloader/hdfsurlstreamhandlerprovider/HdfsURLStreamHandlerProvider.java
index 861d0f4..eb4e369 100644
--- 
a/modules/hdfs-urlstreamhandler-provider/src/main/java/org/apache/accumulo/classloader/hdfsurlstreamhandlerprovider/HdfsURLStreamHandlerProvider.java
+++ 
b/modules/hdfs-urlstreamhandler-provider/src/main/java/org/apache/accumulo/classloader/hdfsurlstreamhandlerprovider/HdfsURLStreamHandlerProvider.java
@@ -51,7 +51,7 @@ public class HdfsURLStreamHandlerProvider extends 
URLStreamHandlerProvider {
 
     private InputStream is;
 
-    public HdfsURLConnection(URL url) {
+    HdfsURLConnection(URL url) {
       super(requireNonNull(url, "null url argument"));
     }
 
diff --git a/pom.xml b/pom.xml
index 99da47f..b2e9d53 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,7 +130,10 @@ under the License.
     <rat.consoleOutput>true</rat.consoleOutput>
     
<sourceReleaseAssemblyDescriptor>source-release-tar</sourceReleaseAssemblyDescriptor>
     <surefire.failIfNoSpecifiedTests>false</surefire.failIfNoSpecifiedTests>
+    <version.accumulo>2.1.4</version.accumulo>
     <version.auto-service>1.1.1</version.auto-service>
+    <version.errorprone>2.47.0</version.errorprone>
+    <version.httpclient5>5.6</version.httpclient5>
   </properties>
   <dependencyManagement>
     <dependencies>
@@ -138,7 +141,7 @@ under the License.
         <!-- most dependencies will be provided by the accumulo installation 
-->
         <groupId>org.apache.accumulo</groupId>
         <artifactId>accumulo-project</artifactId>
-        <version>2.1.4</version>
+        <version>${version.accumulo}</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -155,7 +158,7 @@ under the License.
       <dependency>
         <groupId>org.apache.httpcomponents.client5</groupId>
         <artifactId>httpclient5</artifactId>
-        <version>5.6</version>
+        <version>${version.httpclient5}</version>
       </dependency>
     </dependencies>
   </dependencyManagement>
@@ -668,6 +671,29 @@ under the License.
     </plugins>
   </build>
   <profiles>
+    <profile>
+      <!-- This profile skips all Quality Assurance checks; activate with 
-PskipQA OR -DskipQA  -->
+      <id>skipQA</id>
+      <activation>
+        <property>
+          <name>skipQA</name>
+        </property>
+      </activation>
+      <properties>
+        <apilyzer.skip>true</apilyzer.skip>
+        <checkstyle.skip>true</checkstyle.skip>
+        <formatter.skip>true</formatter.skip>
+        <impsort.skip>true</impsort.skip>
+        <mdep.analyze.skip>true</mdep.analyze.skip>
+        <modernizer.skip>true</modernizer.skip>
+        <rat.skip>true</rat.skip>
+        <skipITs>true</skipITs>
+        <skipTests>true</skipTests>
+        <sort.skip>true</sort.skip>
+        <spotbugs.skip>true</spotbugs.skip>
+        <warbucks.skip>true</warbucks.skip>
+      </properties>
+    </profile>
     <profile>
       <id>accumulo-release</id>
       <properties>
@@ -893,5 +919,58 @@ under the License.
         </pluginManagement>
       </build>
     </profile>
+    <profile>
+      <!-- This profile uses the Google ErrorProne tool to perform static code 
analysis at
+      compile time. Auto-generated code is not checked.
+      See: https://errorprone.info/bugpatterns for list of available bug 
patterns.-->
+      <id>errorprone</id>
+      <activation>
+        <property>
+          <name>errorprone</name>
+        </property>
+      </activation>
+      <properties>
+        <!-- forking is required for -J options to take effect -->
+        <maven.compiler.fork>true</maven.compiler.fork>
+      </properties>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <compilerArgs>
+                <arg>-XDcompilePolicy=simple</arg>
+                <arg>-XDaddTypeAnnotationsToSymbol=true</arg>
+                <arg>--should-stop=ifError=FLOW</arg>
+                <arg>
+                  -Xplugin:ErrorProne \
+                  <!-- this complains about using LinkedHashSet instead of Set
+                    but we need to use that to preserve set order -->
+                  -Xep:NonApiType:OFF \
+                </arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
+                
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
+                
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
+                
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
+              </compilerArgs>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>com.google.errorprone</groupId>
+                  <artifactId>error_prone_core</artifactId>
+                  <version>${version.errorprone}</version>
+                </path>
+              </annotationProcessorPaths>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
   </profiles>
 </project>


Reply via email to