Skip to content

Commit 2792bed

Browse files
Flossyclaude
andcommitted
Fix issues #40 and #41: Code quality improvements
Issue #40: Extract hardcoded buffer size 8192 to shared constant - Created ClassLoaderConstants utility class with DEFAULT_BUFFER_SIZE = 8192 - Replaced hardcoded values in 6 files: MavenNexusClassSource, NexusClassSource (2 locations), RestApiClassSource, MinioClassSource, HdfsClassSource - Replaced local constants in 3 files: MavenRepositoryClassSource, RemoteClassSource, JarRemoteClassSource Issue #41: Fix thread-safety in HashMap caches - Replaced HashMap with ConcurrentHashMap in MavenRepositoryClassSource, MavenNexusClassSource, NexusClassSource - Added thread-safety documentation to class Javadocs - Ensures safe concurrent class loading operations All 287 tests pass Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent bb0a8dd commit 2792bed

9 files changed

Lines changed: 63 additions & 17 deletions

File tree

src/main/java/org/flossware/jclassloader/JarRemoteClassSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import org.flossware.jclassloader.util.ClassNameUtil;
44
import org.slf4j.Logger;
5+
6+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
57
import org.slf4j.LoggerFactory;
68

79
import java.io.ByteArrayOutputStream;
@@ -28,7 +30,6 @@ public class JarRemoteClassSource implements ClassSource, AutoCloseable {
2830
private static final Logger logger = LoggerFactory.getLogger(JarRemoteClassSource.class);
2931
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 10000;
3032
private static final int DEFAULT_READ_TIMEOUT_MS = 30000;
31-
private static final int DEFAULT_BUFFER_SIZE = 8192;
3233

3334
private final String jarUrl;
3435
private final AuthConfig authConfig;

src/main/java/org/flossware/jclassloader/MavenNexusClassSource.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@
33
import org.flossware.jclassloader.util.ClassNameUtil;
44

55
import java.io.ByteArrayOutputStream;
6+
7+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
68
import java.io.IOException;
79
import java.io.InputStream;
810
import java.net.HttpURLConnection;
911
import java.net.URL;
1012
import java.util.ArrayList;
1113
import java.util.Base64;
12-
import java.util.HashMap;
1314
import java.util.List;
1415
import java.util.Map;
1516
import java.util.Objects;
17+
import java.util.concurrent.ConcurrentHashMap;
1618
import java.util.jar.JarEntry;
1719
import java.util.jar.JarInputStream;
1820

1921
/**
2022
* ClassSource implementation for loading classes from Maven artifacts stored in Nexus.
2123
* Downloads JARs from Nexus and extracts class files from them.
2224
* Includes in-memory caching of loaded classes for performance.
25+
*
26+
* <p><b>Thread Safety:</b> This class is thread-safe. The internal class cache uses
27+
* ConcurrentHashMap to support concurrent class loading operations.</p>
2328
*/
2429
public class MavenNexusClassSource implements ClassSource {
2530
private final String nexusUrl;
@@ -50,7 +55,7 @@ public MavenNexusClassSource(String nexusUrl, String repository, List<MavenArtif
5055
this.repository = Objects.requireNonNull(repository, "repository cannot be null");
5156
this.artifacts = new ArrayList<>(artifacts);
5257
this.authConfig = authConfig != null ? authConfig : AuthConfig.none();
53-
this.classCache = new HashMap<>();
58+
this.classCache = new ConcurrentHashMap<>();
5459
}
5560

5661
/**
@@ -124,7 +129,7 @@ private byte[] extractClassFromJar(String jarUrl, String classFileName) throws I
124129
while ((entry = jarIn.getNextJarEntry()) != null) {
125130
if (entry.getName().equals(classFileName) && !entry.isDirectory()) {
126131
ByteArrayOutputStream out = new ByteArrayOutputStream();
127-
byte[] buffer = new byte[8192];
132+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
128133
int bytesRead;
129134
while ((bytesRead = jarIn.read(buffer)) != -1) {
130135
out.write(buffer, 0, bytesRead);

src/main/java/org/flossware/jclassloader/MavenRepositoryClassSource.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,29 @@
33
import org.flossware.jclassloader.util.ClassNameUtil;
44

55
import java.io.ByteArrayOutputStream;
6+
7+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
68
import java.io.IOException;
79
import java.io.InputStream;
810
import java.net.HttpURLConnection;
911
import java.net.URL;
1012
import java.util.ArrayList;
11-
import java.util.HashMap;
1213
import java.util.List;
1314
import java.util.Map;
1415
import java.util.Objects;
16+
import java.util.concurrent.ConcurrentHashMap;
1517
import java.util.jar.JarEntry;
1618
import java.util.jar.JarInputStream;
1719

1820
/**
1921
* ClassSource implementation for loading classes from Maven repositories (Maven Central, custom repos).
2022
* Downloads JARs from the repository and extracts class files from them.
2123
* Includes in-memory caching of loaded classes for performance.
24+
*
25+
* <p><b>Thread Safety:</b> This class is thread-safe. The internal class cache uses
26+
* ConcurrentHashMap to support concurrent class loading operations.</p>
2227
*/
2328
public class MavenRepositoryClassSource implements ClassSource {
24-
private static final int DEFAULT_BUFFER_SIZE = 8192;
25-
2629
private final String repositoryUrl;
2730
private final List<MavenArtifact> artifacts;
2831
private final AuthConfig authConfig;
@@ -48,7 +51,7 @@ public MavenRepositoryClassSource(String repositoryUrl, List<MavenArtifact> arti
4851
this.repositoryUrl = repositoryUrl.endsWith("/") ? repositoryUrl : repositoryUrl + "/";
4952
this.artifacts = new ArrayList<>(artifacts);
5053
this.authConfig = authConfig != null ? authConfig : AuthConfig.none();
51-
this.classCache = new HashMap<>();
54+
this.classCache = new ConcurrentHashMap<>();
5255
}
5356

5457
/**

src/main/java/org/flossware/jclassloader/NexusClassSource.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33
import org.flossware.jclassloader.util.ClassNameUtil;
44

55
import java.io.ByteArrayOutputStream;
6+
7+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
68
import java.io.IOException;
79
import java.io.InputStream;
810
import java.net.HttpURLConnection;
911
import java.net.URL;
1012
import java.util.Base64;
11-
import java.util.HashMap;
1213
import java.util.Map;
1314
import java.util.Objects;
15+
import java.util.concurrent.ConcurrentHashMap;
1416
import java.util.jar.JarEntry;
1517
import java.util.jar.JarInputStream;
1618

1719
/**
1820
* ClassSource implementation for loading classes from Sonatype Nexus repositories.
1921
* Supports both RAW repositories (direct .class files) and MAVEN repositories (classes from JARs).
22+
*
23+
* <p><b>Thread Safety:</b> This class is thread-safe. The internal JAR cache uses
24+
* ConcurrentHashMap to support concurrent class loading operations.</p>
2025
*/
2126
public class NexusClassSource implements ClassSource {
2227
private final String nexusUrl;
@@ -50,7 +55,7 @@ public NexusClassSource(String nexusUrl, String repository, NexusMode mode, Auth
5055
this.repository = Objects.requireNonNull(repository, "repository cannot be null");
5156
this.mode = mode != null ? mode : NexusMode.MAVEN;
5257
this.authConfig = authConfig != null ? authConfig : AuthConfig.none();
53-
this.jarCache = new HashMap<>();
58+
this.jarCache = new ConcurrentHashMap<>();
5459
}
5560

5661
/**
@@ -159,7 +164,7 @@ private byte[] fetchUrl(String urlString) throws IOException {
159164
try (InputStream in = connection.getInputStream();
160165
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
161166

162-
byte[] buffer = new byte[8192];
167+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
163168
int bytesRead;
164169
while ((bytesRead = in.read(buffer)) != -1) {
165170
out.write(buffer, 0, bytesRead);
@@ -186,7 +191,7 @@ protected byte[] loadClassFromJar(String jarUrl, String classFileName) throws IO
186191
while ((entry = jarIn.getNextJarEntry()) != null) {
187192
if (entry.getName().equals(classFileName)) {
188193
ByteArrayOutputStream out = new ByteArrayOutputStream();
189-
byte[] buffer = new byte[8192];
194+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
190195
int bytesRead;
191196
while ((bytesRead = jarIn.read(buffer)) != -1) {
192197
out.write(buffer, 0, bytesRead);

src/main/java/org/flossware/jclassloader/RemoteClassSource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.flossware.jclassloader.util.ClassNameUtil;
44

55
import java.io.ByteArrayOutputStream;
6+
7+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
68
import java.io.IOException;
79
import java.io.InputStream;
810
import java.net.HttpURLConnection;
@@ -16,8 +18,6 @@
1618
* Supports optional authentication (Basic or Bearer token), configurable timeouts, and retry logic.
1719
*/
1820
public class RemoteClassSource implements ClassSource {
19-
private static final int DEFAULT_BUFFER_SIZE = 8192;
20-
2121
/** Default connection timeout in milliseconds (10 seconds) */
2222
private static final int DEFAULT_CONNECT_TIMEOUT_MS = 10000;
2323

src/main/java/org/flossware/jclassloader/RestApiClassSource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.flossware.jclassloader;
22

33
import java.io.ByteArrayOutputStream;
4+
5+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
46
import java.io.IOException;
57
import java.io.InputStream;
68
import java.net.HttpURLConnection;
@@ -66,7 +68,7 @@ public byte[] loadClassData(String className) throws IOException {
6668
try (InputStream in = connection.getInputStream();
6769
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
6870

69-
byte[] buffer = new byte[8192];
71+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
7072
int bytesRead;
7173
while ((bytesRead = in.read(buffer)) != -1) {
7274
out.write(buffer, 0, bytesRead);

src/main/java/org/flossware/jclassloader/filesystem/HdfsClassSource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.flossware.jclassloader.filesystem;
22

33
import org.apache.hadoop.conf.Configuration;
4+
5+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
46
import org.apache.hadoop.fs.FileSystem;
57
import org.apache.hadoop.fs.Path;
68
import org.flossware.jclassloader.ClassSource;
@@ -32,7 +34,7 @@ public byte[] loadClassData(String className) throws IOException {
3234
try (InputStream in = hdfs.open(classPath);
3335
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
3436

35-
byte[] buffer = new byte[8192];
37+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
3638
int bytesRead;
3739
while ((bytesRead = in.read(buffer)) != -1) {
3840
out.write(buffer, 0, bytesRead);

src/main/java/org/flossware/jclassloader/objectstore/MinioClassSource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.flossware.jclassloader.objectstore;
22

33
import io.minio.GetObjectArgs;
4+
5+
import static org.flossware.jclassloader.util.ClassLoaderConstants.DEFAULT_BUFFER_SIZE;
46
import io.minio.MinioClient;
57
import io.minio.StatObjectArgs;
68
import org.flossware.jclassloader.ClassSource;
@@ -44,7 +46,7 @@ public byte[] loadClassData(String className) throws IOException {
4446
.build());
4547
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
4648

47-
byte[] buffer = new byte[8192];
49+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
4850
int bytesRead;
4951
while ((bytesRead = stream.read(buffer)) != -1) {
5052
out.write(buffer, 0, bytesRead);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.flossware.jclassloader.util;
2+
3+
/**
4+
* Common constants used throughout the JClassLoader library.
5+
*
6+
* <p>This class provides shared constant values for buffer sizes, timeouts,
7+
* and other configuration parameters used across multiple ClassSource implementations.</p>
8+
*/
9+
public final class ClassLoaderConstants {
10+
11+
private ClassLoaderConstants() {
12+
throw new AssertionError("Utility class - do not instantiate");
13+
}
14+
15+
/**
16+
* Default buffer size for reading class data (8KB).
17+
*
18+
* <p>This is a common default for I/O operations because:</p>
19+
* <ul>
20+
* <li>Matches most filesystem block sizes</li>
21+
* <li>Good balance between memory usage and I/O performance</li>
22+
* <li>Standard in many Java libraries (BufferedInputStream default is 8192)</li>
23+
* </ul>
24+
*/
25+
public static final int DEFAULT_BUFFER_SIZE = 8192;
26+
}

0 commit comments

Comments
 (0)