diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76163831..e797c2f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: matrix: distribution: ['zulu'] os: [ubuntu-latest, windows-latest, macos-latest] - version: [ 11, 17, 21, 22 ] + version: [ 17, 21, 24 ] steps: - uses: actions/checkout@v5 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f650c70..7b80d986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.0.0 +------------------ + +* Java 17 or greater is now required. + 3.2.0 (2025-05-28) ------------------ diff --git a/README.md b/README.md index c55f0f87..37f471c8 100644 --- a/README.md +++ b/README.md @@ -120,8 +120,8 @@ public class Lookup { } ``` -You can also use the reader object to iterate over the database. -The `reader.networks()` and `reader.networksWithin()` methods can +You can also use the reader object to iterate over the database. +The `reader.networks()` and `reader.networksWithin()` methods can be used for this purpose. ```java @@ -207,7 +207,7 @@ specific to this reader, please [contact MaxMind support](https://www.maxmind.co ## Requirements ## -This API requires Java 11 or greater. +This API requires Java 17 or greater. ## Contributing ## diff --git a/pom.xml b/pom.xml index b8d95115..70c31ea9 100644 --- a/pom.xml +++ b/pom.xml @@ -147,9 +147,9 @@ maven-compiler-plugin 3.14.0 - 11 - 11 - 11 + 17 + 17 + 17 diff --git a/sample/Benchmark.java b/sample/Benchmark.java index 3d73011b..b0f8fd2e 100644 --- a/sample/Benchmark.java +++ b/sample/Benchmark.java @@ -45,7 +45,7 @@ private static void bench(Reader r, int count, int seed) throws IOException { for (int i = 0; i < count; i++) { random.nextBytes(address); InetAddress ip = InetAddress.getByAddress(address); - Map t = r.get(ip, Map.class); + Map t = r.get(ip, Map.class); if (TRACE) { if (i % 50000 == 0) { System.out.println(i + " " + ip); diff --git a/src/main/java/com/maxmind/db/CHMCache.java b/src/main/java/com/maxmind/db/CHMCache.java index f9f63292..0b22d4cc 100644 --- a/src/main/java/com/maxmind/db/CHMCache.java +++ b/src/main/java/com/maxmind/db/CHMCache.java @@ -13,7 +13,7 @@ public class CHMCache implements NodeCache { private static final int DEFAULT_CAPACITY = 4096; private final int capacity; - private final ConcurrentHashMap cache; + private final ConcurrentHashMap, DecodedValue> cache; private boolean cacheFull = false; /** @@ -36,7 +36,7 @@ public CHMCache(int capacity) { } @Override - public DecodedValue get(CacheKey key, Loader loader) throws IOException { + public DecodedValue get(CacheKey key, Loader loader) throws IOException { DecodedValue value = cache.get(key); if (value == null) { value = loader.load(key); diff --git a/src/main/java/com/maxmind/db/CacheKey.java b/src/main/java/com/maxmind/db/CacheKey.java index d62c0843..3a2c0d46 100644 --- a/src/main/java/com/maxmind/db/CacheKey.java +++ b/src/main/java/com/maxmind/db/CacheKey.java @@ -6,61 +6,9 @@ * of the value. * * @param the type of value + * @param offset the offset of the value in the database file + * @param cls the class of the value + * @param type the type of the value */ -public final class CacheKey { - private final int offset; - private final Class cls; - private final java.lang.reflect.Type type; - - CacheKey(int offset, Class cls, java.lang.reflect.Type type) { - this.offset = offset; - this.cls = cls; - this.type = type; - } - - int getOffset() { - return this.offset; - } - - Class getCls() { - return this.cls; - } - - java.lang.reflect.Type getType() { - return this.type; - } - - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - - CacheKey other = (CacheKey) o; - - if (this.offset != other.offset) { - return false; - } - - if (this.cls == null) { - if (other.cls != null) { - return false; - } - } else if (!this.cls.equals(other.cls)) { - return false; - } - - if (this.type == null) { - return other.type == null; - } - return this.type.equals(other.type); - } - - @Override - public int hashCode() { - int result = offset; - result = 31 * result + (cls == null ? 0 : cls.hashCode()); - result = 31 * result + (type == null ? 0 : type.hashCode()); - return result; - } +public record CacheKey(int offset, Class cls, java.lang.reflect.Type type) { } diff --git a/src/main/java/com/maxmind/db/CachedConstructor.java b/src/main/java/com/maxmind/db/CachedConstructor.java index 2b5b3160..c1c5f456 100644 --- a/src/main/java/com/maxmind/db/CachedConstructor.java +++ b/src/main/java/com/maxmind/db/CachedConstructor.java @@ -3,37 +3,10 @@ import java.lang.reflect.Constructor; import java.util.Map; -final class CachedConstructor { - private final Constructor constructor; - private final Class[] parameterTypes; - private final java.lang.reflect.Type[] parameterGenericTypes; - private final Map parameterIndexes; - - CachedConstructor( - Constructor constructor, - Class[] parameterTypes, - java.lang.reflect.Type[] parameterGenericTypes, - Map parameterIndexes - ) { - this.constructor = constructor; - this.parameterTypes = parameterTypes; - this.parameterGenericTypes = parameterGenericTypes; - this.parameterIndexes = parameterIndexes; - } - - Constructor getConstructor() { - return this.constructor; - } - - Class[] getParameterTypes() { - return this.parameterTypes; - } - - java.lang.reflect.Type[] getParameterGenericTypes() { - return this.parameterGenericTypes; - } - - Map getParameterIndexes() { - return this.parameterIndexes; - } +record CachedConstructor( + Constructor constructor, + Class[] parameterTypes, + java.lang.reflect.Type[] parameterGenericTypes, + Map parameterIndexes +) { } diff --git a/src/main/java/com/maxmind/db/CtrlData.java b/src/main/java/com/maxmind/db/CtrlData.java index c9bf03f1..ff9d0ce9 100644 --- a/src/main/java/com/maxmind/db/CtrlData.java +++ b/src/main/java/com/maxmind/db/CtrlData.java @@ -1,31 +1,4 @@ package com.maxmind.db; -final class CtrlData { - private final Type type; - private final int ctrlByte; - private final int offset; - private final int size; - - CtrlData(Type type, int ctrlByte, int offset, int size) { - this.type = type; - this.ctrlByte = ctrlByte; - this.offset = offset; - this.size = size; - } - - public Type getType() { - return this.type; - } - - public int getCtrlByte() { - return this.ctrlByte; - } - - public int getOffset() { - return this.offset; - } - - public int getSize() { - return this.size; - } +record CtrlData(Type type, int ctrlByte, int offset, int size) { } diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index ab9a0218..c1c7446d 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -36,7 +36,7 @@ class Decoder { private final ByteBuffer buffer; - private final ConcurrentHashMap constructors; + private final ConcurrentHashMap, CachedConstructor> constructors; Decoder(NodeCache cache, ByteBuffer buffer, long pointerBase) { this( @@ -51,7 +51,7 @@ class Decoder { NodeCache cache, ByteBuffer buffer, long pointerBase, - ConcurrentHashMap constructors + ConcurrentHashMap, CachedConstructor> constructors ) { this.cache = cache; this.pointerBase = pointerBase; @@ -73,7 +73,7 @@ T decode(int offset, Class cls) throws IOException { } private DecodedValue decode(CacheKey key) throws IOException { - int offset = key.getOffset(); + int offset = key.offset(); if (offset >= this.buffer.capacity()) { throw new InvalidDatabaseException( "The MaxMind DB file's data section contains bad data: " @@ -81,8 +81,8 @@ private DecodedValue decode(CacheKey key) throws IOException { } this.buffer.position(offset); - Class cls = key.getCls(); - return decode(cls, key.getType()); + Class cls = key.cls(); + return decode(cls, key.type()); } private DecodedValue decode(Class cls, java.lang.reflect.Type genericType) @@ -120,16 +120,11 @@ private DecodedValue decode(Class cls, java.lang.reflect.Type genericType int size = ctrlByte & 0x1f; if (size >= 29) { - switch (size) { - case 29: - size = 29 + (0xFF & buffer.get()); - break; - case 30: - size = 285 + decodeInteger(2); - break; - default: - size = 65821 + decodeInteger(3); - } + size = switch (size) { + case 29 -> 29 + (0xFF & buffer.get()); + case 30 -> 285 + decodeInteger(2); + default -> 65821 + decodeInteger(3); + }; } return new DecodedValue(this.decodeByType(type, size, cls, genericType)); @@ -140,12 +135,12 @@ DecodedValue decodePointer(long pointer, Class cls, java.lang.reflect.Type ge int targetOffset = (int) pointer; int position = buffer.position(); - CacheKey key = new CacheKey(targetOffset, cls, genericType); + CacheKey key = new CacheKey<>(targetOffset, cls, genericType); DecodedValue o = cache.get(key, cacheLoader); buffer.position(position); return o; - } + } private Object decodeByType( Type type, @@ -158,8 +153,7 @@ private Object decodeByType( return this.decodeMap(size, cls, genericType); case ARRAY: Class elementClass = Object.class; - if (genericType instanceof ParameterizedType) { - ParameterizedType ptype = (ParameterizedType) genericType; + if (genericType instanceof ParameterizedType ptype) { java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments(); if (actualTypes.length == 1) { elementClass = (Class) actualTypes[0]; @@ -260,16 +254,13 @@ private float decodeFloat(int size) throws InvalidDatabaseException { private static boolean decodeBoolean(int size) throws InvalidDatabaseException { - switch (size) { - case 0: - return false; - case 1: - return true; - default: - throw new InvalidDatabaseException( - "The MaxMind DB file's data section contains bad data: " - + "invalid size of boolean."); - } + return switch (size) { + case 0 -> false; + case 1 -> true; + default -> throw new InvalidDatabaseException( + "The MaxMind DB file's data section contains bad data: " + + "invalid size of boolean."); + }; } private List decodeArray( @@ -319,8 +310,7 @@ private Object decodeMap( ) throws IOException { if (Map.class.isAssignableFrom(cls) || cls.equals(Object.class)) { Class valueClass = Object.class; - if (genericType instanceof ParameterizedType) { - ParameterizedType ptype = (ParameterizedType) genericType; + if (genericType instanceof ParameterizedType ptype) { java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments(); if (actualTypes.length == 2) { Class keyClass = (Class) actualTypes[0]; @@ -381,7 +371,7 @@ private Map decodeMapIntoMap( private Object decodeMapIntoObject(int size, Class cls) throws IOException { - CachedConstructor cachedConstructor = this.constructors.get(cls); + CachedConstructor cachedConstructor = getCachedConstructor(cls); Constructor constructor; Class[] parameterTypes; java.lang.reflect.Type[] parameterGenericTypes; @@ -402,7 +392,7 @@ private Object decodeMapIntoObject(int size, Class cls) this.constructors.put( cls, - new CachedConstructor( + new CachedConstructor<>( constructor, parameterTypes, parameterGenericTypes, @@ -410,10 +400,10 @@ private Object decodeMapIntoObject(int size, Class cls) ) ); } else { - constructor = cachedConstructor.getConstructor(); - parameterTypes = cachedConstructor.getParameterTypes(); - parameterGenericTypes = cachedConstructor.getParameterGenericTypes(); - parameterIndexes = cachedConstructor.getParameterIndexes(); + constructor = cachedConstructor.constructor(); + parameterTypes = cachedConstructor.parameterTypes(); + parameterGenericTypes = cachedConstructor.parameterGenericTypes(); + parameterIndexes = cachedConstructor.parameterIndexes(); } Object[] parameters = new Object[parameterTypes.length]; @@ -455,6 +445,13 @@ private Object decodeMapIntoObject(int size, Class cls) } } + private CachedConstructor getCachedConstructor(Class cls) { + // This cast is safe because we only put CachedConstructor for Class as the key + @SuppressWarnings("unchecked") + CachedConstructor result = (CachedConstructor) this.constructors.get(cls); + return result; + } + private static Constructor findConstructor(Class cls) throws ConstructorNotFoundException { Constructor[] constructors = cls.getConstructors(); @@ -495,11 +492,11 @@ private int nextValueOffset(int offset, int numberToSkip) } CtrlData ctrlData = this.getCtrlData(offset); - int ctrlByte = ctrlData.getCtrlByte(); - int size = ctrlData.getSize(); - offset = ctrlData.getOffset(); + int ctrlByte = ctrlData.ctrlByte(); + int size = ctrlData.size(); + offset = ctrlData.offset(); - Type type = ctrlData.getType(); + Type type = ctrlData.type(); switch (type) { case POINTER: int pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; @@ -555,16 +552,11 @@ private CtrlData getCtrlData(int offset) if (size >= 29) { int bytesToRead = size - 28; offset += bytesToRead; - switch (size) { - case 29: - size = 29 + (0xFF & buffer.get()); - break; - case 30: - size = 285 + decodeInteger(2); - break; - default: - size = 65821 + decodeInteger(3); - } + size = switch (size) { + case 29 -> 29 + (0xFF & buffer.get()); + case 30 -> 285 + decodeInteger(2); + default -> 65821 + decodeInteger(3); + }; } return new CtrlData(type, ctrlByte, offset, size); diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java index 93913c60..da6a0056 100644 --- a/src/main/java/com/maxmind/db/Networks.java +++ b/src/main/java/com/maxmind/db/Networks.java @@ -32,7 +32,7 @@ public final class Networks implements Iterator> { */ Networks(Reader reader, boolean includeAliasedNetworks, Class typeParameterClass) throws ClosedDatabaseException { - this(reader, includeAliasedNetworks, new NetworkNode[]{}, typeParameterClass); + this(reader, includeAliasedNetworks, new NetworkNode[0], typeParameterClass); } /** diff --git a/src/main/java/com/maxmind/db/NetworksIterationException.java b/src/main/java/com/maxmind/db/NetworksIterationException.java index ad24a88c..5f13c9d8 100644 --- a/src/main/java/com/maxmind/db/NetworksIterationException.java +++ b/src/main/java/com/maxmind/db/NetworksIterationException.java @@ -5,14 +5,14 @@ * This class represents an error encountered while iterating over the networks. * The most likely causes are corrupt data in the database, or a bug in the reader code. *

- *

+ *

* This exception extends RuntimeException because it is thrown by the iterator * methods in {@link Networks}. *

* * @see Networks */ -public class NetworksIterationException extends RuntimeException { +public class NetworksIterationException extends RuntimeException { NetworksIterationException(String message) { super(message); } diff --git a/src/main/java/com/maxmind/db/NoCache.java b/src/main/java/com/maxmind/db/NoCache.java index f169e3b3..43cda527 100644 --- a/src/main/java/com/maxmind/db/NoCache.java +++ b/src/main/java/com/maxmind/db/NoCache.java @@ -13,10 +13,10 @@ private NoCache() { } @Override - public DecodedValue get(CacheKey key, Loader loader) throws IOException { + public DecodedValue get(CacheKey key, Loader loader) throws IOException { return loader.load(key); } - + /** * @return the singleton instance of the NoCache class */ diff --git a/src/main/java/com/maxmind/db/NodeCache.java b/src/main/java/com/maxmind/db/NodeCache.java index d7235813..4dd9175c 100644 --- a/src/main/java/com/maxmind/db/NodeCache.java +++ b/src/main/java/com/maxmind/db/NodeCache.java @@ -18,12 +18,12 @@ interface Loader { * @throws IOException * if there is an error loading the value */ - DecodedValue load(CacheKey key) throws IOException; + DecodedValue load(CacheKey key) throws IOException; } /** * This method returns the value for the key. If the key is not in the cache - * then the loader is called to load the value. + * then the loader is called to load the value. * * @param key * the key to look up @@ -33,6 +33,6 @@ interface Loader { * @throws IOException * if there is an error loading the value */ - DecodedValue get(CacheKey key, Loader loader) throws IOException; + DecodedValue get(CacheKey key, Loader loader) throws IOException; } diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index 15609c95..c9e41591 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -26,7 +26,7 @@ public final class Reader implements Closeable { private final Metadata metadata; private final AtomicReference bufferHolderReference; private final NodeCache cache; - private final ConcurrentHashMap constructors; + private final ConcurrentHashMap, CachedConstructor> constructors; /** * The file mode to use when opening a MaxMind DB. diff --git a/src/test/java/com/maxmind/db/DecoderTest.java b/src/test/java/com/maxmind/db/DecoderTest.java index f6e23569..92d6f3b1 100644 --- a/src/test/java/com/maxmind/db/DecoderTest.java +++ b/src/test/java/com/maxmind/db/DecoderTest.java @@ -465,8 +465,8 @@ private static void testTypeDecoding(Type type, Map tests) String key = (String) keyObject; Object value = expectMap.get(key); - if (value instanceof Object[]) { - assertArrayEquals((Object[]) value, (Object[]) got.get(key), desc); + if (value instanceof Object[] arrayValue) { + assertArrayEquals(arrayValue, (Object[]) got.get(key), desc); } else { assertEquals(value, got.get(key), desc); } diff --git a/src/test/java/com/maxmind/db/ReaderTest.java b/src/test/java/com/maxmind/db/ReaderTest.java index 1925a9fb..d2ff0904 100644 --- a/src/test/java/com/maxmind/db/ReaderTest.java +++ b/src/test/java/com/maxmind/db/ReaderTest.java @@ -172,7 +172,7 @@ public networkTest(String network, int prefix,String database, String[] expecte } ), new networkTest( - "255.255.255.0", + "255.255.255.0", 24, "ipv4", new String[]{} @@ -238,7 +238,7 @@ public networkTest(String network, int prefix,String database, String[] expecte "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", - "1.1.1.32/32", + "1.1.1.32/32", } ), new networkTest( @@ -251,7 +251,7 @@ public networkTest(String network, int prefix,String database, String[] expecte "1.1.1.4/30", "1.1.1.8/29", "1.1.1.16/28", - "1.1.1.32/32", + "1.1.1.32/32", }, true ), @@ -555,12 +555,12 @@ private void testDecodingTypesIntoModelObject(Reader reader, boolean booleanValu assertEquals("unicode! ☯ - ♫", model.utf8StringField); - List expectedArray = new ArrayList<>(Arrays.asList( + List expectedArray = new ArrayList<>(List.of( (long) 1, (long) 2, (long) 3 )); assertEquals(expectedArray, model.arrayField); - List expectedArray2 = new ArrayList<>(Arrays.asList( + List expectedArray2 = new ArrayList<>(List.of( (long) 7, (long) 8, (long) 9 )); assertEquals(expectedArray2, model.mapField.mapXField.arrayXField); @@ -675,12 +675,12 @@ private void testDecodingTypesIntoModelObjectBoxed(Reader reader, boolean boolea assertEquals("unicode! ☯ - ♫", model.utf8StringField); - List expectedArray = new ArrayList<>(Arrays.asList( + List expectedArray = new ArrayList<>(List.of( (long) 1, (long) 2, (long) 3 )); assertEquals(expectedArray, model.arrayField); - List expectedArray2 = new ArrayList<>(Arrays.asList( + List expectedArray2 = new ArrayList<>(List.of( (long) 7, (long) 8, (long) 9 )); assertEquals(expectedArray2, model.mapField.mapXField.arrayXField); @@ -785,7 +785,7 @@ private void testDecodingTypesIntoModelWithList(Reader reader) throws IOException { TestModelList model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelList.class); - assertEquals(Arrays.asList((long) 1, (long) 2, (long) 3), model.arrayField); + assertEquals(List.of((long) 1, (long) 2, (long) 3), model.arrayField); } static class TestModelList { @@ -1235,7 +1235,7 @@ private void testMetadata(Reader reader, int ipVersion, long recordSize) { assertEquals(ipVersion, metadata.getIpVersion()); assertEquals("Test", metadata.getDatabaseType()); - List languages = new ArrayList<>(Arrays.asList("en", "zh")); + List languages = new ArrayList<>(List.of("en", "zh")); assertEquals(languages, metadata.getLanguages());