From 2cc58bbcd49ac7c1a0bee071e949b91d95df7ece Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 10:05:32 -0700 Subject: [PATCH 01/15] Eliminate unnecessary duplicate() call --- src/main/java/com/maxmind/db/MultiBuffer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index 9113a7d5..fac2b86d 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -301,13 +301,16 @@ String decode(CharsetDecoder decoder, int maxCharBufferSize) int bufIndex = (int) (pos / this.chunkSize); int bufOffset = (int) (pos % this.chunkSize); - ByteBuffer srcView = buffers[bufIndex].duplicate(); + ByteBuffer srcView = buffers[bufIndex]; + int savedLimit = srcView.limit(); srcView.position(bufOffset); int toRead = (int) Math.min(srcView.remaining(), remainingBytes); srcView.limit(bufOffset + toRead); CoderResult result = decoder.decode(srcView, out, false); + srcView.limit(savedLimit); + if (result.isError()) { result.throwException(); } From 1a261372915817a39570db0675ddd42508e76336 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 10:13:46 -0700 Subject: [PATCH 02/15] Reorder operation to eliminate possible overflow --- src/main/java/com/maxmind/db/MultiBuffer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index fac2b86d..cac11aba 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -155,7 +155,7 @@ public byte get() { /** {@inheritDoc} */ @Override public Buffer get(byte[] dst) { - if (position + dst.length > limit) { + if (position > limit - dst.length) { throw new IndexOutOfBoundsException( "Read exceeds limit: position=" + position + ", length=" + dst.length @@ -359,4 +359,4 @@ public static MultiBuffer mapFromChannel(FileChannel channel) throws IOException } return new MultiBuffer(buffers, DEFAULT_CHUNK_SIZE); } -} \ No newline at end of file +} From 2a1fa7f9521567ba1e4c1bc2b69cf99f3efa0021 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 10:20:43 -0700 Subject: [PATCH 03/15] Make buffers read-only for all paths This also moves the responsibility of reading from the file to the BufferHolder. This seems to make the responsibilities a bit clearer, although I also method on the buffers could work. --- src/main/java/com/maxmind/db/Buffer.java | 15 +--- .../java/com/maxmind/db/BufferHolder.java | 48 +++++++++-- src/main/java/com/maxmind/db/MultiBuffer.java | 86 +++---------------- .../java/com/maxmind/db/SingleBuffer.java | 32 ++----- .../java/com/maxmind/db/MultiBufferTest.java | 61 +++++++++---- 5 files changed, 101 insertions(+), 141 deletions(-) diff --git a/src/main/java/com/maxmind/db/Buffer.java b/src/main/java/com/maxmind/db/Buffer.java index eef66f73..5a58b3c3 100644 --- a/src/main/java/com/maxmind/db/Buffer.java +++ b/src/main/java/com/maxmind/db/Buffer.java @@ -1,7 +1,5 @@ package com.maxmind.db; -import java.io.IOException; -import java.nio.channels.FileChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; @@ -12,6 +10,9 @@ * *

This interface is designed to provide a long-based API while * remaining compatible with the limitations of underlying storage. + * + *

All underlying {@link java.nio.ByteBuffer}s are read-only to prevent + * accidental modification of shared data. */ interface Buffer { /** @@ -96,16 +97,6 @@ interface Buffer { */ Buffer duplicate(); - /** - * Reads data from the given channel into this buffer starting at the - * current position. - * - * @param channel the file channel - * @return the number of bytes read - * @throws IOException if an I/O error occurs - */ - long readFrom(FileChannel channel) throws IOException; - /** * Decodes the buffer's content into a string using the given decoder. * diff --git a/src/main/java/com/maxmind/db/BufferHolder.java b/src/main/java/com/maxmind/db/BufferHolder.java index c0ad56cf..84f9d4f7 100644 --- a/src/main/java/com/maxmind/db/BufferHolder.java +++ b/src/main/java/com/maxmind/db/BufferHolder.java @@ -23,18 +23,48 @@ final class BufferHolder { FileChannel channel = file.getChannel()) { long size = channel.size(); if (mode == FileMode.MEMORY) { - Buffer buf; if (size <= chunkSize) { - buf = new SingleBuffer(size); + // Allocate, read, and make read-only + ByteBuffer buffer = ByteBuffer.allocate((int) size); + if (channel.read(buffer) != size) { + throw new IOException("Unable to read " + + database.getName() + + " into memory. Unexpected end of stream."); + } + buffer.flip(); + this.buffer = new SingleBuffer(buffer); } else { - buf = new MultiBuffer(size); - } - if (buf.readFrom(channel) != buf.capacity()) { - throw new IOException("Unable to read " - + database.getName() - + " into memory. Unexpected end of stream."); + // Allocate chunks, read, and make read-only + int fullChunks = (int) (size / chunkSize); + int remainder = (int) (size % chunkSize); + int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); + ByteBuffer[] buffers = new ByteBuffer[totalChunks]; + + for (int i = 0; i < fullChunks; i++) { + buffers[i] = ByteBuffer.allocate(chunkSize); + } + if (remainder > 0) { + buffers[totalChunks - 1] = ByteBuffer.allocate(remainder); + } + + long totalRead = 0; + for (ByteBuffer buffer : buffers) { + int read = channel.read(buffer); + if (read == -1) { + break; + } + totalRead += read; + buffer.flip(); + } + + if (totalRead != size) { + throw new IOException("Unable to read " + + database.getName() + + " into memory. Unexpected end of stream."); + } + + this.buffer = new MultiBuffer(buffers, chunkSize); } - this.buffer = buf; } else { if (size <= chunkSize) { this.buffer = SingleBuffer.mapFromChannel(channel); diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index cac11aba..068009ac 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -17,6 +17,9 @@ * a single logical position and limit across them. * *

Use this when working with databases/files that may exceed 2GB. + * + *

All underlying {@link ByteBuffer}s are read-only to prevent accidental + * modification of shared data. */ class MultiBuffer implements Buffer { @@ -30,16 +33,6 @@ class MultiBuffer implements Buffer { private long position = 0; private long limit; - /** - * Creates a new {@code MultiBuffer} with the given capacity, backed by - * heap-allocated {@link ByteBuffer}s. - * - * @param capacity the total capacity in bytes - */ - public MultiBuffer(long capacity) { - this(capacity, DEFAULT_CHUNK_SIZE); - } - /** * Creates a new {@code MultiBuffer} backed by the given * {@link ByteBuffer}s. @@ -64,43 +57,19 @@ public MultiBuffer(long capacity) { + " is smaller than expected chunk size"); } - this.buffers = buffers.clone(); - long capacity = 0; - for (ByteBuffer buffer : buffers) { - capacity += buffer.capacity(); + // Make all buffers read-only + this.buffers = new ByteBuffer[buffers.length]; + for (int i = 0; i < buffers.length; i++) { + this.buffers[i] = buffers[i].asReadOnlyBuffer(); } - this.capacity = capacity; - this.limit = capacity; - this.chunkSize = chunkSize; - } - /** - * Creates a new {@code MultiBuffer} with the given capacity, backed by - * heap-allocated {@link ByteBuffer}s with the given chunk size. - * - * @param capacity the total capacity in bytes - * @param chunkSize the size of each buffer chunk - */ - MultiBuffer(long capacity, int chunkSize) { - if (capacity <= 0) { - throw new IllegalArgumentException("Capacity must be positive"); + long capacity = 0; + for (ByteBuffer buffer : this.buffers) { + capacity += buffer.capacity(); } this.capacity = capacity; this.limit = capacity; this.chunkSize = chunkSize; - - int fullChunks = (int) (capacity / chunkSize); - int remainder = (int) (capacity % chunkSize); - int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); - - this.buffers = new ByteBuffer[totalChunks]; - - for (int i = 0; i < fullChunks; i++) { - buffers[i] = ByteBuffer.allocate(chunkSize); - } - if (remainder > 0) { - buffers[totalChunks - 1] = ByteBuffer.allocate(remainder); - } } /** {@inheritDoc} */ @@ -240,41 +209,6 @@ public Buffer duplicate() { return copy; } - /** {@inheritDoc} */ - @Override - public long readFrom(FileChannel channel) throws IOException { - return this.readFrom(channel, DEFAULT_CHUNK_SIZE); - } - - /** - * Reads data from the given channel into this buffer starting at the - * current position. - * - * @param channel the file channel - * @param chunkSize the chunk size to use for positioning reads - * @return the number of bytes read - * @throws IOException if an I/O error occurs - */ - long readFrom(FileChannel channel, int chunkSize) throws IOException { - long totalRead = 0; - long pos = position; - for (int i = (int) (pos / chunkSize); i < buffers.length; i++) { - ByteBuffer buf = buffers[i]; - buf.position((int) (pos % chunkSize)); - int read = channel.read(buf); - if (read == -1) { - break; - } - totalRead += read; - pos += read; - if (pos >= limit) { - break; - } - } - position = pos; - return totalRead; - } - /** {@inheritDoc} */ @Override public String decode(CharsetDecoder decoder) diff --git a/src/main/java/com/maxmind/db/SingleBuffer.java b/src/main/java/com/maxmind/db/SingleBuffer.java index 89c95980..568ed347 100644 --- a/src/main/java/com/maxmind/db/SingleBuffer.java +++ b/src/main/java/com/maxmind/db/SingleBuffer.java @@ -12,34 +12,22 @@ * *

This implementation is limited to capacities up to * {@link Integer#MAX_VALUE}, as {@link ByteBuffer} cannot exceed that size. + * + *

The underlying {@link ByteBuffer} is read-only to prevent accidental + * modification of shared data. */ class SingleBuffer implements Buffer { private final ByteBuffer buffer; - /** - * Creates a new {@code SingleBuffer} with the given capacity. - * - * @param capacity the capacity in bytes (must be <= Integer.MAX_VALUE) - * @throws IllegalArgumentException if the capacity exceeds - * {@link Integer#MAX_VALUE} - */ - public SingleBuffer(long capacity) { - if (capacity > Integer.MAX_VALUE) { - throw new IllegalArgumentException( - "SingleBuffer cannot exceed Integer.MAX_VALUE capacity" - ); - } - this.buffer = ByteBuffer.allocate((int) capacity); - } - /** * Creates a new {@code SingleBuffer} wrapping the given {@link ByteBuffer}. + * The buffer is made read-only. * * @param buffer the underlying buffer */ - private SingleBuffer(ByteBuffer buffer) { - this.buffer = buffer; + SingleBuffer(ByteBuffer buffer) { + this.buffer = buffer.asReadOnlyBuffer(); } /** {@inheritDoc} */ @@ -111,12 +99,6 @@ public SingleBuffer duplicate() { return new SingleBuffer(this.buffer.duplicate()); } - /** {@inheritDoc} */ - @Override - public long readFrom(FileChannel channel) throws IOException { - return channel.read(buffer); - } - /** {@inheritDoc} */ @Override public String decode(CharsetDecoder decoder) @@ -145,6 +127,6 @@ public static SingleBuffer wrap(byte[] array) { public static SingleBuffer mapFromChannel(FileChannel channel) throws IOException { ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size()); - return new SingleBuffer(buffer.asReadOnlyBuffer()); + return new SingleBuffer(buffer); } } diff --git a/src/test/java/com/maxmind/db/MultiBufferTest.java b/src/test/java/com/maxmind/db/MultiBufferTest.java index caf85f21..16f9fdf7 100644 --- a/src/test/java/com/maxmind/db/MultiBufferTest.java +++ b/src/test/java/com/maxmind/db/MultiBufferTest.java @@ -16,6 +16,15 @@ import org.junit.jupiter.api.io.TempDir; public class MultiBufferTest { + /** + * Helper method to create a MultiBuffer with a single empty ByteBuffer. + */ + static MultiBuffer createEmptyBuffer(int capacity) { + ByteBuffer bb = ByteBuffer.allocate(capacity); + bb.flip(); + return new MultiBuffer(new ByteBuffer[]{bb}, capacity); + } + static MultiBuffer createBuffer(int chunkSize) { try { Path tmpFile = Files.createTempFile("test-data", ".bin"); @@ -69,13 +78,25 @@ static MultiBuffer createBuffer(int chunkSize) { try (RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "r"); FileChannel channel = file.getChannel()) { - MultiBuffer buffer = new MultiBuffer(channel.size(), chunkSize); - - buffer.readFrom(channel, chunkSize); - buffer.position(0); - buffer.limit(channel.size()); - - return buffer; + long size = channel.size(); + int fullChunks = (int) (size / chunkSize); + int remainder = (int) (size % chunkSize); + int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); + + ByteBuffer[] buffers = new ByteBuffer[totalChunks]; + for (int i = 0; i < fullChunks; i++) { + buffers[i] = ByteBuffer.allocate(chunkSize); + } + if (remainder > 0) { + buffers[totalChunks - 1] = ByteBuffer.allocate(remainder); + } + + for (ByteBuffer buffer : buffers) { + channel.read(buffer); + buffer.flip(); + } + + return new MultiBuffer(buffers, chunkSize); } } catch (IOException e) { fail("Could not create test buffer: " + e.getMessage()); @@ -85,46 +106,46 @@ static MultiBuffer createBuffer(int chunkSize) { @Test public void testPositionSetter() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); buffer.position(500); assertEquals(500, buffer.position()); } @Test public void testPositionSetterInvalidNegative() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.position(-1)); } @Test public void testPositionSetterExceedsLimit() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); buffer.limit(500); assertThrows(IllegalArgumentException.class, () -> buffer.position(600)); } @Test public void testLimitSetter() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); buffer.limit(500); assertEquals(500, buffer.limit()); } @Test public void testLimitSetterInvalidNegative() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.limit(-1)); } @Test public void testLimitSetterExceedsCapacity() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.limit(1001)); } @Test public void testLimitSetterAdjustsPosition() { - MultiBuffer buffer = new MultiBuffer(1000); + MultiBuffer buffer = createEmptyBuffer(1000); buffer.position(800); buffer.limit(500); assertEquals(500, buffer.position()); @@ -168,7 +189,7 @@ public void testGetByteArray() { @Test public void testGetByteArrayExceedsLimit() { - MultiBuffer buffer = new MultiBuffer(100); + MultiBuffer buffer = createEmptyBuffer(100); buffer.limit(5); byte[] dst = new byte[10]; assertThrows(IndexOutOfBoundsException.class, () -> buffer.get(dst)); @@ -222,7 +243,7 @@ public void testGetFloatAcrossChunks() { @Test public void testDuplicate() { - MultiBuffer original = new MultiBuffer(1000); + MultiBuffer original = createEmptyBuffer(1000); original.position(100); original.limit(800); @@ -273,10 +294,12 @@ public void testReadFromFileChannel(@TempDir Path tempDir) throws IOException { Files.write(testFile, testData); try (FileChannel channel = FileChannel.open(testFile, StandardOpenOption.READ)) { - MultiBuffer buffer = new MultiBuffer(testData.length); - long bytesRead = buffer.readFrom(channel); + ByteBuffer buffer = ByteBuffer.allocate(testData.length); + long bytesRead = channel.read(buffer); + buffer.flip(); + MultiBuffer multiBuffer = new MultiBuffer(new ByteBuffer[]{buffer}, testData.length); assertEquals(21, bytesRead); - assertEquals(21, buffer.position()); + assertEquals(0, multiBuffer.position()); } } From ffc744b52858b82285d31653199f9e7961530cca Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 10:42:50 -0700 Subject: [PATCH 04/15] Add changelog entry for #289 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b80d986..9d0d1bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ CHANGELOG ------------------ * Java 17 or greater is now required. +* Added support for MaxMind DB files larger than 2GB. The library now uses + an internal Buffer abstraction that can handle databases exceeding the + 2GB ByteBuffer limit. Files under 2GB continue to use a single ByteBuffer + for optimal performance. Requested by nonetallt. GitHub #154. Fixed by + Silvano Cerza. GitHub #289. 3.2.0 (2025-05-28) ------------------ From 5560ee5836bbf6100ab393e67e5503c60a9bc06a Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 11:03:44 -0700 Subject: [PATCH 05/15] Use records where appropriate This introduces some breaking changes due to the accessor method naming pattern. --- CHANGELOG.md | 11 ++ .../java/com/maxmind/db/DatabaseRecord.java | 32 +-- .../java/com/maxmind/db/DecodedValue.java | 14 +- src/main/java/com/maxmind/db/Decoder.java | 12 +- src/main/java/com/maxmind/db/Metadata.java | 187 ++++-------------- src/main/java/com/maxmind/db/Network.java | 45 ++--- src/main/java/com/maxmind/db/Networks.java | 4 +- src/main/java/com/maxmind/db/Reader.java | 34 ++-- src/test/java/com/maxmind/db/NetworkTest.java | 8 +- src/test/java/com/maxmind/db/ReaderTest.java | 38 ++-- 10 files changed, 121 insertions(+), 264 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d0d1bf6..6720f178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,17 @@ CHANGELOG 2GB ByteBuffer limit. Files under 2GB continue to use a single ByteBuffer for optimal performance. Requested by nonetallt. GitHub #154. Fixed by Silvano Cerza. GitHub #289. +* `DatabaseRecord`, `Metadata`, `Network`, and internal `DecodedValue` classes + have been converted to records. The following API changes were made: + * `DatabaseRecord.getData()` and `DatabaseRecord.getNetwork()` have been + replaced with record accessor methods `data()` and `network()`. + * `Metadata.getBuildDate()` has been renamed to `buildDate()` to follow record + naming conventions. All other simple getter methods on `Metadata` (e.g., + `getBinaryFormatMajorVersion()`, `getDatabaseType()`, etc.) have been + replaced with their corresponding record accessor methods (e.g., + `binaryFormatMajorVersion()`, `databaseType()`, etc.). + * `Network.getNetworkAddress()` and `Network.getPrefixLength()` have been + replaced with record accessor methods `networkAddress()` and `prefixLength()`. 3.2.0 (2025-05-28) ------------------ diff --git a/src/main/java/com/maxmind/db/DatabaseRecord.java b/src/main/java/com/maxmind/db/DatabaseRecord.java index 6779408b..9ff1c7ac 100644 --- a/src/main/java/com/maxmind/db/DatabaseRecord.java +++ b/src/main/java/com/maxmind/db/DatabaseRecord.java @@ -7,11 +7,14 @@ * lookup. * * @param the type to deserialize the returned value to + * @param data the data for the record in the database. The record will be + * {@code null} if there was no data for the address in the + * database. + * @param network the network associated with the record in the database. This is + * the largest network where all of the IPs in the network have the same + * data. */ -public final class DatabaseRecord { - private final T data; - private final Network network; - +public record DatabaseRecord(T data, Network network) { /** * Create a new record. * @@ -20,25 +23,6 @@ public final class DatabaseRecord { * @param prefixLength the network prefix length associated with the record in the database. */ public DatabaseRecord(T data, InetAddress ipAddress, int prefixLength) { - this.data = data; - this.network = new Network(ipAddress, prefixLength); - } - - /** - * @return the data for the record in the database. The record will be - * null if there was no data for the address in the - * database. - */ - public T getData() { - return data; - } - - /** - * @return the network associated with the record in the database. This is - * the largest network where all of the IPs in the network have the same - * data. - */ - public Network getNetwork() { - return network; + this(data, new Network(ipAddress, prefixLength)); } } diff --git a/src/main/java/com/maxmind/db/DecodedValue.java b/src/main/java/com/maxmind/db/DecodedValue.java index 8a6f4454..c3174f69 100644 --- a/src/main/java/com/maxmind/db/DecodedValue.java +++ b/src/main/java/com/maxmind/db/DecodedValue.java @@ -3,15 +3,7 @@ /** * {@code DecodedValue} is a wrapper for the decoded value and the number of bytes used * to decode it. + * + * @param value the decoded value */ -public final class DecodedValue { - final Object value; - - DecodedValue(Object value) { - this.value = value; - } - - Object getValue() { - return value; - } -} +record DecodedValue(Object value) {} diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index 48dc24ce..74f194cf 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -69,7 +69,7 @@ T decode(long offset, Class cls) throws IOException { } this.buffer.position(offset); - return cls.cast(decode(cls, null).getValue()); + return cls.cast(decode(cls, null).value()); } private DecodedValue decode(CacheKey key) throws IOException { @@ -300,7 +300,7 @@ private List decodeArray( } for (int i = 0; i < size; i++) { - Object e = this.decode(elementClass, null).getValue(); + Object e = this.decode(elementClass, null).value(); array.add(elementClass.cast(e)); } @@ -360,8 +360,8 @@ private Map decodeMapIntoMap( } for (int i = 0; i < size; i++) { - String key = (String) this.decode(String.class, null).getValue(); - Object value = this.decode(valueClass, null).getValue(); + String key = (String) this.decode(String.class, null).value(); + Object value = this.decode(valueClass, null).value(); try { map.put(key, valueClass.cast(value)); } catch (ClassCastException e) { @@ -412,7 +412,7 @@ private Object decodeMapIntoObject(int size, Class cls) Object[] parameters = new Object[parameterTypes.length]; for (int i = 0; i < size; i++) { - String key = (String) this.decode(String.class, null).getValue(); + String key = (String) this.decode(String.class, null).value(); Integer parameterIndex = parameterIndexes.get(key); if (parameterIndex == null) { @@ -424,7 +424,7 @@ private Object decodeMapIntoObject(int size, Class cls) parameters[parameterIndex] = this.decode( parameterTypes[parameterIndex], parameterGenericTypes[parameterIndex] - ).getValue(); + ).value(); } try { diff --git a/src/main/java/com/maxmind/db/Metadata.java b/src/main/java/com/maxmind/db/Metadata.java index aa2d733d..463c1a67 100644 --- a/src/main/java/com/maxmind/db/Metadata.java +++ b/src/main/java/com/maxmind/db/Metadata.java @@ -7,171 +7,62 @@ /** * {@code Metadata} holds data associated with the database itself. + * + * @param binaryFormatMajorVersion The major version number for the database's + * binary format. + * @param binaryFormatMinorVersion The minor version number for the database's + * binary format. + * @param buildEpoch The date of the database build. + * @param databaseType A string that indicates the structure of each + * data record associated with an IP address. + * The actual definition of these structures is + * left up to the database creator. + * @param languages List of languages supported by the database. + * @param description Map from language code to description in that + * language. + * @param ipVersion Whether the database contains IPv4 or IPv6 + * address data. The only possible values are 4 + * and 6. + * @param nodeCount The number of nodes in the search tree. + * @param recordSize The number of bits in a record in the search + * tree. Note that each node consists of two + * records. */ -public final class Metadata { - private final int binaryFormatMajorVersion; - private final int binaryFormatMinorVersion; - - private final BigInteger buildEpoch; - - private final String databaseType; - - private final Map description; - - private final int ipVersion; - - private final List languages; - - private final int nodeByteSize; - - private final long nodeCount; - - private final int recordSize; - - private final long searchTreeSize; - +public record Metadata( + @MaxMindDbParameter(name = "binary_format_major_version") int binaryFormatMajorVersion, + @MaxMindDbParameter(name = "binary_format_minor_version") int binaryFormatMinorVersion, + @MaxMindDbParameter(name = "build_epoch") BigInteger buildEpoch, + @MaxMindDbParameter(name = "database_type") String databaseType, + @MaxMindDbParameter(name = "languages") List languages, + @MaxMindDbParameter(name = "description") Map description, + @MaxMindDbParameter(name = "ip_version") int ipVersion, + @MaxMindDbParameter(name = "node_count") long nodeCount, + @MaxMindDbParameter(name = "record_size") int recordSize +) { /** - * Constructs a {@code Metadata} object. - * - * @param binaryFormatMajorVersion The major version number for the database's - * binary format. - * @param binaryFormatMinorVersion The minor version number for the database's - * binary format. - * @param buildEpoch The date of the database build. - * @param databaseType A string that indicates the structure of each - * data record associated with an IP address. - * The actual definition of these structures is - * left up to the database creator. - * @param languages List of languages supported by the database. - * @param description Map from language code to description in that - * language. - * @param ipVersion Whether the database contains IPv4 or IPv6 - * address data. The only possible values are 4 - * and 6. - * @param nodeCount The number of nodes in the search tree. - * @param recordSize The number of bits in a record in the search - * tree. Note that each node consists of two - * records. + * Compact constructor for the Metadata record. */ @MaxMindDbConstructor - public Metadata( - @MaxMindDbParameter(name = "binary_format_major_version") int binaryFormatMajorVersion, - @MaxMindDbParameter(name = "binary_format_minor_version") int binaryFormatMinorVersion, - @MaxMindDbParameter(name = "build_epoch") BigInteger buildEpoch, - @MaxMindDbParameter(name = "database_type") String databaseType, - @MaxMindDbParameter(name = "languages") List languages, - @MaxMindDbParameter(name = "description") Map description, - @MaxMindDbParameter(name = "ip_version") int ipVersion, - @MaxMindDbParameter(name = "node_count") long nodeCount, - @MaxMindDbParameter(name = "record_size") int recordSize) { - this.binaryFormatMajorVersion = binaryFormatMajorVersion; - this.binaryFormatMinorVersion = binaryFormatMinorVersion; - this.buildEpoch = buildEpoch; - this.databaseType = databaseType; - this.languages = languages; - this.description = description; - this.ipVersion = ipVersion; - this.nodeCount = nodeCount; - this.recordSize = recordSize; - - this.nodeByteSize = this.recordSize / 4; - this.searchTreeSize = this.nodeCount * this.nodeByteSize; - } - - /** - * @return the major version number for the database's binary format. - */ - public int getBinaryFormatMajorVersion() { - return this.binaryFormatMajorVersion; - } - - /** - * @return the minor version number for the database's binary format. - */ - public int getBinaryFormatMinorVersion() { - return this.binaryFormatMinorVersion; - } + public Metadata {} /** * @return the date of the database build. */ - public Date getBuildDate() { - return new Date(this.buildEpoch.longValue() * 1000); - } - - /** - * @return a string that indicates the structure of each data record - * associated with an IP address. The actual definition of these - * structures is left up to the database creator. - */ - public String getDatabaseType() { - return this.databaseType; - } - - /** - * @return map from language code to description in that language. - */ - public Map getDescription() { - return this.description; - } - - /** - * @return whether the database contains IPv4 or IPv6 address data. The only - * possible values are 4 and 6. - */ - public int getIpVersion() { - return this.ipVersion; - } - - /** - * @return list of languages supported by the database. - */ - public List getLanguages() { - return this.languages; + public Date buildDate() { + return new Date(buildEpoch.longValue() * 1000); } /** * @return the nodeByteSize */ - int getNodeByteSize() { - return this.nodeByteSize; - } - - /** - * @return the number of nodes in the search tree. - */ - long getNodeCount() { - return this.nodeCount; - } - - /** - * @return the number of bits in a record in the search tree. Note that each - * node consists of two records. - */ - int getRecordSize() { - return this.recordSize; + int nodeByteSize() { + return recordSize / 4; } /** * @return the searchTreeSize */ - long getSearchTreeSize() { - return this.searchTreeSize; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "Metadata [binaryFormatMajorVersion=" - + this.binaryFormatMajorVersion + ", binaryFormatMinorVersion=" - + this.binaryFormatMinorVersion + ", buildEpoch=" - + this.buildEpoch + ", databaseType=" + this.databaseType - + ", description=" + this.description + ", ipVersion=" - + this.ipVersion + ", nodeCount=" + this.nodeCount - + ", recordSize=" + this.recordSize + "]"; + long searchTreeSize() { + return nodeCount * nodeByteSize(); } } diff --git a/src/main/java/com/maxmind/db/Network.java b/src/main/java/com/maxmind/db/Network.java index 32033e0a..2ba3ea2b 100644 --- a/src/main/java/com/maxmind/db/Network.java +++ b/src/main/java/com/maxmind/db/Network.java @@ -4,32 +4,19 @@ import java.net.UnknownHostException; /** - * Network represents an IP network. + * {@code Network} represents an IP network. + * + * @param ipAddress An IP address in the network. This does not have to be + * the first address in the network. + * @param prefixLength The prefix length for the network. This is the number of + * leading 1 bits in the subnet mask, sometimes also known as + * netmask length. */ -public final class Network { - private final InetAddress ipAddress; - private final int prefixLength; - private InetAddress networkAddress = null; - - /** - * Construct a Network - * - * @param ipAddress An IP address in the network. This does not have to be - * the first address in the network. - * @param prefixLength The prefix length for the network. - */ - public Network(InetAddress ipAddress, int prefixLength) { - this.ipAddress = ipAddress; - this.prefixLength = prefixLength; - } - +public record Network(InetAddress ipAddress, int prefixLength) { /** * @return The first address in the network. */ - public InetAddress getNetworkAddress() { - if (networkAddress != null) { - return networkAddress; - } + public InetAddress networkAddress() { byte[] ipBytes = ipAddress.getAddress(); byte[] networkBytes = new byte[ipBytes.length]; int curPrefix = prefixLength; @@ -44,27 +31,19 @@ public InetAddress getNetworkAddress() { } try { - networkAddress = InetAddress.getByAddress(networkBytes); + return InetAddress.getByAddress(networkBytes); } catch (UnknownHostException e) { throw new RuntimeException( "Illegal network address byte length of " + networkBytes.length); } - return networkAddress; } - /** - * @return The prefix length is the number of leading 1 bits in the subnet - * mask. Sometimes also known as netmask length. - */ - public int getPrefixLength() { - return prefixLength; - } /** * @return A string representation of the network in CIDR notation, e.g., - * 1.2.3.0/24 or 2001::/8. + * {@code 1.2.3.0/24} or {@code 2001::/8}. */ public String toString() { - return getNetworkAddress().getHostAddress() + "/" + prefixLength; + return networkAddress().getHostAddress() + "/" + prefixLength; } } diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java index cc77af25..0912ff56 100644 --- a/src/main/java/com/maxmind/db/Networks.java +++ b/src/main/java/com/maxmind/db/Networks.java @@ -132,7 +132,7 @@ public boolean hasNext() { NetworkNode node = this.nodes.pop(); // Next until we don't have data. - while (node.pointer != this.reader.getMetadata().getNodeCount()) { + while (node.pointer != this.reader.getMetadata().nodeCount()) { // This skips IPv4 aliases without hardcoding the networks that the writer // currently aliases. if (!this.includeAliasedNetworks && this.reader.getIpv4Start() != 0 @@ -141,7 +141,7 @@ public boolean hasNext() { break; } - if (node.pointer > this.reader.getMetadata().getNodeCount()) { + if (node.pointer > this.reader.getMetadata().nodeCount()) { this.lastNode = node; return true; } diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index 2e6b1007..8a9e7be3 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -173,7 +173,7 @@ private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws I * @throws IOException if a file I/O error occurs. */ public T get(InetAddress ipAddress, Class cls) throws IOException { - return getRecord(ipAddress, cls).getData(); + return getRecord(ipAddress, cls).data(); } long getIpv4Start() { @@ -200,7 +200,7 @@ public DatabaseRecord getRecord(InetAddress ipAddress, Class cls) long record = traverseResult[0]; int pl = (int) traverseResult[1]; - long nodeCount = this.metadata.getNodeCount(); + long nodeCount = this.metadata.nodeCount(); Buffer buffer = this.getBufferHolder().get(); T dataRecord = null; if (record > nodeCount) { @@ -253,7 +253,7 @@ public Networks networks( Class typeParameterClass) throws InvalidNetworkException, ClosedDatabaseException, InvalidDatabaseException { try { - if (this.getMetadata().getIpVersion() == 6) { + if (this.getMetadata().ipVersion() == 6) { InetAddress ipv6 = InetAddress.getByAddress(new byte[16]); Network ipAllV6 = new Network(ipv6, 0); // Mask 128. return this.networksWithin(ipAllV6, includeAliasedNetworks, typeParameterClass); @@ -280,7 +280,7 @@ BufferHolder getBufferHolder() throws ClosedDatabaseException { private long startNode(int bitLength) { // Check if we are looking up an IPv4 address in an IPv6 tree. If this // is the case, we can skip over the first 96 nodes. - if (this.metadata.getIpVersion() == 6 && bitLength == 32) { + if (this.metadata.ipVersion() == 6 && bitLength == 32) { return this.ipV4Start; } // The first node of the tree is always node 0, at the beginning of the @@ -290,12 +290,12 @@ private long startNode(int bitLength) { private long findIpV4StartNode(Buffer buffer) throws InvalidDatabaseException { - if (this.metadata.getIpVersion() == 4) { + if (this.metadata.ipVersion() == 4) { return 0; } long node = 0; - for (int i = 0; i < 96 && node < this.metadata.getNodeCount(); i++) { + for (int i = 0; i < 96 && node < this.metadata.nodeCount(); i++) { node = this.readNode(buffer, node, 0); } return node; @@ -322,15 +322,15 @@ public Networks networksWithin( boolean includeAliasedNetworks, Class typeParameterClass) throws InvalidNetworkException, ClosedDatabaseException, InvalidDatabaseException { - InetAddress networkAddress = network.getNetworkAddress(); - if (this.metadata.getIpVersion() == 4 && networkAddress instanceof Inet6Address) { + InetAddress networkAddress = network.networkAddress(); + if (this.metadata.ipVersion() == 4 && networkAddress instanceof Inet6Address) { throw new InvalidNetworkException(networkAddress); } byte[] ipBytes = networkAddress.getAddress(); - int prefixLength = network.getPrefixLength(); + int prefixLength = network.prefixLength(); - if (this.metadata.getIpVersion() == 6 && ipBytes.length == IPV4_LEN) { + if (this.metadata.ipVersion() == 6 && ipBytes.length == IPV4_LEN) { if (includeAliasedNetworks) { // Convert it to the IP address (in 16-byte from) of the IPv4 address. ipBytes = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -364,7 +364,7 @@ private long[] traverseTree(byte[] ip, int bitCount) Buffer buffer = this.getBufferHolder().get(); int bitLength = ip.length * 8; long record = this.startNode(bitLength); - long nodeCount = this.metadata.getNodeCount(); + long nodeCount = this.metadata.nodeCount(); int i = 0; for (; i < bitCount && record < nodeCount; i++) { @@ -383,9 +383,9 @@ long readNode(Buffer buffer, long nodeNumber, int index) throws InvalidDatabaseException { // index is the index of the record within the node, which // can either be 0 or 1. - long baseOffset = nodeNumber * this.metadata.getNodeByteSize(); + long baseOffset = nodeNumber * this.metadata.nodeByteSize(); - switch (this.metadata.getRecordSize()) { + switch (this.metadata.recordSize()) { case 24: // For a 24 bit record, each record is 3 bytes. buffer.position(baseOffset + (long) index * 3); @@ -408,7 +408,7 @@ long readNode(Buffer buffer, long nodeNumber, int index) return Decoder.decodeLong(buffer, 0, 4); default: throw new InvalidDatabaseException("Unknown record size: " - + this.metadata.getRecordSize()); + + this.metadata.recordSize()); } } @@ -417,8 +417,8 @@ T resolveDataPointer( long pointer, Class cls ) throws IOException { - long resolved = (pointer - this.metadata.getNodeCount()) - + this.metadata.getSearchTreeSize(); + long resolved = (pointer - this.metadata.nodeCount()) + + this.metadata.searchTreeSize(); if (resolved >= buffer.capacity()) { throw new InvalidDatabaseException( @@ -431,7 +431,7 @@ T resolveDataPointer( Decoder decoder = new Decoder( this.cache, buffer, - this.metadata.getSearchTreeSize() + DATA_SECTION_SEPARATOR_SIZE, + this.metadata.searchTreeSize() + DATA_SECTION_SEPARATOR_SIZE, this.constructors ); return decoder.decode(resolved, cls); diff --git a/src/test/java/com/maxmind/db/NetworkTest.java b/src/test/java/com/maxmind/db/NetworkTest.java index d04d7fa6..ac841e2e 100644 --- a/src/test/java/com/maxmind/db/NetworkTest.java +++ b/src/test/java/com/maxmind/db/NetworkTest.java @@ -14,8 +14,8 @@ public void testIPv6() throws UnknownHostException { 28 ); - assertEquals("2001:db0:0:0:0:0:0:0", network.getNetworkAddress().getHostAddress()); - assertEquals(28, network.getPrefixLength()); + assertEquals("2001:db0:0:0:0:0:0:0", network.networkAddress().getHostAddress()); + assertEquals(28, network.prefixLength()); assertEquals("2001:db0:0:0:0:0:0:0/28", network.toString()); } @@ -26,8 +26,8 @@ public void TestIPv4() throws UnknownHostException { 31 ); - assertEquals("192.168.213.110", network.getNetworkAddress().getHostAddress()); - assertEquals(31, network.getPrefixLength()); + assertEquals("192.168.213.110", network.networkAddress().getHostAddress()); + assertEquals(31, network.prefixLength()); assertEquals("192.168.213.110/31", network.toString()); } diff --git a/src/test/java/com/maxmind/db/ReaderTest.java b/src/test/java/com/maxmind/db/ReaderTest.java index 98dfda03..7daa7430 100644 --- a/src/test/java/com/maxmind/db/ReaderTest.java +++ b/src/test/java/com/maxmind/db/ReaderTest.java @@ -109,12 +109,12 @@ public void testNetworks(int chunkSize) throws IOException, InvalidDatabaseExcep while(networks.hasNext()) { var iteration = networks.next(); - var data = (Map) iteration.getData(); + var data = (Map) iteration.data(); InetAddress actualIPInData = InetAddress.getByName((String) data.get("ip")); assertEquals( - iteration.getNetwork().getNetworkAddress(), + iteration.network().networkAddress(), actualIPInData, "expected ip address" ); @@ -366,7 +366,7 @@ public void testNetworksWithin(int chunkSize) throws IOException, InvalidNetwork List innerIPs = new ArrayList<>(); while(networks.hasNext()){ var iteration = networks.next(); - innerIPs.add(iteration.getNetwork().toString()); + innerIPs.add(iteration.network().toString()); } assertArrayEquals(test.expected, innerIPs.toArray()); @@ -404,7 +404,7 @@ public void testGeoIPNetworksWithin(int chunkSize) throws IOException, InvalidNe ArrayList innerIPs = new ArrayList<>(); while(networks.hasNext()){ var iteration = networks.next(); - innerIPs.add(iteration.getNetwork().toString()); + innerIPs.add(iteration.network().toString()); } assertArrayEquals(test.expected, innerIPs.toArray()); @@ -434,12 +434,12 @@ public void testGetRecord(int chunkSize) throws IOException { try (Reader reader = new Reader(test.db, chunkSize)) { DatabaseRecord record = reader.getRecord(test.ip, Map.class); - assertEquals(test.network, record.getNetwork().toString()); + assertEquals(test.network, record.network().toString()); if (test.hasRecord) { - assertNotNull(record.getData()); + assertNotNull(record.data()); } else { - assertNull(record.getData()); + assertNull(record.data()); } } } @@ -458,12 +458,12 @@ public void testGetRecord(int chunkSize) throws IOException { try (Reader reader = new Reader(test.db, chunkSize)) { var record = reader.getRecord(test.ip, String.class); - assertEquals(test.network, record.getNetwork().toString()); + assertEquals(test.network, record.network().toString()); if (test.hasRecord) { - assertNotNull(record.getData()); + assertNotNull(record.data()); } else { - assertNull(record.getData()); + assertNull(record.data()); } } } @@ -473,7 +473,7 @@ var record = reader.getRecord(test.ip, String.class); @MethodSource("chunkSizes") public void testMetadataPointers(int chunkSize) throws IOException { Reader reader = new Reader(getFile("MaxMind-DB-test-metadata-pointers.mmdb"), chunkSize); - assertEquals("Lots of pointers in metadata", reader.getMetadata().getDatabaseType()); + assertEquals("Lots of pointers in metadata", reader.getMetadata().databaseType()); } @ParameterizedTest @@ -1279,26 +1279,26 @@ private void testMetadata(Reader reader, int ipVersion, long recordSize) { Metadata metadata = reader.getMetadata(); - assertEquals(2, metadata.getBinaryFormatMajorVersion(), "major version"); - assertEquals(0, metadata.getBinaryFormatMinorVersion()); - assertEquals(ipVersion, metadata.getIpVersion()); - assertEquals("Test", metadata.getDatabaseType()); + assertEquals(2, metadata.binaryFormatMajorVersion(), "major version"); + assertEquals(0, metadata.binaryFormatMinorVersion()); + assertEquals(ipVersion, metadata.ipVersion()); + assertEquals("Test", metadata.databaseType()); List languages = new ArrayList<>(List.of("en", "zh")); - assertEquals(languages, metadata.getLanguages()); + assertEquals(languages, metadata.languages()); Map description = new HashMap<>(); description.put("en", "Test Database"); description.put("zh", "Test Database Chinese"); - assertEquals(description, metadata.getDescription()); - assertEquals(recordSize, metadata.getRecordSize()); + assertEquals(description, metadata.description()); + assertEquals(recordSize, metadata.recordSize()); Calendar cal = Calendar.getInstance(); cal.set(2014, Calendar.JANUARY, 1); - assertTrue(metadata.getBuildDate().compareTo(cal.getTime()) > 0); + assertTrue(metadata.buildDate().compareTo(cal.getTime()) > 0); } private void testIpV4(Reader reader, File file) throws IOException { From c7e1851b400b19b28bf60a9ce13b4e4564f4d334 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 11:46:03 -0700 Subject: [PATCH 06/15] Make Buffer a sealed interface --- src/main/java/com/maxmind/db/Buffer.java | 2 +- src/main/java/com/maxmind/db/MultiBuffer.java | 2 +- src/main/java/com/maxmind/db/SingleBuffer.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/maxmind/db/Buffer.java b/src/main/java/com/maxmind/db/Buffer.java index 5a58b3c3..b12dbda7 100644 --- a/src/main/java/com/maxmind/db/Buffer.java +++ b/src/main/java/com/maxmind/db/Buffer.java @@ -14,7 +14,7 @@ *

All underlying {@link java.nio.ByteBuffer}s are read-only to prevent * accidental modification of shared data. */ -interface Buffer { +sealed interface Buffer permits SingleBuffer, MultiBuffer { /** * Returns the total capacity of this buffer in bytes. * diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index 068009ac..83780b57 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -21,7 +21,7 @@ *

All underlying {@link ByteBuffer}s are read-only to prevent accidental * modification of shared data. */ -class MultiBuffer implements Buffer { +final class MultiBuffer implements Buffer { /** Default maximum size per underlying chunk. */ static final int DEFAULT_CHUNK_SIZE = Integer.MAX_VALUE - 8; diff --git a/src/main/java/com/maxmind/db/SingleBuffer.java b/src/main/java/com/maxmind/db/SingleBuffer.java index 568ed347..36f25ad3 100644 --- a/src/main/java/com/maxmind/db/SingleBuffer.java +++ b/src/main/java/com/maxmind/db/SingleBuffer.java @@ -16,7 +16,7 @@ *

The underlying {@link ByteBuffer} is read-only to prevent accidental * modification of shared data. */ -class SingleBuffer implements Buffer { +final class SingleBuffer implements Buffer { private final ByteBuffer buffer; From 58ff7a45373997275b78f11f1c61fb2d093fc518 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 11:49:18 -0700 Subject: [PATCH 07/15] Use a switch expression in another spot --- src/main/java/com/maxmind/db/Reader.java | 36 +++++++++++------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index 8a9e7be3..1e0e85fb 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -385,31 +385,27 @@ long readNode(Buffer buffer, long nodeNumber, int index) // can either be 0 or 1. long baseOffset = nodeNumber * this.metadata.nodeByteSize(); - switch (this.metadata.recordSize()) { - case 24: + int recordSize = this.metadata.recordSize(); + return switch (recordSize) { + case 24 -> { // For a 24 bit record, each record is 3 bytes. buffer.position(baseOffset + (long) index * 3); - return Decoder.decodeLong(buffer, 0, 3); - case 28: + yield Decoder.decodeLong(buffer, 0, 3); + } + case 28 -> { int middle = buffer.get(baseOffset + 3); - - if (index == 0) { - // We get the most significant from the first half - // of the byte. It belongs to the first record. - middle = (0xF0 & middle) >>> 4; - } else { - // We get the most significant byte of the second record. - middle = 0x0F & middle; - } + // We get the most significant bits from the appropriate half + // of the byte based on the index. + middle = index == 0 ? (0xF0 & middle) >>> 4 : 0x0F & middle; buffer.position(baseOffset + (long) index * 4); - return Decoder.decodeLong(buffer, middle, 3); - case 32: + yield Decoder.decodeLong(buffer, middle, 3); + } + case 32 -> { buffer.position(baseOffset + (long) index * 4); - return Decoder.decodeLong(buffer, 0, 4); - default: - throw new InvalidDatabaseException("Unknown record size: " - + this.metadata.recordSize()); - } + yield Decoder.decodeLong(buffer, 0, 4); + } + default -> throw new InvalidDatabaseException("Unknown record size: " + recordSize); + }; } T resolveDataPointer( From 6fbcf51830aecb96fd873c1dbf796d7911060b59 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 12:40:13 -0700 Subject: [PATCH 08/15] Use var for variables I realize this is contentious among some Java programmers, but given that most people modifying this code will be familiar with this pattern from other languages (Go, C#, etc.), this seems like an improvement. --- .../java/com/maxmind/db/BufferHolder.java | 28 ++-- src/main/java/com/maxmind/db/CHMCache.java | 2 +- src/main/java/com/maxmind/db/Decoder.java | 102 ++++++------- src/main/java/com/maxmind/db/MultiBuffer.java | 84 +++++------ src/main/java/com/maxmind/db/Networks.java | 14 +- src/main/java/com/maxmind/db/Reader.java | 36 ++--- .../java/com/maxmind/db/SingleBuffer.java | 2 +- src/test/java/com/maxmind/db/DecoderTest.java | 80 +++++----- .../java/com/maxmind/db/MultiBufferTest.java | 98 ++++++------ .../com/maxmind/db/MultiThreadedTest.java | 10 +- src/test/java/com/maxmind/db/NetworkTest.java | 4 +- src/test/java/com/maxmind/db/PointerTest.java | 8 +- src/test/java/com/maxmind/db/ReaderTest.java | 142 +++++++++--------- 13 files changed, 305 insertions(+), 305 deletions(-) diff --git a/src/main/java/com/maxmind/db/BufferHolder.java b/src/main/java/com/maxmind/db/BufferHolder.java index 84f9d4f7..cd4a824a 100644 --- a/src/main/java/com/maxmind/db/BufferHolder.java +++ b/src/main/java/com/maxmind/db/BufferHolder.java @@ -35,10 +35,10 @@ final class BufferHolder { this.buffer = new SingleBuffer(buffer); } else { // Allocate chunks, read, and make read-only - int fullChunks = (int) (size / chunkSize); - int remainder = (int) (size % chunkSize); - int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); - ByteBuffer[] buffers = new ByteBuffer[totalChunks]; + var fullChunks = (int) (size / chunkSize); + var remainder = (int) (size % chunkSize); + var totalChunks = fullChunks + (remainder > 0 ? 1 : 0); + var buffers = new ByteBuffer[totalChunks]; for (int i = 0; i < fullChunks; i++) { buffers[i] = ByteBuffer.allocate(chunkSize); @@ -47,9 +47,9 @@ final class BufferHolder { buffers[totalChunks - 1] = ByteBuffer.allocate(remainder); } - long totalRead = 0; - for (ByteBuffer buffer : buffers) { - int read = channel.read(buffer); + var totalRead = 0L; + for (var buffer : buffers) { + var read = channel.read(buffer); if (read == -1) { break; } @@ -90,13 +90,13 @@ final class BufferHolder { if (null == stream) { throw new NullPointerException("Unable to use a NULL InputStream"); } - List chunks = new ArrayList<>(); - long total = 0; - byte[] tmp = new byte[chunkSize]; + var chunks = new ArrayList(); + var total = 0L; + var tmp = new byte[chunkSize]; int read; while (-1 != (read = stream.read(tmp))) { - ByteBuffer chunk = ByteBuffer.allocate(read); + var chunk = ByteBuffer.allocate(read); chunk.put(tmp, 0, read); chunk.flip(); chunks.add(chunk); @@ -104,9 +104,9 @@ final class BufferHolder { } if (total <= chunkSize) { - byte[] data = new byte[(int) total]; - int pos = 0; - for (ByteBuffer chunk : chunks) { + var data = new byte[(int) total]; + var pos = 0; + for (var chunk : chunks) { System.arraycopy(chunk.array(), 0, data, pos, chunk.capacity()); pos += chunk.capacity(); } diff --git a/src/main/java/com/maxmind/db/CHMCache.java b/src/main/java/com/maxmind/db/CHMCache.java index 0b22d4cc..250b45b1 100644 --- a/src/main/java/com/maxmind/db/CHMCache.java +++ b/src/main/java/com/maxmind/db/CHMCache.java @@ -37,7 +37,7 @@ public CHMCache(int capacity) { @Override public DecodedValue get(CacheKey key, Loader loader) throws IOException { - DecodedValue value = cache.get(key); + var value = cache.get(key); if (value == null) { value = loader.load(key); if (!cacheFull) { diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index 74f194cf..cf2c960a 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -87,26 +87,26 @@ private DecodedValue decode(CacheKey key) throws IOException { private DecodedValue decode(Class cls, java.lang.reflect.Type genericType) throws IOException { - int ctrlByte = 0xFF & this.buffer.get(); + var ctrlByte = 0xFF & this.buffer.get(); - Type type = Type.fromControlByte(ctrlByte); + var type = Type.fromControlByte(ctrlByte); // Pointers are a special case, we don't read the next 'size' bytes, we // use the size to determine the length of the pointer and then follow // it. if (type.equals(Type.POINTER)) { - int pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; - int base = pointerSize == 4 ? (byte) 0 : (byte) (ctrlByte & 0x7); - int packed = this.decodeInteger(base, pointerSize); - long pointer = packed + this.pointerBase + POINTER_VALUE_OFFSETS[pointerSize]; + var pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; + var base = pointerSize == 4 ? (byte) 0 : (byte) (ctrlByte & 0x7); + var packed = this.decodeInteger(base, pointerSize); + var pointer = packed + this.pointerBase + POINTER_VALUE_OFFSETS[pointerSize]; return decodePointer(pointer, cls, genericType); } if (type.equals(Type.EXTENDED)) { - int nextByte = this.buffer.get(); + var nextByte = this.buffer.get(); - int typeNum = nextByte + 7; + var typeNum = nextByte + 7; if (typeNum < 8) { throw new InvalidDatabaseException( @@ -132,11 +132,11 @@ private DecodedValue decode(Class cls, java.lang.reflect.Type genericType DecodedValue decodePointer(long pointer, Class cls, java.lang.reflect.Type genericType) throws IOException { - long targetOffset = pointer; - long position = buffer.position(); + var targetOffset = pointer; + var position = buffer.position(); - CacheKey key = new CacheKey<>(targetOffset, cls, genericType); - DecodedValue o = cache.get(key, cacheLoader); + var key = new CacheKey<>(targetOffset, cls, genericType); + var o = cache.get(key, cacheLoader); buffer.position(position); return o; @@ -154,7 +154,7 @@ private Object decodeByType( case ARRAY: Class elementClass = Object.class; if (genericType instanceof ParameterizedType ptype) { - java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments(); + var actualTypes = ptype.getActualTypeArguments(); if (actualTypes.length == 1) { elementClass = (Class) actualTypes[0]; } @@ -186,9 +186,9 @@ private Object decodeByType( } private String decodeString(long size) throws CharacterCodingException { - long oldLimit = buffer.limit(); + var oldLimit = buffer.limit(); buffer.limit(buffer.position() + size); - String s = buffer.decode(utfDecoder); + var s = buffer.decode(utfDecoder); buffer.limit(oldLimit); return s; } @@ -234,7 +234,7 @@ static int decodeInteger(Buffer buffer, int base, int size) { } private BigInteger decodeBigInteger(int size) { - byte[] bytes = this.getByteArray(size); + var bytes = this.getByteArray(size); return new BigInteger(1, bytes); } @@ -287,10 +287,10 @@ private List decodeArray( throw new DeserializationException( "No constructor found for the List: " + e.getMessage(), e); } - Object[] parameters = {size}; + var parameters = new Object[]{size}; try { @SuppressWarnings("unchecked") - List array2 = (List) constructor.newInstance(parameters); + var array2 = (List) constructor.newInstance(parameters); array = array2; } catch (InstantiationException | IllegalAccessException @@ -300,7 +300,7 @@ private List decodeArray( } for (int i = 0; i < size; i++) { - Object e = this.decode(elementClass, null).value(); + var e = this.decode(elementClass, null).value(); array.add(elementClass.cast(e)); } @@ -315,9 +315,9 @@ private Object decodeMap( if (Map.class.isAssignableFrom(cls) || cls.equals(Object.class)) { Class valueClass = Object.class; if (genericType instanceof ParameterizedType ptype) { - java.lang.reflect.Type[] actualTypes = ptype.getActualTypeArguments(); + var actualTypes = ptype.getActualTypeArguments(); if (actualTypes.length == 2) { - Class keyClass = (Class) actualTypes[0]; + var keyClass = (Class) actualTypes[0]; if (!keyClass.equals(String.class)) { throw new DeserializationException("Map keys must be strings."); } @@ -347,10 +347,10 @@ private Map decodeMapIntoMap( throw new DeserializationException( "No constructor found for the Map: " + e.getMessage(), e); } - Object[] parameters = {size}; + var parameters = new Object[]{size}; try { @SuppressWarnings("unchecked") - Map map2 = (Map) constructor.newInstance(parameters); + var map2 = (Map) constructor.newInstance(parameters); map = map2; } catch (InstantiationException | IllegalAccessException @@ -360,8 +360,8 @@ private Map decodeMapIntoMap( } for (int i = 0; i < size; i++) { - String key = (String) this.decode(String.class, null).value(); - Object value = this.decode(valueClass, null).value(); + var key = (String) this.decode(String.class, null).value(); + var value = this.decode(valueClass, null).value(); try { map.put(key, valueClass.cast(value)); } catch (ClassCastException e) { @@ -375,7 +375,7 @@ private Map decodeMapIntoMap( private Object decodeMapIntoObject(int size, Class cls) throws IOException { - CachedConstructor cachedConstructor = getCachedConstructor(cls); + var cachedConstructor = getCachedConstructor(cls); Constructor constructor; Class[] parameterTypes; java.lang.reflect.Type[] parameterGenericTypes; @@ -388,9 +388,9 @@ private Object decodeMapIntoObject(int size, Class cls) parameterGenericTypes = constructor.getGenericParameterTypes(); parameterIndexes = new HashMap<>(); - Annotation[][] annotations = constructor.getParameterAnnotations(); + var annotations = constructor.getParameterAnnotations(); for (int i = 0; i < constructor.getParameterCount(); i++) { - String parameterName = getParameterName(cls, i, annotations[i]); + var parameterName = getParameterName(cls, i, annotations[i]); parameterIndexes.put(parameterName, i); } @@ -410,13 +410,13 @@ private Object decodeMapIntoObject(int size, Class cls) parameterIndexes = cachedConstructor.parameterIndexes(); } - Object[] parameters = new Object[parameterTypes.length]; + var parameters = new Object[parameterTypes.length]; for (int i = 0; i < size; i++) { - String key = (String) this.decode(String.class, null).value(); + var key = (String) this.decode(String.class, null).value(); - Integer parameterIndex = parameterIndexes.get(key); + var parameterIndex = parameterIndexes.get(key); if (parameterIndex == null) { - long offset = this.nextValueOffset(this.buffer.position(), 1); + var offset = this.nextValueOffset(this.buffer.position(), 1); this.buffer.position(offset); continue; } @@ -434,9 +434,9 @@ private Object decodeMapIntoObject(int size, Class cls) | InvocationTargetException e) { throw new DeserializationException("Error creating object: " + e.getMessage(), e); } catch (IllegalArgumentException e) { - StringBuilder sbErrors = new StringBuilder(); - for (String key : parameterIndexes.keySet()) { - int index = parameterIndexes.get(key); + var sbErrors = new StringBuilder(); + for (var key : parameterIndexes.keySet()) { + var index = parameterIndexes.get(key); if (parameters[index] != null && !parameters[index].getClass().isAssignableFrom(parameterTypes[index])) { sbErrors.append(" argument type mismatch in " + key + " MMDB Type: " @@ -458,8 +458,8 @@ private CachedConstructor getCachedConstructor(Class cls) { private static Constructor findConstructor(Class cls) throws ConstructorNotFoundException { - Constructor[] constructors = cls.getConstructors(); - for (Constructor constructor : constructors) { + var constructors = cls.getConstructors(); + for (var constructor : constructors) { if (constructor.getAnnotation(MaxMindDbConstructor.class) == null) { continue; } @@ -477,11 +477,11 @@ private static String getParameterName( int index, Annotation[] annotations ) throws ParameterNotFoundException { - for (Annotation annotation : annotations) { + for (var annotation : annotations) { if (!annotation.annotationType().equals(MaxMindDbParameter.class)) { continue; } - MaxMindDbParameter paramAnnotation = (MaxMindDbParameter) annotation; + var paramAnnotation = (MaxMindDbParameter) annotation; return paramAnnotation.name(); } throw new ParameterNotFoundException( @@ -495,15 +495,15 @@ private long nextValueOffset(long offset, int numberToSkip) return offset; } - CtrlData ctrlData = this.getCtrlData(offset); - int ctrlByte = ctrlData.ctrlByte(); - int size = ctrlData.size(); + var ctrlData = this.getCtrlData(offset); + var ctrlByte = ctrlData.ctrlByte(); + var size = ctrlData.size(); offset = ctrlData.offset(); - Type type = ctrlData.type(); + var type = ctrlData.type(); switch (type) { case POINTER: - int pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; + var pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; offset += pointerSize; break; case MAP: @@ -531,15 +531,15 @@ private CtrlData getCtrlData(long offset) } this.buffer.position(offset); - int ctrlByte = 0xFF & this.buffer.get(); + var ctrlByte = 0xFF & this.buffer.get(); offset++; - Type type = Type.fromControlByte(ctrlByte); + var type = Type.fromControlByte(ctrlByte); if (type.equals(Type.EXTENDED)) { - int nextByte = this.buffer.get(); + var nextByte = this.buffer.get(); - int typeNum = nextByte + 7; + var typeNum = nextByte + 7; if (typeNum < 8) { throw new InvalidDatabaseException( @@ -552,9 +552,9 @@ private CtrlData getCtrlData(long offset) offset++; } - int size = ctrlByte & 0x1f; + var size = ctrlByte & 0x1f; if (size >= 29) { - int bytesToRead = size - 28; + var bytesToRead = size - 28; offset += bytesToRead; size = switch (size) { case 29 -> 29 + (0xFF & buffer.get()); @@ -571,7 +571,7 @@ private byte[] getByteArray(int length) { } private static byte[] getByteArray(Buffer buffer, int length) { - byte[] bytes = new byte[length]; + var bytes = new byte[length]; buffer.get(bytes); return bytes; } diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index 83780b57..d3d5f81c 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -45,7 +45,7 @@ final class MultiBuffer implements Buffer { */ MultiBuffer(ByteBuffer[] buffers, int chunkSize) { for (int i = 0; i < buffers.length; i++) { - ByteBuffer chunk = buffers[i]; + var chunk = buffers[i]; if (chunk.capacity() == chunkSize) { continue; } @@ -63,8 +63,8 @@ final class MultiBuffer implements Buffer { this.buffers[i] = buffers[i].asReadOnlyBuffer(); } - long capacity = 0; - for (ByteBuffer buffer : this.buffers) { + var capacity = 0L; + for (var buffer : this.buffers) { capacity += buffer.capacity(); } this.capacity = capacity; @@ -116,7 +116,7 @@ public Buffer limit(long newLimit) { /** {@inheritDoc} */ @Override public byte get() { - byte value = get(position); + var value = get(position); position++; return value; } @@ -131,15 +131,15 @@ public Buffer get(byte[] dst) { + ", limit=" + limit ); } - long pos = position; - int offset = 0; - int length = dst.length; + var pos = position; + var offset = 0; + var length = dst.length; while (length > 0) { - int bufIndex = (int) (pos / this.chunkSize); - int bufOffset = (int) (pos % this.chunkSize); - ByteBuffer buf = buffers[bufIndex]; + var bufIndex = (int) (pos / this.chunkSize); + var bufOffset = (int) (pos % this.chunkSize); + var buf = buffers[bufIndex]; buf.position(bufOffset); - int toRead = Math.min(buf.remaining(), length); + var toRead = Math.min(buf.remaining(), length); buf.get(dst, offset, toRead); pos += toRead; offset += toRead; @@ -155,24 +155,24 @@ public byte get(long index) { if (index < 0 || index >= limit) { throw new IndexOutOfBoundsException("Index: " + index); } - int bufIndex = (int) (index / this.chunkSize); - int offset = (int) (index % this.chunkSize); + var bufIndex = (int) (index / this.chunkSize); + var offset = (int) (index % this.chunkSize); return buffers[bufIndex].get(offset); } /** {@inheritDoc} */ @Override public double getDouble() { - int bufIndex = (int) (position / this.chunkSize); - int off = (int) (position % this.chunkSize); - ByteBuffer buf = buffers[bufIndex]; + var bufIndex = (int) (position / this.chunkSize); + var off = (int) (position % this.chunkSize); + var buf = buffers[bufIndex]; buf.position(off); if (buf.remaining() >= 8) { - double value = buf.getDouble(); + var value = buf.getDouble(); position += 8; return value; } else { - byte[] eight = new byte[8]; + var eight = new byte[8]; get(eight); return ByteBuffer.wrap(eight).getDouble(); } @@ -181,16 +181,16 @@ public double getDouble() { /** {@inheritDoc} */ @Override public float getFloat() { - int bufIndex = (int) (position / this.chunkSize); - int off = (int) (position % this.chunkSize); - ByteBuffer buf = buffers[bufIndex]; + var bufIndex = (int) (position / this.chunkSize); + var off = (int) (position % this.chunkSize); + var buf = buffers[bufIndex]; buf.position(off); if (buf.remaining() >= 4) { - float value = buf.getFloat(); + var value = buf.getFloat(); position += 4; return value; } else { - byte[] four = new byte[4]; + var four = new byte[4]; get(four); return ByteBuffer.wrap(four).getFloat(); } @@ -199,11 +199,11 @@ public float getFloat() { /** {@inheritDoc} */ @Override public Buffer duplicate() { - ByteBuffer[] duplicatedBuffers = new ByteBuffer[buffers.length]; + var duplicatedBuffers = new ByteBuffer[buffers.length]; for (int i = 0; i < buffers.length; i++) { duplicatedBuffers[i] = buffers[i].duplicate(); } - MultiBuffer copy = new MultiBuffer(duplicatedBuffers, chunkSize); + var copy = new MultiBuffer(duplicatedBuffers, chunkSize); copy.position = this.position; copy.limit = this.limit; return copy; @@ -218,7 +218,7 @@ public String decode(CharsetDecoder decoder) String decode(CharsetDecoder decoder, int maxCharBufferSize) throws CharacterCodingException { - long remainingBytes = limit - position; + var remainingBytes = limit - position; // Cannot allocate more than maxCharBufferSize for CharBuffer if (remainingBytes > maxCharBufferSize) { @@ -227,22 +227,22 @@ String decode(CharsetDecoder decoder, int maxCharBufferSize) ); } - CharBuffer out = CharBuffer.allocate((int) remainingBytes); - long pos = position; + var out = CharBuffer.allocate((int) remainingBytes); + var pos = position; while (remainingBytes > 0) { // Locate which underlying buffer we are in - int bufIndex = (int) (pos / this.chunkSize); - int bufOffset = (int) (pos % this.chunkSize); + var bufIndex = (int) (pos / this.chunkSize); + var bufOffset = (int) (pos % this.chunkSize); - ByteBuffer srcView = buffers[bufIndex]; - int savedLimit = srcView.limit(); + var srcView = buffers[bufIndex]; + var savedLimit = srcView.limit(); srcView.position(bufOffset); - int toRead = (int) Math.min(srcView.remaining(), remainingBytes); + var toRead = (int) Math.min(srcView.remaining(), remainingBytes); srcView.limit(bufOffset + toRead); - CoderResult result = decoder.decode(srcView, out, false); + var result = decoder.decode(srcView, out, false); srcView.limit(savedLimit); if (result.isError()) { @@ -269,21 +269,21 @@ String decode(CharsetDecoder decoder, int maxCharBufferSize) * @throws IOException if an I/O error occurs */ public static MultiBuffer mapFromChannel(FileChannel channel) throws IOException { - long size = channel.size(); + var size = channel.size(); if (size <= 0) { throw new IllegalArgumentException("File channel has no data"); } - int fullChunks = (int) (size / DEFAULT_CHUNK_SIZE); - int remainder = (int) (size % DEFAULT_CHUNK_SIZE); - int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); + var fullChunks = (int) (size / DEFAULT_CHUNK_SIZE); + var remainder = (int) (size % DEFAULT_CHUNK_SIZE); + var totalChunks = fullChunks + (remainder > 0 ? 1 : 0); - ByteBuffer[] buffers = new ByteBuffer[totalChunks]; - long remaining = size; + var buffers = new ByteBuffer[totalChunks]; + var remaining = size; for (int i = 0; i < totalChunks; i++) { - long chunkPos = (long) i * DEFAULT_CHUNK_SIZE; - long chunkSize = Math.min(DEFAULT_CHUNK_SIZE, remaining); + var chunkPos = (long) i * DEFAULT_CHUNK_SIZE; + var chunkSize = Math.min(DEFAULT_CHUNK_SIZE, remaining); buffers[i] = channel.map( FileChannel.MapMode.READ_ONLY, chunkPos, diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java index 0912ff56..604b9432 100644 --- a/src/main/java/com/maxmind/db/Networks.java +++ b/src/main/java/com/maxmind/db/Networks.java @@ -79,11 +79,11 @@ public final class Networks implements Iterator> { @Override public DatabaseRecord next() { try { - T data = this.reader.resolveDataPointer( + var data = this.reader.resolveDataPointer( this.buffer, this.lastNode.pointer, this.typeParameterClass); - byte[] ip = this.lastNode.ip; - int prefixLength = this.lastNode.prefix; + var ip = this.lastNode.ip; + var prefixLength = this.lastNode.prefix; // We do this because uses of includeAliasedNetworks will get IPv4 networks // from the ::FFFF:0:0/96. We want to return the IPv4 form of the address @@ -95,7 +95,7 @@ public DatabaseRecord next() { // If the ip is in ipv6 form, drop the prefix manually // as InetAddress converts it to ipv4. - InetAddress ipAddr = InetAddress.getByAddress(ip); + var ipAddr = InetAddress.getByAddress(ip); if (ipAddr instanceof Inet4Address && ip.length > 4 && prefixLength > 96) { prefixLength -= 96; } @@ -129,7 +129,7 @@ private boolean isInIpv4Subtree(byte[] ip) { @Override public boolean hasNext() { while (!this.nodes.isEmpty()) { - NetworkNode node = this.nodes.pop(); + var node = this.nodes.pop(); // Next until we don't have data. while (node.pointer != this.reader.getMetadata().nodeCount()) { @@ -146,7 +146,7 @@ public boolean hasNext() { return true; } - byte[] ipRight = Arrays.copyOf(node.ip, node.ip.length); + var ipRight = Arrays.copyOf(node.ip, node.ip.length); if (ipRight.length <= (node.prefix >> 3)) { throw new NetworksIterationException("Invalid search tree"); } @@ -154,7 +154,7 @@ public boolean hasNext() { ipRight[node.prefix >> 3] |= 1 << (7 - (node.prefix % 8)); try { - long rightPointer = this.reader.readNode(this.buffer, node.pointer, 1); + var rightPointer = this.reader.readNode(this.buffer, node.pointer, 1); node.prefix++; this.nodes.push(new NetworkNode(ipRight, node.prefix, rightPointer)); diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index 1e0e85fb..1debebb3 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -152,10 +152,10 @@ private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws I } this.cache = cache; - Buffer buffer = bufferHolder.get(); + var buffer = bufferHolder.get(); long start = this.findMetadataStart(buffer, name); - Decoder metadataDecoder = new Decoder(this.cache, buffer, start); + var metadataDecoder = new Decoder(this.cache, buffer, start); this.metadata = metadataDecoder.decode(start, Metadata.class); this.ipV4Start = this.findIpV4StartNode(buffer); @@ -193,15 +193,15 @@ long getIpv4Start() { public DatabaseRecord getRecord(InetAddress ipAddress, Class cls) throws IOException { - byte[] rawAddress = ipAddress.getAddress(); + var rawAddress = ipAddress.getAddress(); - long[] traverseResult = traverseTree(rawAddress, rawAddress.length * 8); + var traverseResult = traverseTree(rawAddress, rawAddress.length * 8); long record = traverseResult[0]; int pl = (int) traverseResult[1]; long nodeCount = this.metadata.nodeCount(); - Buffer buffer = this.getBufferHolder().get(); + var buffer = this.getBufferHolder().get(); T dataRecord = null; if (record > nodeCount) { // record is a data pointer @@ -254,13 +254,13 @@ public Networks networks( InvalidNetworkException, ClosedDatabaseException, InvalidDatabaseException { try { if (this.getMetadata().ipVersion() == 6) { - InetAddress ipv6 = InetAddress.getByAddress(new byte[16]); - Network ipAllV6 = new Network(ipv6, 0); // Mask 128. + var ipv6 = InetAddress.getByAddress(new byte[16]); + var ipAllV6 = new Network(ipv6, 0); // Mask 128. return this.networksWithin(ipAllV6, includeAliasedNetworks, typeParameterClass); } - InetAddress ipv4 = InetAddress.getByAddress(new byte[4]); - Network ipAllV4 = new Network(ipv4, 0); // Mask 32. + var ipv4 = InetAddress.getByAddress(new byte[4]); + var ipAllV4 = new Network(ipv4, 0); // Mask 32. return this.networksWithin(ipAllV4, includeAliasedNetworks, typeParameterClass); } catch (UnknownHostException e) { /* This is returned by getByAddress. This should never happen @@ -270,7 +270,7 @@ public Networks networks( } BufferHolder getBufferHolder() throws ClosedDatabaseException { - BufferHolder bufferHolder = this.bufferHolderReference.get(); + var bufferHolder = this.bufferHolderReference.get(); if (bufferHolder == null) { throw new ClosedDatabaseException(); } @@ -322,12 +322,12 @@ public Networks networksWithin( boolean includeAliasedNetworks, Class typeParameterClass) throws InvalidNetworkException, ClosedDatabaseException, InvalidDatabaseException { - InetAddress networkAddress = network.networkAddress(); + var networkAddress = network.networkAddress(); if (this.metadata.ipVersion() == 4 && networkAddress instanceof Inet6Address) { throw new InvalidNetworkException(networkAddress); } - byte[] ipBytes = networkAddress.getAddress(); + var ipBytes = networkAddress.getAddress(); int prefixLength = network.prefixLength(); if (this.metadata.ipVersion() == 6 && ipBytes.length == IPV4_LEN) { @@ -343,7 +343,7 @@ public Networks networksWithin( prefixLength += 96; } - long[] traverseResult = this.traverseTree(ipBytes, prefixLength); + var traverseResult = this.traverseTree(ipBytes, prefixLength); long node = traverseResult[0]; int prefix = (int) traverseResult[1]; @@ -361,7 +361,7 @@ public Networks networksWithin( */ private long[] traverseTree(byte[] ip, int bitCount) throws ClosedDatabaseException, InvalidDatabaseException { - Buffer buffer = this.getBufferHolder().get(); + var buffer = this.getBufferHolder().get(); int bitLength = ip.length * 8; long record = this.startNode(bitLength); long nodeCount = this.metadata.nodeCount(); @@ -383,9 +383,9 @@ long readNode(Buffer buffer, long nodeNumber, int index) throws InvalidDatabaseException { // index is the index of the record within the node, which // can either be 0 or 1. - long baseOffset = nodeNumber * this.metadata.nodeByteSize(); + var baseOffset = nodeNumber * this.metadata.nodeByteSize(); - int recordSize = this.metadata.recordSize(); + var recordSize = this.metadata.recordSize(); return switch (recordSize) { case 24 -> { // For a 24 bit record, each record is 3 bytes. @@ -424,7 +424,7 @@ T resolveDataPointer( // We only want the data from the decoder, not the offset where it was // found. - Decoder decoder = new Decoder( + var decoder = new Decoder( this.cache, buffer, this.metadata.searchTreeSize() + DATA_SECTION_SEPARATOR_SIZE, @@ -443,7 +443,7 @@ T resolveDataPointer( */ private long findMetadataStart(Buffer buffer, String databaseName) throws InvalidDatabaseException { - long fileSize = buffer.capacity(); + var fileSize = buffer.capacity(); FILE: for (long i = 0; i < fileSize - METADATA_START_MARKER.length + 1; i++) { diff --git a/src/main/java/com/maxmind/db/SingleBuffer.java b/src/main/java/com/maxmind/db/SingleBuffer.java index 36f25ad3..eca1629a 100644 --- a/src/main/java/com/maxmind/db/SingleBuffer.java +++ b/src/main/java/com/maxmind/db/SingleBuffer.java @@ -126,7 +126,7 @@ public static SingleBuffer wrap(byte[] array) { */ public static SingleBuffer mapFromChannel(FileChannel channel) throws IOException { - ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size()); + var buffer = channel.map(MapMode.READ_ONLY, 0, channel.size()); return new SingleBuffer(buffer); } } diff --git a/src/test/java/com/maxmind/db/DecoderTest.java b/src/test/java/com/maxmind/db/DecoderTest.java index 5e02c347..07d00d01 100644 --- a/src/test/java/com/maxmind/db/DecoderTest.java +++ b/src/test/java/com/maxmind/db/DecoderTest.java @@ -20,7 +20,7 @@ public class DecoderTest { private static Map int32() { int max = (2 << 30) - 1; - HashMap int32 = new HashMap<>(); + var int32 = new HashMap(); int32.put(0, new byte[] {0x0, 0x1}); int32.put(-1, new byte[] {0x4, 0x1, (byte) 0xff, (byte) 0xff, @@ -49,7 +49,7 @@ private static Map int32() { private static Map uint32() { long max = (((long) 1) << 32) - 1; - HashMap uint32s = new HashMap<>(); + var uint32s = new HashMap(); uint32s.put((long) 0, new byte[] {(byte) 0xc0}); uint32s.put((long) ((1 << 8) - 1), new byte[] {(byte) 0xc1, @@ -69,7 +69,7 @@ private static Map uint32() { private static Map uint16() { int max = (1 << 16) - 1; - Map uint16s = new HashMap<>(); + var uint16s = new HashMap(); uint16s.put(0, new byte[] {(byte) 0xa0}); uint16s.put((1 << 8) - 1, new byte[] {(byte) 0xa1, (byte) 0xff}); @@ -80,7 +80,7 @@ private static Map uint16() { } private static Map largeUint(int bits) { - Map uints = new HashMap<>(); + var uints = new HashMap(); byte ctrlByte = (byte) (bits == 64 ? 0x2 : 0x3); @@ -92,10 +92,10 @@ private static Map largeUint(int bits) { for (int power = 1; power <= bits / 8; power++) { - BigInteger key = BigInteger.valueOf(2).pow(8 * power) + var key = BigInteger.valueOf(2).pow(8 * power) .subtract(BigInteger.valueOf(1)); - byte[] value = new byte[2 + power]; + var value = new byte[2 + power]; value[0] = (byte) power; value[1] = ctrlByte; for (int i = 2; i < value.length; i++) { @@ -108,7 +108,7 @@ private static Map largeUint(int bits) { } private static Map pointers() { - Map pointers = new HashMap<>(); + var pointers = new HashMap(); pointers.put((long) 0, new byte[] {0x20, 0x0}); pointers.put((long) 5, new byte[] {0x20, 0x5}); @@ -131,7 +131,7 @@ private static Map pointers() { } private static Map strings() { - Map strings = new HashMap<>(); + var strings = new HashMap(); DecoderTest.addTestString(strings, (byte) 0x40, ""); DecoderTest.addTestString(strings, (byte) 0x41, "1"); @@ -167,12 +167,12 @@ private static Map strings() { } private static Map bytes() { - Map bytes = new HashMap<>(); + var bytes = new HashMap(); - Map strings = DecoderTest.strings(); + var strings = DecoderTest.strings(); for (String s : strings.keySet()) { - byte[] ba = strings.get(s); + var ba = strings.get(s); ba[0] ^= 0xc0; bytes.put(s.getBytes(StandardCharsets.UTF_8), ba); @@ -189,8 +189,8 @@ private static void addTestString(Map tests, byte ctrl, private static void addTestString(Map tests, byte[] ctrl, String str) { - byte[] sb = str.getBytes(StandardCharsets.UTF_8); - byte[] bytes = new byte[ctrl.length + sb.length]; + var sb = str.getBytes(StandardCharsets.UTF_8); + var bytes = new byte[ctrl.length + sb.length]; System.arraycopy(ctrl, 0, bytes, 0, ctrl.length); System.arraycopy(sb, 0, bytes, ctrl.length, sb.length); @@ -198,7 +198,7 @@ private static void addTestString(Map tests, byte[] ctrl, } private static Map doubles() { - Map doubles = new HashMap<>(); + var doubles = new HashMap(); doubles.put(0.0, new byte[] {0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}); doubles.put(0.5, new byte[] {0x68, 0x3F, (byte) 0xE0, 0x0, 0x0, 0x0, @@ -220,7 +220,7 @@ private static Map doubles() { } private static Map floats() { - Map floats = new HashMap<>(); + var floats = new HashMap(); floats.put((float) 0.0, new byte[] {0x4, 0x8, 0x0, 0x0, 0x0, 0x0}); floats.put((float) 1.0, new byte[] {0x4, 0x8, 0x3F, (byte) 0x80, 0x0, 0x0}); @@ -243,7 +243,7 @@ private static Map floats() { } private static Map booleans() { - Map booleans = new HashMap<>(); + var booleans = new HashMap(); booleans.put(Boolean.FALSE, new byte[] {0x0, 0x7}); booleans.put(Boolean.TRUE, new byte[] {0x1, 0x7}); @@ -251,17 +251,17 @@ private static Map booleans() { } private static Map, byte[]> maps() { - Map, byte[]> maps = new HashMap<>(); + var maps = new HashMap, byte[]>(); - Map empty = Map.of(); + var empty = Map.of(); maps.put(empty, new byte[] {(byte) 0xe0}); - Map one = new HashMap<>(); + var one = new HashMap(); one.put("en", "Foo"); maps.put(one, new byte[] {(byte) 0xe1, /* en */0x42, 0x65, 0x6e, /* Foo */0x43, 0x46, 0x6f, 0x6f}); - Map two = new HashMap<>(); + var two = new HashMap(); two.put("en", "Foo"); two.put("zh", "人"); maps.put(two, new byte[] {(byte) 0xe2, @@ -274,7 +274,7 @@ private static Map booleans() { /* 人 */ 0x43, (byte) 0xe4, (byte) 0xba, (byte) 0xba}); - Map> nested = new HashMap<>(); + var nested = new HashMap>(); nested.put("name", two); maps.put(nested, new byte[] {(byte) 0xe1, /* name */ @@ -287,8 +287,8 @@ private static Map booleans() { /* 人 */ 0x43, (byte) 0xe4, (byte) 0xba, (byte) 0xba}); - Map> guess = new HashMap<>(); - List languages = new ArrayList<>(); + var guess = new HashMap>(); + var languages = new ArrayList(); languages.add("en"); languages.add("zh"); guess.put("languages", languages); @@ -305,15 +305,15 @@ private static Map booleans() { } private static Map, byte[]> arrays() { - Map, byte[]> arrays = new HashMap<>(); + var arrays = new HashMap, byte[]>(); - ArrayList f1 = new ArrayList<>(); + var f1 = new ArrayList(); f1.add("Foo"); arrays.put(f1, new byte[] {0x1, 0x4, /* Foo */ 0x43, 0x46, 0x6f, 0x6f}); - ArrayList f2 = new ArrayList<>(); + var f2 = new ArrayList(); f2.add("Foo"); f2.add("人"); arrays.put(f2, new byte[] {0x2, 0x4, @@ -322,7 +322,7 @@ private static Map, byte[]> arrays() { /* 人 */ 0x43, (byte) 0xe4, (byte) 0xba, (byte) 0xba}); - ArrayList empty = new ArrayList<>(); + var empty = new ArrayList(); arrays.put(empty, new byte[] {0x0, 0x4}); return arrays; @@ -398,10 +398,10 @@ public void testArrays() throws IOException { @Test public void testInvalidControlByte() { - SingleBuffer buffer = SingleBuffer.wrap(new byte[] {0x0, 0xF}); + var buffer = SingleBuffer.wrap(new byte[] {0x0, 0xF}); - Decoder decoder = new Decoder(new CHMCache(), buffer, 0); - InvalidDatabaseException ex = assertThrows( + var decoder = new Decoder(new CHMCache(), buffer, 0); + var ex = assertThrows( InvalidDatabaseException.class, () -> decoder.decode(0, String.class)); assertThat(ex.getMessage(), @@ -410,16 +410,16 @@ public void testInvalidControlByte() { private static void testTypeDecoding(Type type, Map tests) throws IOException { - NodeCache cache = new CHMCache(); + var cache = new CHMCache(); for (Map.Entry entry : tests.entrySet()) { - T expect = entry.getKey(); - byte[] input = entry.getValue(); + var expect = entry.getKey(); + var input = entry.getValue(); - String desc = "decoded " + type.name() + " - " + expect; - SingleBuffer buffer = SingleBuffer.wrap(input); + var desc = "decoded " + type.name() + " - " + expect; + var buffer = SingleBuffer.wrap(input); - Decoder decoder = new TestDecoder(cache, buffer, 0); + var decoder = new TestDecoder(cache, buffer, 0); switch (type) { case BYTES: @@ -455,14 +455,14 @@ private static void testTypeDecoding(Type type, Map tests) default: { // We hit this for Type.MAP. - Map got = decoder.decode(0, Map.class); - Map expectMap = (Map) expect; + var got = decoder.decode(0, Map.class); + var expectMap = (Map) expect; assertEquals(expectMap.size(), got.size(), desc); for (Object keyObject : expectMap.keySet()) { - String key = (String) keyObject; - Object value = expectMap.get(key); + var key = (String) keyObject; + var value = expectMap.get(key); if (value instanceof Object[] arrayValue) { assertArrayEquals(arrayValue, (Object[]) got.get(key), desc); diff --git a/src/test/java/com/maxmind/db/MultiBufferTest.java b/src/test/java/com/maxmind/db/MultiBufferTest.java index 16f9fdf7..ed27633e 100644 --- a/src/test/java/com/maxmind/db/MultiBufferTest.java +++ b/src/test/java/com/maxmind/db/MultiBufferTest.java @@ -20,15 +20,15 @@ public class MultiBufferTest { * Helper method to create a MultiBuffer with a single empty ByteBuffer. */ static MultiBuffer createEmptyBuffer(int capacity) { - ByteBuffer bb = ByteBuffer.allocate(capacity); + var bb = ByteBuffer.allocate(capacity); bb.flip(); return new MultiBuffer(new ByteBuffer[]{bb}, capacity); } static MultiBuffer createBuffer(int chunkSize) { try { - Path tmpFile = Files.createTempFile("test-data", ".bin"); - byte[] data = new byte[]{ + var tmpFile = Files.createTempFile("test-data", ".bin"); + var data = new byte[]{ // uint16: 500 (byte) 0xa2, 0x1, (byte) 0xf4, @@ -78,12 +78,12 @@ static MultiBuffer createBuffer(int chunkSize) { try (RandomAccessFile file = new RandomAccessFile(tmpFile.toFile(), "r"); FileChannel channel = file.getChannel()) { - long size = channel.size(); - int fullChunks = (int) (size / chunkSize); - int remainder = (int) (size % chunkSize); - int totalChunks = fullChunks + (remainder > 0 ? 1 : 0); + var size = channel.size(); + var fullChunks = (int) (size / chunkSize); + var remainder = (int) (size % chunkSize); + var totalChunks = fullChunks + (remainder > 0 ? 1 : 0); - ByteBuffer[] buffers = new ByteBuffer[totalChunks]; + var buffers = new ByteBuffer[totalChunks]; for (int i = 0; i < fullChunks; i++) { buffers[i] = ByteBuffer.allocate(chunkSize); } @@ -106,46 +106,46 @@ static MultiBuffer createBuffer(int chunkSize) { @Test public void testPositionSetter() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); buffer.position(500); assertEquals(500, buffer.position()); } @Test public void testPositionSetterInvalidNegative() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.position(-1)); } @Test public void testPositionSetterExceedsLimit() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); buffer.limit(500); assertThrows(IllegalArgumentException.class, () -> buffer.position(600)); } @Test public void testLimitSetter() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); buffer.limit(500); assertEquals(500, buffer.limit()); } @Test public void testLimitSetterInvalidNegative() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.limit(-1)); } @Test public void testLimitSetterExceedsCapacity() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); assertThrows(IllegalArgumentException.class, () -> buffer.limit(1001)); } @Test public void testLimitSetterAdjustsPosition() { - MultiBuffer buffer = createEmptyBuffer(1000); + var buffer = createEmptyBuffer(1000); buffer.position(800); buffer.limit(500); assertEquals(500, buffer.position()); @@ -153,14 +153,14 @@ public void testLimitSetterAdjustsPosition() { @Test public void testGetByIndex() { - MultiBuffer buffer = createBuffer(24); + var buffer = createBuffer(24); assertEquals(0x2a, buffer.get(4)); assertEquals(0x1, buffer.get(10)); } @Test public void testGetByIndexOutOfBounds() { - MultiBuffer buffer = createBuffer(24); + var buffer = createBuffer(24); buffer.limit(50); assertThrows(IndexOutOfBoundsException.class, () -> buffer.get(50)); assertThrows(IndexOutOfBoundsException.class, () -> buffer.get(-1)); @@ -168,18 +168,18 @@ public void testGetByIndexOutOfBounds() { @Test public void testGetSingleByte() { - MultiBuffer buffer = createBuffer(24); + var buffer = createBuffer(24); assertEquals((byte) 0xa2, buffer.get()); assertEquals(1, buffer.position()); } @Test public void testGetByteArray() { - MultiBuffer buffer = createBuffer(24); - byte[] dst = new byte[10]; + var buffer = createBuffer(24); + var dst = new byte[10]; buffer.position(32); buffer.get(dst); - byte[] expectedBytes = new byte[]{ + var expectedBytes = new byte[]{ 0x2, 0x4, 0x43, 0x46, 0x6f, 0x6f, 0x43, (byte) 0xe4, (byte) 0xba, (byte) 0xba}; @@ -189,19 +189,19 @@ public void testGetByteArray() { @Test public void testGetByteArrayExceedsLimit() { - MultiBuffer buffer = createEmptyBuffer(100); + var buffer = createEmptyBuffer(100); buffer.limit(5); - byte[] dst = new byte[10]; + var dst = new byte[10]; assertThrows(IndexOutOfBoundsException.class, () -> buffer.get(dst)); } @Test public void testGetByteArrayAcrossChunks() { - MultiBuffer buffer = createBuffer(35); - byte[] dst = new byte[10]; + var buffer = createBuffer(35); + var dst = new byte[10]; buffer.position(32); buffer.get(dst); - byte[] expectedBytes = new byte[]{ + var expectedBytes = new byte[]{ 0x2, 0x4, 0x43, 0x46, 0x6f, 0x6f, 0x43, (byte) 0xe4, (byte) 0xba, (byte) 0xba}; @@ -211,7 +211,7 @@ public void testGetByteArrayAcrossChunks() { @Test public void testGetDouble() { - MultiBuffer buffer = createBuffer(24); + var buffer = createBuffer(24); buffer.position(13); assertEquals(3.14159265359, buffer.getDouble()); assertEquals(21, buffer.position()); @@ -219,7 +219,7 @@ public void testGetDouble() { @Test public void testGetDoubleAcrossChunks() { - MultiBuffer buffer = createBuffer(16); + var buffer = createBuffer(16); buffer.position(13); assertEquals(3.14159265359, buffer.getDouble()); assertEquals(21, buffer.position()); @@ -227,7 +227,7 @@ public void testGetDoubleAcrossChunks() { @Test public void testGetFloat() { - MultiBuffer buffer = createBuffer(26); + var buffer = createBuffer(26); buffer.position(21); assertEquals(3.14f, buffer.getFloat()); assertEquals(25, buffer.position()); @@ -235,7 +235,7 @@ public void testGetFloat() { @Test public void testGetFloatAcrossChunks() { - MultiBuffer buffer = createBuffer(22); + var buffer = createBuffer(22); buffer.position(21); assertEquals(3.14f, buffer.getFloat()); assertEquals(25, buffer.position()); @@ -243,11 +243,11 @@ public void testGetFloatAcrossChunks() { @Test public void testDuplicate() { - MultiBuffer original = createEmptyBuffer(1000); + var original = createEmptyBuffer(1000); original.position(100); original.limit(800); - MultiBuffer duplicate = (MultiBuffer) original.duplicate(); + var duplicate = (MultiBuffer) original.duplicate(); assertEquals(original.capacity(), duplicate.capacity()); assertEquals(original.position(), duplicate.position()); @@ -261,18 +261,18 @@ public void testDuplicate() { @Test public void testWrapValidChunks() { - ByteBuffer[] chunks = new ByteBuffer[] { + var chunks = new ByteBuffer[] { ByteBuffer.allocate(8), ByteBuffer.allocate(3) }; - MultiBuffer buffer = new MultiBuffer(chunks, 8); + var buffer = new MultiBuffer(chunks, 8); assertEquals(11, buffer.capacity()); } @Test public void testWrapInvalidChunkSize() { - ByteBuffer[] chunks = new ByteBuffer[] { + var chunks = new ByteBuffer[] { ByteBuffer.allocate(3), ByteBuffer.allocate(8) }; @@ -283,8 +283,8 @@ public void testWrapInvalidChunkSize() { @Test public void testReadFromFileChannel(@TempDir Path tempDir) throws IOException { // Create test file - Path testFile = tempDir.resolve("test.dat"); - byte[] testData = new byte[]{ + var testFile = tempDir.resolve("test.dat"); + var testData = new byte[]{ (byte) 0xa2, 0x1, (byte) 0xf4, (byte) 0xc2, 0x2a, 0x78, 0x2, 0x1, 0x1, (byte) 0xf4, @@ -294,10 +294,10 @@ public void testReadFromFileChannel(@TempDir Path tempDir) throws IOException { Files.write(testFile, testData); try (FileChannel channel = FileChannel.open(testFile, StandardOpenOption.READ)) { - ByteBuffer buffer = ByteBuffer.allocate(testData.length); - long bytesRead = channel.read(buffer); + var buffer = ByteBuffer.allocate(testData.length); + var bytesRead = channel.read(buffer); buffer.flip(); - MultiBuffer multiBuffer = new MultiBuffer(new ByteBuffer[]{buffer}, testData.length); + var multiBuffer = new MultiBuffer(new ByteBuffer[]{buffer}, testData.length); assertEquals(21, bytesRead); assertEquals(0, multiBuffer.position()); } @@ -306,8 +306,8 @@ public void testReadFromFileChannel(@TempDir Path tempDir) throws IOException { @Test public void testMapFromChannel(@TempDir Path tempDir) throws IOException { // Create test file - Path testFile = tempDir.resolve("test.dat"); - byte[] testData = new byte[]{ + var testFile = tempDir.resolve("test.dat"); + var testData = new byte[]{ (byte) 0xa2, 0x1, (byte) 0xf4, (byte) 0xc2, 0x2a, 0x78, 0x2, 0x1, 0x1, (byte) 0xf4, @@ -317,14 +317,14 @@ public void testMapFromChannel(@TempDir Path tempDir) throws IOException { Files.write(testFile, testData); try (FileChannel channel = FileChannel.open(testFile, StandardOpenOption.READ)) { - MultiBuffer buffer = MultiBuffer.mapFromChannel(channel); + var buffer = MultiBuffer.mapFromChannel(channel); assertEquals(21, buffer.capacity()); } } @Test public void testMapFromEmptyChannel(@TempDir Path tempDir) throws IOException { - Path emptyFile = tempDir.resolve("empty.dat"); + var emptyFile = tempDir.resolve("empty.dat"); Files.createFile(emptyFile); try (FileChannel channel = FileChannel.open(emptyFile, StandardOpenOption.READ)) { @@ -334,17 +334,17 @@ public void testMapFromEmptyChannel(@TempDir Path tempDir) throws IOException { @Test public void testDecodeString() throws CharacterCodingException { - MultiBuffer buffer = createBuffer(22); + var buffer = createBuffer(22); buffer.position(26); buffer.limit(29); - String result = buffer.decode(StandardCharsets.UTF_8.newDecoder()); + var result = buffer.decode(StandardCharsets.UTF_8.newDecoder()); assertEquals("123", result); assertEquals(29, buffer.position()); } @Test public void testDecodeStringTooLarge() { - MultiBuffer buffer = createBuffer(65); + var buffer = createBuffer(65); buffer.position(62); buffer.limit(89); assertThrows(IllegalStateException.class, () -> @@ -353,10 +353,10 @@ public void testDecodeStringTooLarge() { @Test public void testDecodeAcrossChunks() throws CharacterCodingException { - MultiBuffer buffer = createBuffer(65); + var buffer = createBuffer(65); buffer.position(62); buffer.limit(89); - String result = buffer.decode(StandardCharsets.UTF_8.newDecoder()); + var result = buffer.decode(StandardCharsets.UTF_8.newDecoder()); assertEquals("123456789012345678901234567", result); assertEquals(89, buffer.position()); } diff --git a/src/test/java/com/maxmind/db/MultiThreadedTest.java b/src/test/java/com/maxmind/db/MultiThreadedTest.java index 22852af2..43f8f2ba 100644 --- a/src/test/java/com/maxmind/db/MultiThreadedTest.java +++ b/src/test/java/com/maxmind/db/MultiThreadedTest.java @@ -19,7 +19,7 @@ public class MultiThreadedTest { public void multipleMmapOpens() throws InterruptedException, ExecutionException { Callable> task = () -> { - try (Reader reader = new Reader(ReaderTest.getFile("MaxMind-DB-test-decoder.mmdb"))) { + try (var reader = new Reader(ReaderTest.getFile("MaxMind-DB-test-decoder.mmdb"))) { return reader.get(InetAddress.getByName("::1.1.1.0"), Map.class); } }; @@ -29,7 +29,7 @@ public void multipleMmapOpens() throws InterruptedException, @Test public void streamThreadTest() throws IOException, InterruptedException, ExecutionException { - try (Reader reader = new Reader(ReaderTest.getStream("MaxMind-DB-test-decoder.mmdb"), 2048)) { + try (var reader = new Reader(ReaderTest.getStream("MaxMind-DB-test-decoder.mmdb"), 2048)) { MultiThreadedTest.threadTest(reader); } } @@ -37,7 +37,7 @@ public void streamThreadTest() throws IOException, InterruptedException, @Test public void mmapThreadTest() throws IOException, InterruptedException, ExecutionException { - try (Reader reader = new Reader(ReaderTest.getFile("MaxMind-DB-test-decoder.mmdb"))) { + try (var reader = new Reader(ReaderTest.getFile("MaxMind-DB-test-decoder.mmdb"))) { MultiThreadedTest.threadTest(reader); } } @@ -52,12 +52,12 @@ private static void runThreads(Callable> task) throws InterruptedException, ExecutionException { int threadCount = 256; var tasks = Collections.nCopies(threadCount, task); - ExecutorService executorService = Executors + var executorService = Executors .newFixedThreadPool(threadCount); var futures = executorService.invokeAll(tasks); for (Future> future : futures) { - Map record = future.get(); + var record = future.get(); assertEquals(268435456, (long) record.get("uint32")); assertEquals("unicode! ☯ - ♫", record.get("utf8_string")); } diff --git a/src/test/java/com/maxmind/db/NetworkTest.java b/src/test/java/com/maxmind/db/NetworkTest.java index ac841e2e..ffff1600 100644 --- a/src/test/java/com/maxmind/db/NetworkTest.java +++ b/src/test/java/com/maxmind/db/NetworkTest.java @@ -9,7 +9,7 @@ public class NetworkTest { @Test public void testIPv6() throws UnknownHostException { - Network network = new Network( + var network = new Network( InetAddress.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 28 ); @@ -21,7 +21,7 @@ public void testIPv6() throws UnknownHostException { @Test public void TestIPv4() throws UnknownHostException { - Network network = new Network( + var network = new Network( InetAddress.getByName("192.168.213.111"), 31 ); diff --git a/src/test/java/com/maxmind/db/PointerTest.java b/src/test/java/com/maxmind/db/PointerTest.java index 1aa755f6..1c48d0de 100644 --- a/src/test/java/com/maxmind/db/PointerTest.java +++ b/src/test/java/com/maxmind/db/PointerTest.java @@ -13,11 +13,11 @@ public class PointerTest { @SuppressWarnings("static-method") @Test public void testWithPointers() throws IOException { - File file = ReaderTest.getFile("maps-with-pointers.raw"); - BufferHolder ptf = new BufferHolder(file, FileMode.MEMORY); - Decoder decoder = new Decoder(NoCache.getInstance(), ptf.get(), 0); + var file = ReaderTest.getFile("maps-with-pointers.raw"); + var ptf = new BufferHolder(file, FileMode.MEMORY); + var decoder = new Decoder(NoCache.getInstance(), ptf.get(), 0); - Map map = new HashMap<>(); + var map = new HashMap(); map.put("long_key", "long_value1"); assertEquals(map, decoder.decode(0, Map.class)); diff --git a/src/test/java/com/maxmind/db/ReaderTest.java b/src/test/java/com/maxmind/db/ReaderTest.java index 7daa7430..ffd346b3 100644 --- a/src/test/java/com/maxmind/db/ReaderTest.java +++ b/src/test/java/com/maxmind/db/ReaderTest.java @@ -51,7 +51,7 @@ public void teardownReader() throws IOException { } static IntStream chunkSizes() { - int[] sizes = new int[] { + var sizes = new int[] { 512, 2048, // The default chunk size of the MultiBuffer is close to max int, that causes @@ -69,8 +69,8 @@ static IntStream chunkSizes() { public void test(int chunkSize) throws IOException { for (long recordSize : new long[] {24, 28, 32}) { for (int ipVersion : new int[] {4, 6}) { - File file = getFile("MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); - try (Reader reader = new Reader(file, chunkSize)) { + var file = getFile("MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); + try (var reader = new Reader(file, chunkSize)) { this.testMetadata(reader, ipVersion, recordSize); if (ipVersion == 4) { this.testIpV4(reader, file); @@ -102,16 +102,16 @@ static class GetRecordTest { public void testNetworks(int chunkSize) throws IOException, InvalidDatabaseException, InvalidNetworkException { for (long recordSize : new long[] {24, 28, 32}) { for (int ipVersion : new int[] {4, 6}) { - File file = getFile("MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); + var file = getFile("MaxMind-DB-test-ipv" + ipVersion + "-" + recordSize + ".mmdb"); - Reader reader = new Reader(file, chunkSize); + var reader = new Reader(file, chunkSize); var networks = reader.networks(false, Map.class); while(networks.hasNext()) { var iteration = networks.next(); var data = (Map) iteration.data(); - InetAddress actualIPInData = InetAddress.getByName((String) data.get("ip")); + var actualIPInData = InetAddress.getByName((String) data.get("ip")); assertEquals( iteration.network().networkAddress(), @@ -128,12 +128,12 @@ public void testNetworks(int chunkSize) throws IOException, InvalidDatabaseExcep @ParameterizedTest @MethodSource("chunkSizes") public void testNetworksWithInvalidSearchTree(int chunkSize) throws IOException, InvalidNetworkException{ - File file = getFile("MaxMind-DB-test-broken-search-tree-24.mmdb"); - Reader reader = new Reader(file, chunkSize); + var file = getFile("MaxMind-DB-test-broken-search-tree-24.mmdb"); + var reader = new Reader(file, chunkSize); var networks = reader.networks(false, Map.class); - Exception exception = assertThrows(RuntimeException.class, () -> { + var exception = assertThrows(RuntimeException.class, () -> { while(networks.hasNext()){ assertNotNull(networks.next()); } @@ -354,16 +354,16 @@ public networkTest(String network, int prefix,String database, String[] expecte public void testNetworksWithin(int chunkSize) throws IOException, InvalidNetworkException{ for(networkTest test : tests){ for(int recordSize : new int[]{24, 28, 32}){ - File file = getFile("MaxMind-DB-test-"+test.database+"-"+recordSize+".mmdb"); - Reader reader = new Reader(file, chunkSize); + var file = getFile("MaxMind-DB-test-"+test.database+"-"+recordSize+".mmdb"); + var reader = new Reader(file, chunkSize); - InetAddress address = InetAddress.getByName(test.network); - Network network = new Network(address, test.prefix); + var address = InetAddress.getByName(test.network); + var network = new Network(address, test.prefix); boolean includeAliasedNetworks = !test.skipAliasedNetworks; var networks = reader.networksWithin(network, includeAliasedNetworks, Map.class); - List innerIPs = new ArrayList<>(); + var innerIPs = new ArrayList(); while(networks.hasNext()){ var iteration = networks.next(); innerIPs.add(iteration.network().toString()); @@ -393,15 +393,15 @@ public void testNetworksWithin(int chunkSize) throws IOException, InvalidNetwork @MethodSource("chunkSizes") public void testGeoIPNetworksWithin(int chunkSize) throws IOException, InvalidNetworkException{ for (networkTest test : geoipTests){ - File file = getFile(test.database); - Reader reader = new Reader(file, chunkSize); + var file = getFile(test.database); + var reader = new Reader(file, chunkSize); - InetAddress address = InetAddress.getByName(test.network); - Network network = new Network(address, test.prefix); + var address = InetAddress.getByName(test.network); + var network = new Network(address, test.prefix); var networks = reader.networksWithin(network, test.skipAliasedNetworks, Map.class); - ArrayList innerIPs = new ArrayList<>(); + var innerIPs = new ArrayList(); while(networks.hasNext()){ var iteration = networks.next(); innerIPs.add(iteration.network().toString()); @@ -416,7 +416,7 @@ public void testGeoIPNetworksWithin(int chunkSize) throws IOException, InvalidNe @ParameterizedTest @MethodSource("chunkSizes") public void testGetRecord(int chunkSize) throws IOException { - GetRecordTest[] mapTests = { + var mapTests = new GetRecordTest[] { new GetRecordTest("1.1.1.1", "MaxMind-DB-test-ipv6-32.mmdb", "1.0.0.0/8", false), new GetRecordTest("::1:ffff:ffff", "MaxMind-DB-test-ipv6-24.mmdb", "0:0:0:0:0:1:ffff:ffff/128", true), @@ -431,8 +431,8 @@ public void testGetRecord(int chunkSize) throws IOException { "0:0:0:0:0:0:101:100/120", true), }; for (GetRecordTest test : mapTests) { - try (Reader reader = new Reader(test.db, chunkSize)) { - DatabaseRecord record = reader.getRecord(test.ip, Map.class); + try (var reader = new Reader(test.db, chunkSize)) { + var record = reader.getRecord(test.ip, Map.class); assertEquals(test.network, record.network().toString()); @@ -444,7 +444,7 @@ public void testGetRecord(int chunkSize) throws IOException { } } - GetRecordTest[] stringTests = { + var stringTests = new GetRecordTest[] { new GetRecordTest("200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", "0.0.0.0/0", true), new GetRecordTest("::200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", @@ -455,7 +455,7 @@ public void testGetRecord(int chunkSize) throws IOException { "8000:0:0:0:0:0:0:0/1", false) }; for (GetRecordTest test : stringTests) { - try (Reader reader = new Reader(test.db, chunkSize)) { + try (var reader = new Reader(test.db, chunkSize)) { var record = reader.getRecord(test.ip, String.class); assertEquals(test.network, record.network().toString()); @@ -472,7 +472,7 @@ var record = reader.getRecord(test.ip, String.class); @ParameterizedTest @MethodSource("chunkSizes") public void testMetadataPointers(int chunkSize) throws IOException { - Reader reader = new Reader(getFile("MaxMind-DB-test-metadata-pointers.mmdb"), chunkSize); + var reader = new Reader(getFile("MaxMind-DB-test-metadata-pointers.mmdb"), chunkSize); assertEquals("Lots of pointers in metadata", reader.getMetadata().databaseType()); } @@ -573,7 +573,7 @@ var record = reader.get(InetAddress.getByName("::1.1.1.0"), Map.class); private void testDecodingTypesIntoModelObject(Reader reader, boolean booleanValue) throws IOException { - TestModel model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModel.class); + var model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModel.class); if (booleanValue) { assertTrue(model.booleanField); @@ -585,12 +585,12 @@ private void testDecodingTypesIntoModelObject(Reader reader, boolean booleanValu assertEquals("unicode! ☯ - ♫", model.utf8StringField); - List expectedArray = new ArrayList<>(List.of( + var expectedArray = new ArrayList<>(List.of( (long) 1, (long) 2, (long) 3 )); assertEquals(expectedArray, model.arrayField); - List expectedArray2 = new ArrayList<>(List.of( + var expectedArray2 = new ArrayList<>(List.of( (long) 7, (long) 8, (long) 9 )); assertEquals(expectedArray2, model.mapField.mapXField.arrayXField); @@ -693,7 +693,7 @@ public MapXModel( private void testDecodingTypesIntoModelObjectBoxed(Reader reader, boolean booleanValue) throws IOException { - TestModelBoxed model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelBoxed.class); + var model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelBoxed.class); if (booleanValue) { assertTrue(model.booleanField); @@ -705,12 +705,12 @@ private void testDecodingTypesIntoModelObjectBoxed(Reader reader, boolean boolea assertEquals("unicode! ☯ - ♫", model.utf8StringField); - List expectedArray = new ArrayList<>(List.of( + var expectedArray = new ArrayList<>(List.of( (long) 1, (long) 2, (long) 3 )); assertEquals(expectedArray, model.arrayField); - List expectedArray2 = new ArrayList<>(List.of( + var expectedArray2 = new ArrayList<>(List.of( (long) 7, (long) 8, (long) 9 )); assertEquals(expectedArray2, model.mapField.mapXField.arrayXField); @@ -813,7 +813,7 @@ public MapXModelBoxed( private void testDecodingTypesIntoModelWithList(Reader reader) throws IOException { - TestModelList model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelList.class); + var model = reader.get(InetAddress.getByName("::1.1.1.0"), TestModelList.class); assertEquals(List.of((long) 1, (long) 2, (long) 3), model.arrayField); } @@ -870,7 +870,7 @@ var record = reader.get(InetAddress.getByName("::"), Map.class); } private void testZerosModelObject(Reader reader) throws IOException { - TestModel model = reader.get(InetAddress.getByName("::"), TestModel.class); + var model = reader.get(InetAddress.getByName("::"), TestModel.class); assertFalse(model.booleanField); @@ -878,7 +878,7 @@ private void testZerosModelObject(Reader reader) throws IOException { assertEquals("", model.utf8StringField); - List expectedArray = new ArrayList<>(); + var expectedArray = new ArrayList(); assertEquals(expectedArray, model.arrayField); assertNull(model.mapField.mapXField); @@ -897,7 +897,7 @@ private void testZerosModelObject(Reader reader) throws IOException { public void testDecodeSubdivisions(int chunkSize) throws IOException { this.testReader = new Reader(getFile("GeoIP2-City-Test.mmdb"), chunkSize); - TestModelSubdivisions model = this.testReader.get( + var model = this.testReader.get( InetAddress.getByName("2.125.160.216"), TestModelSubdivisions.class ); @@ -935,7 +935,7 @@ public TestModelSubdivision( @MethodSource("chunkSizes") public void testDecodeWrongTypeWithConstructorException(int chunkSize) throws IOException { this.testReader = new Reader(getFile("GeoIP2-City-Test.mmdb"), chunkSize); - DeserializationException ex = assertThrows(DeserializationException.class, + var ex = assertThrows(DeserializationException.class, () -> this.testReader.get(InetAddress.getByName("2.125.160.216"), TestModelSubdivisionsWithUnknownException.class)); @@ -959,7 +959,7 @@ public TestModelSubdivisionsWithUnknownException( @MethodSource("chunkSizes") public void testDecodeWrongTypeWithWrongArguments(int chunkSize) throws IOException { this.testReader = new Reader(getFile("GeoIP2-City-Test.mmdb"), chunkSize); - DeserializationException ex = assertThrows(DeserializationException.class, + var ex = assertThrows(DeserializationException.class, () -> this.testReader.get(InetAddress.getByName("2.125.160.216"), TestWrongModelSubdivisions.class)); assertThat(ex.getMessage(), containsString("Error getting record for IP")); @@ -969,7 +969,7 @@ public void testDecodeWrongTypeWithWrongArguments(int chunkSize) throws IOExcept @MethodSource("chunkSizes") public void testDecodeWithDataTypeMismatchInModel(int chunkSize) throws IOException { this.testReader = new Reader(getFile("GeoIP2-City-Test.mmdb"), chunkSize); - DeserializationException ex = assertThrows(DeserializationException.class, + var ex = assertThrows(DeserializationException.class, () -> this.testReader.get(InetAddress.getByName("2.125.160.216"), TestDataTypeMismatchInModel.class)); assertThat(ex.getMessage(), containsString("Error getting record for IP")); @@ -994,7 +994,7 @@ public TestConstructorMismatchModel( public void testDecodeWithDataTypeMismatchInModelAndNullValue(int chunkSize) throws IOException { this.testReader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize); - DeserializationException ex = assertThrows(DeserializationException.class, + var ex = assertThrows(DeserializationException.class, () -> this.testReader.get( InetAddress.getByName("::1.1.1.0"), TestConstructorMismatchModel.class)); @@ -1053,7 +1053,7 @@ public void testDecodeConcurrentHashMap(int chunkSize) throws IOException { var eng = (Map) subdivisions.get(0); - String isoCode = (String) eng.get("iso_code"); + var isoCode = (String) eng.get("iso_code"); assertEquals("ENG", isoCode); } @@ -1062,7 +1062,7 @@ public void testDecodeConcurrentHashMap(int chunkSize) throws IOException { public void testDecodeVector(int chunkSize) throws IOException { this.testReader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize); - TestModelVector model = this.testReader.get( + var model = this.testReader.get( InetAddress.getByName("::1.1.1.0"), TestModelVector.class ); @@ -1089,7 +1089,7 @@ public TestModelVector( @ParameterizedTest @MethodSource("chunkSizes") public void testCacheWithDifferentModels(int chunkSize) throws IOException { - NodeCache cache = new CHMCache(); + var cache = new CHMCache(); this.testReader = new Reader( getFile("MaxMind-DB-test-decoder.mmdb"), @@ -1097,13 +1097,13 @@ public void testCacheWithDifferentModels(int chunkSize) throws IOException { chunkSize ); - TestModelA modelA = this.testReader.get( + var modelA = this.testReader.get( InetAddress.getByName("::1.1.1.0"), TestModelA.class ); assertEquals("unicode! ☯ - ♫", modelA.utf8StringFieldA); - TestModelB modelB = this.testReader.get( + var modelB = this.testReader.get( InetAddress.getByName("::1.1.1.0"), TestModelB.class ); @@ -1134,32 +1134,32 @@ public TestModelB( @Test public void testCacheKey() { - Class cls = TestModelCacheKey.class; + var cls = TestModelCacheKey.class; - CacheKey a = new CacheKey<>(1, cls, getType(cls, 0)); - CacheKey b = new CacheKey<>(1, cls, getType(cls, 0)); + var a = new CacheKey<>(1, cls, getType(cls, 0)); + var b = new CacheKey<>(1, cls, getType(cls, 0)); assertEquals(a, b); - CacheKey c = new CacheKey<>(2, cls, getType(cls, 0)); + var c = new CacheKey<>(2, cls, getType(cls, 0)); assertNotEquals(a, c); - CacheKey d = new CacheKey<>(1, String.class, getType(cls, 0)); + var d = new CacheKey<>(1, String.class, getType(cls, 0)); assertNotEquals(a, d); - CacheKey e = new CacheKey<>(1, cls, getType(cls, 1)); + var e = new CacheKey<>(1, cls, getType(cls, 1)); assertNotEquals(a, e); } private java.lang.reflect.Type getType(Class cls, int i) { - Constructor[] constructors = cls.getConstructors(); + var constructors = cls.getConstructors(); Constructor constructor = null; - for (Constructor constructor2 : constructors) { + for (var constructor2 : constructors) { constructor = (Constructor) constructor2; break; } assertNotNull(constructor); - java.lang.reflect.Type[] types = constructor.getGenericParameterTypes(); + var types = constructor.getGenericParameterTypes(); return types[i]; } @@ -1188,7 +1188,7 @@ public void testBrokenDatabaseStream(int chunkSize) throws IOException { } private void testBrokenDatabase(Reader reader) { - InvalidDatabaseException ex = assertThrows( + var ex = assertThrows( InvalidDatabaseException.class, () -> reader.get(InetAddress.getByName("2001:220::"), Map.class)); assertThat(ex.getMessage(), @@ -1210,7 +1210,7 @@ public void testBrokenSearchTreePointerStream(int chunkSize) throws IOException } private void testBrokenSearchTreePointer(Reader reader) { - InvalidDatabaseException ex = assertThrows(InvalidDatabaseException.class, + var ex = assertThrows(InvalidDatabaseException.class, () -> reader.get(InetAddress.getByName("1.1.1.32"), Map.class)); assertThat(ex.getMessage(), containsString("The MaxMind DB file's search tree is corrupt")); } @@ -1230,7 +1230,7 @@ public void testBrokenDataPointerStream(int chunkSize) throws IOException { } private void testBrokenDataPointer(Reader reader) { - InvalidDatabaseException ex = assertThrows(InvalidDatabaseException.class, + var ex = assertThrows(InvalidDatabaseException.class, () -> reader.get(InetAddress.getByName("1.1.1.16"), Map.class)); assertThat(ex.getMessage(), containsString("The MaxMind DB file's data section contains bad data")); @@ -1239,10 +1239,10 @@ private void testBrokenDataPointer(Reader reader) { @ParameterizedTest @MethodSource("chunkSizes") public void testClosedReaderThrowsException(int chunkSize) throws IOException { - Reader reader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize); + var reader = new Reader(getFile("MaxMind-DB-test-decoder.mmdb"), chunkSize); reader.close(); - ClosedDatabaseException ex = assertThrows(ClosedDatabaseException.class, + var ex = assertThrows(ClosedDatabaseException.class, () -> reader.get(InetAddress.getByName("1.1.1.16"), Map.class)); assertEquals("The MaxMind DB has been closed.", ex.getMessage()); } @@ -1252,7 +1252,7 @@ public void testClosedReaderThrowsException(int chunkSize) throws IOException { public void voidTestMapKeyIsString(int chunkSize) throws IOException { this.testReader = new Reader(getFile("GeoIP2-City-Test.mmdb"), chunkSize); - DeserializationException ex = assertThrows( + var ex = assertThrows( DeserializationException.class, () -> this.testReader.get( InetAddress.getByName("2.125.160.216"), @@ -1277,25 +1277,25 @@ public TestModelInvalidMap( private void testMetadata(Reader reader, int ipVersion, long recordSize) { - Metadata metadata = reader.getMetadata(); + var metadata = reader.getMetadata(); assertEquals(2, metadata.binaryFormatMajorVersion(), "major version"); assertEquals(0, metadata.binaryFormatMinorVersion()); assertEquals(ipVersion, metadata.ipVersion()); assertEquals("Test", metadata.databaseType()); - List languages = new ArrayList<>(List.of("en", "zh")); + var languages = new ArrayList<>(List.of("en", "zh")); assertEquals(languages, metadata.languages()); - Map description = new HashMap<>(); + var description = new HashMap(); description.put("en", "Test Database"); description.put("zh", "Test Database Chinese"); assertEquals(description, metadata.description()); assertEquals(recordSize, metadata.recordSize()); - Calendar cal = Calendar.getInstance(); + var cal = Calendar.getInstance(); cal.set(2014, Calendar.JANUARY, 1); assertTrue(metadata.buildDate().compareTo(cal.getTime()) > 0); @@ -1304,8 +1304,8 @@ private void testMetadata(Reader reader, int ipVersion, long recordSize) { private void testIpV4(Reader reader, File file) throws IOException { for (int i = 0; i <= 5; i++) { - String address = "1.1.1." + (int) Math.pow(2, i); - Map data = new HashMap<>(); + var address = "1.1.1." + (int) Math.pow(2, i); + var data = new HashMap(); data.put("ip", address); assertEquals( @@ -1315,7 +1315,7 @@ private void testIpV4(Reader reader, File file) throws IOException { ); } - Map pairs = new HashMap<>(); + var pairs = new HashMap(); pairs.put("1.1.1.3", "1.1.1.2"); pairs.put("1.1.1.5", "1.1.1.4"); pairs.put("1.1.1.7", "1.1.1.4"); @@ -1324,7 +1324,7 @@ private void testIpV4(Reader reader, File file) throws IOException { pairs.put("1.1.1.17", "1.1.1.16"); pairs.put("1.1.1.31", "1.1.1.16"); for (String address : pairs.keySet()) { - Map data = new HashMap<>(); + var data = new HashMap(); data.put("ip", pairs.get(address)); assertEquals( @@ -1341,11 +1341,11 @@ private void testIpV4(Reader reader, File file) throws IOException { // XXX - logic could be combined with above private void testIpV6(Reader reader, File file) throws IOException { - String[] subnets = new String[] {"::1:ffff:ffff", "::2:0:0", + var subnets = new String[] {"::1:ffff:ffff", "::2:0:0", "::2:0:40", "::2:0:50", "::2:0:58"}; for (String address : subnets) { - Map data = new HashMap<>(); + var data = new HashMap(); data.put("ip", address); assertEquals( @@ -1355,7 +1355,7 @@ private void testIpV6(Reader reader, File file) throws IOException { ); } - Map pairs = new HashMap<>(); + var pairs = new HashMap(); pairs.put("::2:0:1", "::2:0:0"); pairs.put("::2:0:33", "::2:0:0"); pairs.put("::2:0:39", "::2:0:0"); @@ -1366,7 +1366,7 @@ private void testIpV6(Reader reader, File file) throws IOException { pairs.put("::2:0:59", "::2:0:58"); for (String address : pairs.keySet()) { - Map data = new HashMap<>(); + var data = new HashMap(); data.put("ip", pairs.get(address)); assertEquals( From 8e7f408aae7978f56c67ff042a445ce314d6896f Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 13:01:21 -0700 Subject: [PATCH 09/15] Delete unused code --- .../java/com/maxmind/db/BufferHolder.java | 12 ---------- .../maxmind/db/ClosedDatabaseException.java | 3 --- src/main/java/com/maxmind/db/Decoder.java | 1 - src/main/java/com/maxmind/db/MultiBuffer.java | 1 - src/main/java/com/maxmind/db/Networks.java | 24 ------------------- src/main/java/com/maxmind/db/Reader.java | 1 - 6 files changed, 42 deletions(-) diff --git a/src/main/java/com/maxmind/db/BufferHolder.java b/src/main/java/com/maxmind/db/BufferHolder.java index cd4a824a..cd60a915 100644 --- a/src/main/java/com/maxmind/db/BufferHolder.java +++ b/src/main/java/com/maxmind/db/BufferHolder.java @@ -8,7 +8,6 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; -import java.util.List; final class BufferHolder { // DO NOT PASS OUTSIDE THIS CLASS. Doing so will remove thread safety. @@ -75,17 +74,6 @@ final class BufferHolder { } } - /** - * Construct a ThreadBuffer from the provided URL. - * - * @param stream the source of my bytes. - * @throws IOException if unable to read from your source. - * @throws NullPointerException if you provide a NULL InputStream - */ - BufferHolder(InputStream stream) throws IOException { - this(stream, MultiBuffer.DEFAULT_CHUNK_SIZE); - } - BufferHolder(InputStream stream, int chunkSize) throws IOException { if (null == stream) { throw new NullPointerException("Unable to use a NULL InputStream"); diff --git a/src/main/java/com/maxmind/db/ClosedDatabaseException.java b/src/main/java/com/maxmind/db/ClosedDatabaseException.java index 6a02c28a..8d3169fa 100644 --- a/src/main/java/com/maxmind/db/ClosedDatabaseException.java +++ b/src/main/java/com/maxmind/db/ClosedDatabaseException.java @@ -6,9 +6,6 @@ * Signals that the underlying database has been closed. */ public class ClosedDatabaseException extends IOException { - - private static final long serialVersionUID = 1L; - ClosedDatabaseException() { super("The MaxMind DB has been closed."); } diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index cf2c960a..49331636 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -6,7 +6,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.math.BigInteger; -import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; diff --git a/src/main/java/com/maxmind/db/MultiBuffer.java b/src/main/java/com/maxmind/db/MultiBuffer.java index d3d5f81c..16a02014 100644 --- a/src/main/java/com/maxmind/db/MultiBuffer.java +++ b/src/main/java/com/maxmind/db/MultiBuffer.java @@ -6,7 +6,6 @@ import java.nio.channels.FileChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; -import java.nio.charset.CoderResult; /** * A {@link Buffer} implementation backed by multiple {@link ByteBuffer}s, diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java index 604b9432..ca866859 100644 --- a/src/main/java/com/maxmind/db/Networks.java +++ b/src/main/java/com/maxmind/db/Networks.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; -import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Iterator; import java.util.Stack; @@ -22,19 +21,6 @@ public final class Networks implements Iterator> { private final Buffer buffer; /* Stores the buffer for Next() calls */ private final Class typeParameterClass; - /** - * Constructs a Networks instance. - * - * @param reader The reader object. - * @param includeAliasedNetworks The boolean to include aliased networks. - * @param typeParameterClass The type of data returned by the iterator. - * @throws ClosedDatabaseException Exception for a closed database. - */ - Networks(Reader reader, boolean includeAliasedNetworks, Class typeParameterClass) - throws ClosedDatabaseException { - this(reader, includeAliasedNetworks, new NetworkNode[0], typeParameterClass); - } - /** * Constructs a Networks instance. * @@ -60,16 +46,6 @@ public final class Networks implements Iterator> { } } - /** - * Constructs a Networks instance with includeAliasedNetworks set to false by default. - * - * @param reader The reader object. - * @param typeParameterClass The type of data returned by the iterator. - */ - Networks(Reader reader, Class typeParameterClass) throws ClosedDatabaseException { - this(reader, false, typeParameterClass); - } - /** * Returns the next DataRecord. * diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index 1debebb3..a1c6b1e8 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -7,7 +7,6 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; -import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; From 49f0b1f49a5b2ac15a357ec00405c0cd014553c7 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 13:01:40 -0700 Subject: [PATCH 10/15] Do not bother setting serialVersionUID --- src/main/java/com/maxmind/db/ConstructorNotFoundException.java | 2 -- src/main/java/com/maxmind/db/DeserializationException.java | 2 -- src/main/java/com/maxmind/db/InvalidDatabaseException.java | 3 --- src/main/java/com/maxmind/db/ParameterNotFoundException.java | 2 -- 4 files changed, 9 deletions(-) diff --git a/src/main/java/com/maxmind/db/ConstructorNotFoundException.java b/src/main/java/com/maxmind/db/ConstructorNotFoundException.java index 2f803a87..781afca1 100644 --- a/src/main/java/com/maxmind/db/ConstructorNotFoundException.java +++ b/src/main/java/com/maxmind/db/ConstructorNotFoundException.java @@ -5,8 +5,6 @@ * constructor in the class with the MaxMindDbConstructor annotation. */ public class ConstructorNotFoundException extends RuntimeException { - private static final long serialVersionUID = 1L; - ConstructorNotFoundException(String message) { super(message); } diff --git a/src/main/java/com/maxmind/db/DeserializationException.java b/src/main/java/com/maxmind/db/DeserializationException.java index c18b24ba..e7e3db5a 100644 --- a/src/main/java/com/maxmind/db/DeserializationException.java +++ b/src/main/java/com/maxmind/db/DeserializationException.java @@ -4,8 +4,6 @@ * Signals that the value could not be deserialized into the type. */ public class DeserializationException extends RuntimeException { - private static final long serialVersionUID = 1L; - DeserializationException() { super("Database value cannot be deserialized into the type."); } diff --git a/src/main/java/com/maxmind/db/InvalidDatabaseException.java b/src/main/java/com/maxmind/db/InvalidDatabaseException.java index 776ea4b0..0e1c94ea 100644 --- a/src/main/java/com/maxmind/db/InvalidDatabaseException.java +++ b/src/main/java/com/maxmind/db/InvalidDatabaseException.java @@ -8,9 +8,6 @@ * corrupt or otherwise not in a format supported by the reader. */ public class InvalidDatabaseException extends IOException { - - private static final long serialVersionUID = 6161763462364823003L; - /** * @param message A message describing the reason why the exception was thrown. */ diff --git a/src/main/java/com/maxmind/db/ParameterNotFoundException.java b/src/main/java/com/maxmind/db/ParameterNotFoundException.java index bf67f7c1..e0d21339 100644 --- a/src/main/java/com/maxmind/db/ParameterNotFoundException.java +++ b/src/main/java/com/maxmind/db/ParameterNotFoundException.java @@ -5,8 +5,6 @@ * parameters of the constructor class with the MaxMindDbParameter annotation. */ public class ParameterNotFoundException extends RuntimeException { - private static final long serialVersionUID = 1L; - ParameterNotFoundException(String message) { super(message); } From efd39e40777e4814303e671deeb13dad370c61f4 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 13:02:09 -0700 Subject: [PATCH 11/15] Remove unnecessary local variable --- src/main/java/com/maxmind/db/Decoder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index 49331636..ee6b9930 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -131,10 +131,9 @@ private DecodedValue decode(Class cls, java.lang.reflect.Type genericType DecodedValue decodePointer(long pointer, Class cls, java.lang.reflect.Type genericType) throws IOException { - var targetOffset = pointer; var position = buffer.position(); - var key = new CacheKey<>(targetOffset, cls, genericType); + var key = new CacheKey<>(pointer, cls, genericType); var o = cache.get(key, cacheLoader); buffer.position(position); From ecb2b7dee182e19b35bb4fecbb2355cb77e86887 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 9 Oct 2025 13:02:34 -0700 Subject: [PATCH 12/15] Make attribute final --- src/main/java/com/maxmind/db/Networks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/maxmind/db/Networks.java b/src/main/java/com/maxmind/db/Networks.java index ca866859..00ed79cf 100644 --- a/src/main/java/com/maxmind/db/Networks.java +++ b/src/main/java/com/maxmind/db/Networks.java @@ -145,7 +145,7 @@ public boolean hasNext() { static class NetworkNode { /** The IP address of the node. */ - public byte[] ip; + public final byte[] ip; /** The prefix of the node. */ public int prefix; /** The node number. */ From f4c9bd0337ae8f2d3490c84a61403ffc546b00c0 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 10 Oct 2025 09:58:58 -0700 Subject: [PATCH 13/15] Move nodeByteSize and searchTreeSize to Reader They were not public and are used in hot paths. --- src/main/java/com/maxmind/db/Metadata.java | 14 -------------- src/main/java/com/maxmind/db/Reader.java | 12 +++++++++--- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/maxmind/db/Metadata.java b/src/main/java/com/maxmind/db/Metadata.java index 463c1a67..04629ce1 100644 --- a/src/main/java/com/maxmind/db/Metadata.java +++ b/src/main/java/com/maxmind/db/Metadata.java @@ -51,18 +51,4 @@ public record Metadata( public Date buildDate() { return new Date(buildEpoch.longValue() * 1000); } - - /** - * @return the nodeByteSize - */ - int nodeByteSize() { - return recordSize / 4; - } - - /** - * @return the searchTreeSize - */ - long searchTreeSize() { - return nodeCount * nodeByteSize(); - } } diff --git a/src/main/java/com/maxmind/db/Reader.java b/src/main/java/com/maxmind/db/Reader.java index a1c6b1e8..3af489d3 100644 --- a/src/main/java/com/maxmind/db/Reader.java +++ b/src/main/java/com/maxmind/db/Reader.java @@ -23,6 +23,8 @@ public final class Reader implements Closeable { private final long ipV4Start; private final Metadata metadata; + private final int nodeByteSize; + private final long searchTreeSize; private final AtomicReference bufferHolderReference; private final NodeCache cache; private final ConcurrentHashMap, CachedConstructor> constructors; @@ -157,6 +159,10 @@ private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws I var metadataDecoder = new Decoder(this.cache, buffer, start); this.metadata = metadataDecoder.decode(start, Metadata.class); + // Calculate and cache these values as they are used in hot paths + this.nodeByteSize = this.metadata.recordSize() / 4; + this.searchTreeSize = this.metadata.nodeCount() * this.nodeByteSize; + this.ipV4Start = this.findIpV4StartNode(buffer); this.constructors = new ConcurrentHashMap<>(); @@ -382,7 +388,7 @@ long readNode(Buffer buffer, long nodeNumber, int index) throws InvalidDatabaseException { // index is the index of the record within the node, which // can either be 0 or 1. - var baseOffset = nodeNumber * this.metadata.nodeByteSize(); + var baseOffset = nodeNumber * this.nodeByteSize; var recordSize = this.metadata.recordSize(); return switch (recordSize) { @@ -413,7 +419,7 @@ T resolveDataPointer( Class cls ) throws IOException { long resolved = (pointer - this.metadata.nodeCount()) - + this.metadata.searchTreeSize(); + + this.searchTreeSize; if (resolved >= buffer.capacity()) { throw new InvalidDatabaseException( @@ -426,7 +432,7 @@ T resolveDataPointer( var decoder = new Decoder( this.cache, buffer, - this.metadata.searchTreeSize() + DATA_SECTION_SEPARATOR_SIZE, + this.searchTreeSize + DATA_SECTION_SEPARATOR_SIZE, this.constructors ); return decoder.decode(resolved, cls); From f07a1abe0361dd678c5a344cef6ffb8da93415b8 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 10 Oct 2025 09:59:50 -0700 Subject: [PATCH 14/15] Check size after read --- src/test/java/com/maxmind/db/MultiBufferTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/maxmind/db/MultiBufferTest.java b/src/test/java/com/maxmind/db/MultiBufferTest.java index ed27633e..43be354f 100644 --- a/src/test/java/com/maxmind/db/MultiBufferTest.java +++ b/src/test/java/com/maxmind/db/MultiBufferTest.java @@ -91,11 +91,21 @@ static MultiBuffer createBuffer(int chunkSize) { buffers[totalChunks - 1] = ByteBuffer.allocate(remainder); } - for (ByteBuffer buffer : buffers) { - channel.read(buffer); + var totalRead = 0L; + for (var buffer : buffers) { + var read = channel.read(buffer); + if (read == -1) { + break; + } + totalRead += read; buffer.flip(); } + if (totalRead != size) { + throw new IOException("Unable to read test data into memory. " + + "Expected " + size + " bytes but read " + totalRead + " bytes."); + } + return new MultiBuffer(buffers, chunkSize); } } catch (IOException e) { From f44f6ca055e4d2ecc0de336f64f959b48ee3399d Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 10 Oct 2025 10:07:21 -0700 Subject: [PATCH 15/15] Use modern Java time APIs --- CHANGELOG.md | 11 ++++++----- src/main/java/com/maxmind/db/Metadata.java | 8 ++++---- src/test/java/com/maxmind/db/ReaderTest.java | 11 +++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6720f178..89c3df84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,16 @@ CHANGELOG 2GB ByteBuffer limit. Files under 2GB continue to use a single ByteBuffer for optimal performance. Requested by nonetallt. GitHub #154. Fixed by Silvano Cerza. GitHub #289. +* `Metadata.getBuildDate()` has been replaced with `buildTime()`, which returns + `java.time.Instant` instead of `java.util.Date`. The instant represents the + database build time in UTC. * `DatabaseRecord`, `Metadata`, `Network`, and internal `DecodedValue` classes have been converted to records. The following API changes were made: * `DatabaseRecord.getData()` and `DatabaseRecord.getNetwork()` have been replaced with record accessor methods `data()` and `network()`. - * `Metadata.getBuildDate()` has been renamed to `buildDate()` to follow record - naming conventions. All other simple getter methods on `Metadata` (e.g., - `getBinaryFormatMajorVersion()`, `getDatabaseType()`, etc.) have been - replaced with their corresponding record accessor methods (e.g., - `binaryFormatMajorVersion()`, `databaseType()`, etc.). + * Simple getter methods on `Metadata` (e.g., `getBinaryFormatMajorVersion()`, + `getDatabaseType()`, etc.) have been replaced with their corresponding record + accessor methods (e.g., `binaryFormatMajorVersion()`, `databaseType()`, etc.). * `Network.getNetworkAddress()` and `Network.getPrefixLength()` have been replaced with record accessor methods `networkAddress()` and `prefixLength()`. diff --git a/src/main/java/com/maxmind/db/Metadata.java b/src/main/java/com/maxmind/db/Metadata.java index 04629ce1..ddeb8b01 100644 --- a/src/main/java/com/maxmind/db/Metadata.java +++ b/src/main/java/com/maxmind/db/Metadata.java @@ -1,7 +1,7 @@ package com.maxmind.db; import java.math.BigInteger; -import java.util.Date; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -46,9 +46,9 @@ public record Metadata( public Metadata {} /** - * @return the date of the database build. + * @return the instant of the database build in UTC. */ - public Date buildDate() { - return new Date(buildEpoch.longValue() * 1000); + public Instant buildTime() { + return Instant.ofEpochSecond(buildEpoch.longValue()); } } diff --git a/src/test/java/com/maxmind/db/ReaderTest.java b/src/test/java/com/maxmind/db/ReaderTest.java index ffd346b3..5c8fcb90 100644 --- a/src/test/java/com/maxmind/db/ReaderTest.java +++ b/src/test/java/com/maxmind/db/ReaderTest.java @@ -19,9 +19,11 @@ import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -1295,10 +1297,11 @@ private void testMetadata(Reader reader, int ipVersion, long recordSize) { assertEquals(description, metadata.description()); assertEquals(recordSize, metadata.recordSize()); - var cal = Calendar.getInstance(); - cal.set(2014, Calendar.JANUARY, 1); + var january2014 = LocalDate.of(2014, 1, 1) + .atStartOfDay(ZoneOffset.UTC) + .toInstant(); - assertTrue(metadata.buildDate().compareTo(cal.getTime()) > 0); + assertTrue(metadata.buildTime().isAfter(january2014)); } private void testIpV4(Reader reader, File file) throws IOException {