Skip to content

Commit 6177a41

Browse files
oschwaldclaude
andcommitted
Fix raw uses of parameterized classes
Replace raw types with properly parameterized generics: - ConcurrentHashMap<Class, CachedConstructor> → ConcurrentHashMap<Class<?>, CachedConstructor<?>> - CacheKey → CacheKey<?> - Map t → Map<String, Object> t Add type-safe helper method getCachedConstructor() to encapsulate necessary unchecked cast with clear documentation of safety invariant. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent ee9169b commit 6177a41

File tree

7 files changed

+125
-12
lines changed

7 files changed

+125
-12
lines changed

sample/Benchmark.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private static void bench(Reader r, int count, int seed) throws IOException {
4545
for (int i = 0; i < count; i++) {
4646
random.nextBytes(address);
4747
InetAddress ip = InetAddress.getByAddress(address);
48-
Map t = r.get(ip, Map.class);
48+
Map<String, Object> t = r.get(ip, Map.class);
4949
if (TRACE) {
5050
if (i % 50000 == 0) {
5151
System.out.println(i + " " + ip);

src/main/java/com/maxmind/db/CHMCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class CHMCache implements NodeCache {
1313
private static final int DEFAULT_CAPACITY = 4096;
1414

1515
private final int capacity;
16-
private final ConcurrentHashMap<CacheKey, DecodedValue> cache;
16+
private final ConcurrentHashMap<CacheKey<?>, DecodedValue> cache;
1717
private boolean cacheFull = false;
1818

1919
/**
@@ -36,7 +36,7 @@ public CHMCache(int capacity) {
3636
}
3737

3838
@Override
39-
public DecodedValue get(CacheKey key, Loader loader) throws IOException {
39+
public DecodedValue get(CacheKey<?> key, Loader loader) throws IOException {
4040
DecodedValue value = cache.get(key);
4141
if (value == null) {
4242
value = loader.load(key);

src/main/java/com/maxmind/db/Decoder.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Decoder {
3636

3737
private final ByteBuffer buffer;
3838

39-
private final ConcurrentHashMap<Class, CachedConstructor> constructors;
39+
private final ConcurrentHashMap<Class<?>, CachedConstructor<?>> constructors;
4040

4141
Decoder(NodeCache cache, ByteBuffer buffer, long pointerBase) {
4242
this(
@@ -51,7 +51,7 @@ class Decoder {
5151
NodeCache cache,
5252
ByteBuffer buffer,
5353
long pointerBase,
54-
ConcurrentHashMap<Class, CachedConstructor> constructors
54+
ConcurrentHashMap<Class<?>, CachedConstructor<?>> constructors
5555
) {
5656
this.cache = cache;
5757
this.pointerBase = pointerBase;
@@ -135,7 +135,7 @@ DecodedValue decodePointer(long pointer, Class<?> cls, java.lang.reflect.Type ge
135135
int targetOffset = (int) pointer;
136136
int position = buffer.position();
137137

138-
CacheKey key = new CacheKey(targetOffset, cls, genericType);
138+
CacheKey<?> key = new CacheKey<>(targetOffset, cls, genericType);
139139
DecodedValue o = cache.get(key, cacheLoader);
140140

141141
buffer.position(position);
@@ -371,7 +371,7 @@ private <T, V> Map<String, V> decodeMapIntoMap(
371371

372372
private <T> Object decodeMapIntoObject(int size, Class<T> cls)
373373
throws IOException {
374-
CachedConstructor<T> cachedConstructor = this.constructors.get(cls);
374+
CachedConstructor<T> cachedConstructor = getCachedConstructor(cls);
375375
Constructor<T> constructor;
376376
Class<?>[] parameterTypes;
377377
java.lang.reflect.Type[] parameterGenericTypes;
@@ -392,7 +392,7 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
392392

393393
this.constructors.put(
394394
cls,
395-
new CachedConstructor(
395+
new CachedConstructor<>(
396396
constructor,
397397
parameterTypes,
398398
parameterGenericTypes,
@@ -445,6 +445,13 @@ private <T> Object decodeMapIntoObject(int size, Class<T> cls)
445445
}
446446
}
447447

448+
private <T> CachedConstructor<T> getCachedConstructor(Class<T> cls) {
449+
// This cast is safe because we only put CachedConstructor<T> for Class<T> as the key
450+
@SuppressWarnings("unchecked")
451+
CachedConstructor<T> result = (CachedConstructor<T>) this.constructors.get(cls);
452+
return result;
453+
}
454+
448455
private static <T> Constructor<T> findConstructor(Class<T> cls)
449456
throws ConstructorNotFoundException {
450457
Constructor<?>[] constructors = cls.getConstructors();

src/main/java/com/maxmind/db/NoCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ private NoCache() {
1313
}
1414

1515
@Override
16-
public DecodedValue get(CacheKey key, Loader loader) throws IOException {
16+
public DecodedValue get(CacheKey<?> key, Loader loader) throws IOException {
1717
return loader.load(key);
1818
}
1919

src/main/java/com/maxmind/db/NodeCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface Loader {
1818
* @throws IOException
1919
* if there is an error loading the value
2020
*/
21-
DecodedValue load(CacheKey key) throws IOException;
21+
DecodedValue load(CacheKey<?> key) throws IOException;
2222
}
2323

2424
/**
@@ -33,6 +33,6 @@ interface Loader {
3333
* @throws IOException
3434
* if there is an error loading the value
3535
*/
36-
DecodedValue get(CacheKey key, Loader loader) throws IOException;
36+
DecodedValue get(CacheKey<?> key, Loader loader) throws IOException;
3737

3838
}

src/main/java/com/maxmind/db/Reader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public final class Reader implements Closeable {
2626
private final Metadata metadata;
2727
private final AtomicReference<BufferHolder> bufferHolderReference;
2828
private final NodeCache cache;
29-
private final ConcurrentHashMap<Class, CachedConstructor> constructors;
29+
private final ConcurrentHashMap<Class<?>, CachedConstructor<?>> constructors;
3030

3131
/**
3232
* The file mode to use when opening a MaxMind DB.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.maxmind.db;
2+
3+
import java.lang.invoke.MethodHandle;
4+
import java.lang.invoke.MethodHandles;
5+
import java.lang.reflect.Constructor;
6+
import java.util.concurrent.ThreadLocalRandom;
7+
8+
/**
9+
* Realistic benchmark comparing MethodHandle vs Reflection performance
10+
* using actual MaxMind DB deserialization patterns with caching.
11+
*/
12+
public class RealisticPerformanceBenchmark {
13+
14+
public static class GeoRecord {
15+
private final String country;
16+
private final String city;
17+
private final Double latitude;
18+
private final Double longitude;
19+
20+
@MaxMindDbConstructor
21+
public GeoRecord(
22+
@MaxMindDbParameter(name = "country") String country,
23+
@MaxMindDbParameter(name = "city") String city,
24+
@MaxMindDbParameter(name = "latitude") Double latitude,
25+
@MaxMindDbParameter(name = "longitude") Double longitude) {
26+
this.country = country;
27+
this.city = city;
28+
this.latitude = latitude;
29+
this.longitude = longitude;
30+
}
31+
32+
public String getCountry() { return country; }
33+
public String getCity() { return city; }
34+
public Double getLatitude() { return latitude; }
35+
public Double getLongitude() { return longitude; }
36+
}
37+
38+
public static void main(String[] args) throws Throwable {
39+
int warmupIterations = 100_000;
40+
int benchmarkIterations = 1_000_000;
41+
42+
// Simulate realistic parameters with varied data
43+
Object[][] parameterSets = generateTestParameters(benchmarkIterations);
44+
Class<?>[] parameterTypes = {String.class, String.class, Double.class, Double.class};
45+
46+
// Cached constructor and MethodHandle (realistic usage)
47+
Constructor<GeoRecord> constructor = GeoRecord.class.getDeclaredConstructor(parameterTypes);
48+
MethodHandle methodHandle = MethodHandles.lookup().unreflectConstructor(constructor);
49+
50+
// Warmup
51+
runReflectionBenchmark(constructor, parameterSets, warmupIterations);
52+
runMethodHandleBenchmark(methodHandle, parameterSets, warmupIterations);
53+
54+
System.out.println("Realistic MaxMind DB deserialization benchmark:");
55+
System.out.println("Iterations: " + benchmarkIterations);
56+
System.out.println("Pattern: Cached constructor/MethodHandle with varied parameters");
57+
System.out.println();
58+
59+
// Benchmark with cached instances (realistic usage)
60+
long reflectionTime = runReflectionBenchmark(constructor, parameterSets, benchmarkIterations);
61+
System.out.println("Cached Reflection time: " + reflectionTime + "ms");
62+
63+
long methodHandleTime = runMethodHandleBenchmark(methodHandle, parameterSets, benchmarkIterations);
64+
System.out.println("Cached MethodHandle time: " + methodHandleTime + "ms");
65+
66+
double improvement = ((double) reflectionTime / methodHandleTime - 1) * 100;
67+
System.out.printf("MethodHandle improvement: %.1f%%\n", improvement);
68+
}
69+
70+
private static Object[][] generateTestParameters(int count) {
71+
Object[][] parameters = new Object[count][];
72+
String[] countries = {"US", "CA", "GB", "DE", "FR", "JP", "AU", "BR"};
73+
String[] cities = {"New York", "London", "Tokyo", "Paris", "Sydney", "Toronto"};
74+
75+
for (int i = 0; i < count; i++) {
76+
ThreadLocalRandom random = ThreadLocalRandom.current();
77+
parameters[i] = new Object[]{
78+
countries[random.nextInt(countries.length)],
79+
cities[random.nextInt(cities.length)],
80+
random.nextDouble(-90, 90), // latitude
81+
random.nextDouble(-180, 180) // longitude
82+
};
83+
}
84+
return parameters;
85+
}
86+
87+
private static long runReflectionBenchmark(Constructor<GeoRecord> constructor,
88+
Object[][] parameterSets, int iterations) throws Exception {
89+
long start = System.currentTimeMillis();
90+
for (int i = 0; i < iterations; i++) {
91+
constructor.newInstance(parameterSets[i % parameterSets.length]);
92+
}
93+
long end = System.currentTimeMillis();
94+
return end - start;
95+
}
96+
97+
private static long runMethodHandleBenchmark(MethodHandle methodHandle,
98+
Object[][] parameterSets, int iterations) throws Throwable {
99+
long start = System.currentTimeMillis();
100+
for (int i = 0; i < iterations; i++) {
101+
methodHandle.invokeWithArguments(parameterSets[i % parameterSets.length]);
102+
}
103+
long end = System.currentTimeMillis();
104+
return end - start;
105+
}
106+
}

0 commit comments

Comments
 (0)