map = decoders.get();
+ CharsetDecoder d = map.get(charset);
+ if (d != null) {
+ d.reset();
+ d.onMalformedInput(CodingErrorAction.REPLACE);
+ d.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ return d;
+ }
+
+ d = charset.newDecoder();
+ d.onMalformedInput(CodingErrorAction.REPLACE);
+ d.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ map.put(charset, d);
+ return d;
+ }
+}
diff --git a/src/main/java/org/iq80/leveldb/util/Snappy.java b/src/main/java/org/iq80/leveldb/util/Snappy.java
new file mode 100644
index 0000000..acc05fa
--- /dev/null
+++ b/src/main/java/org/iq80/leveldb/util/Snappy.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2011 the original author or authors.
+ * See the notice.md file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.iq80.leveldb.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ *
+ * A Snappy abstraction which attempts uses the iq80 implementation and falls back
+ * to the xerial Snappy implementation it cannot be loaded. You can change the
+ * load order by setting the 'leveldb.snappy' system property. Example:
+ *
+ *
+ * -Dleveldb.snappy=xerial,iq80
+ *
+ *
+ * The system property can also be configured with the name of a class which
+ * implements the Snappy.SPI interface.
+ *
+ *
+ * @author Hiram Chirino
+ */
+public final class Snappy
+{
+ private Snappy()
+ {
+ }
+
+ public interface SPI
+ {
+ int uncompress(ByteBuffer compressed, ByteBuffer uncompressed)
+ throws IOException;
+
+ int uncompress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException;
+
+ int compress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException;
+
+ byte[] compress(String text)
+ throws IOException;
+
+ int maxCompressedLength(int length);
+ }
+
+ public static class XerialSnappy
+ implements SPI
+ {
+ static {
+ // Make sure that the JNI libs are fully loaded.
+ try {
+ org.xerial.snappy.Snappy.compress("test");
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int uncompress(ByteBuffer compressed, ByteBuffer uncompressed)
+ throws IOException
+ {
+ return org.xerial.snappy.Snappy.uncompress(compressed, uncompressed);
+ }
+
+ @Override
+ public int uncompress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ return org.xerial.snappy.Snappy.uncompress(input, inputOffset, length, output, outputOffset);
+ }
+
+ @Override
+ public int compress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ return org.xerial.snappy.Snappy.compress(input, inputOffset, length, output, outputOffset);
+ }
+
+ @Override
+ public byte[] compress(String text)
+ throws IOException
+ {
+ return org.xerial.snappy.Snappy.compress(text);
+ }
+
+ @Override
+ public int maxCompressedLength(int length)
+ {
+ return org.xerial.snappy.Snappy.maxCompressedLength(length);
+ }
+ }
+
+ public static class IQ80Snappy
+ implements SPI
+ {
+ static {
+ // Make sure that the library can fully load.
+ try {
+ new IQ80Snappy().compress("test");
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int uncompress(ByteBuffer compressed, ByteBuffer uncompressed)
+ throws IOException
+ {
+ byte[] input;
+ int inputOffset;
+ int length;
+ byte[] output;
+ int outputOffset;
+ if (compressed.hasArray()) {
+ input = compressed.array();
+ inputOffset = compressed.arrayOffset() + compressed.position();
+ length = compressed.remaining();
+ }
+ else {
+ input = new byte[compressed.remaining()];
+ inputOffset = 0;
+ length = input.length;
+ compressed.mark();
+ compressed.get(input);
+ compressed.reset();
+ }
+ if (uncompressed.hasArray()) {
+ output = uncompressed.array();
+ outputOffset = uncompressed.arrayOffset() + uncompressed.position();
+ }
+ else {
+ int t = org.iq80.snappy.Snappy.getUncompressedLength(input, inputOffset);
+ output = new byte[t];
+ outputOffset = 0;
+ }
+
+ int count = org.iq80.snappy.Snappy.uncompress(input, inputOffset, length, output, outputOffset);
+ if (uncompressed.hasArray()) {
+ uncompressed.limit(uncompressed.position() + count);
+ }
+ else {
+ int p = uncompressed.position();
+ uncompressed.limit(uncompressed.capacity());
+ uncompressed.put(output, 0, count);
+ uncompressed.flip().position(p);
+ }
+ return count;
+ }
+
+ @Override
+ public int uncompress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ return org.iq80.snappy.Snappy.uncompress(input, inputOffset, length, output, outputOffset);
+ }
+
+ @Override
+ public int compress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ return org.iq80.snappy.Snappy.compress(input, inputOffset, length, output, outputOffset);
+ }
+
+ @Override
+ public byte[] compress(String text)
+ throws IOException
+ {
+ byte[] uncomressed = text.getBytes(UTF_8);
+ byte[] compressedOut = new byte[maxCompressedLength(uncomressed.length)];
+ int compressedSize = compress(uncomressed, 0, uncomressed.length, compressedOut, 0);
+ byte[] trimmedBuffer = new byte[compressedSize];
+ System.arraycopy(compressedOut, 0, trimmedBuffer, 0, compressedSize);
+ return trimmedBuffer;
+ }
+
+ @Override
+ public int maxCompressedLength(int length)
+ {
+ return org.iq80.snappy.Snappy.maxCompressedLength(length);
+ }
+ }
+
+ private static final SPI SNAPPY;
+
+ static {
+ SPI attempt = null;
+ String[] factories = System.getProperty("leveldb.snappy", "iq80,xerial").split(",");
+ for (int i = 0; i < factories.length && attempt == null; i++) {
+ String name = factories[i];
+ try {
+ name = name.trim();
+ if ("xerial".equals(name.toLowerCase())) {
+ name = "org.iq80.leveldb.util.Snappy$XerialSnappy";
+ }
+ else if ("iq80".equals(name.toLowerCase())) {
+ name = "org.iq80.leveldb.util.Snappy$IQ80Snappy";
+ }
+ attempt = (SPI) Thread.currentThread().getContextClassLoader().loadClass(name).newInstance();
+ }
+ catch (Throwable e) {
+ }
+ }
+ SNAPPY = attempt;
+ }
+
+ public static boolean available()
+ {
+ return SNAPPY != null;
+ }
+
+ public static void uncompress(ByteBuffer compressed, ByteBuffer uncompressed)
+ throws IOException
+ {
+ SNAPPY.uncompress(compressed, uncompressed);
+ }
+
+ public static void uncompress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ SNAPPY.uncompress(input, inputOffset, length, output, outputOffset);
+ }
+
+ public static int compress(byte[] input, int inputOffset, int length, byte[] output, int outputOffset)
+ throws IOException
+ {
+ return SNAPPY.compress(input, inputOffset, length, output, outputOffset);
+ }
+
+ public static byte[] compress(String text)
+ throws IOException
+ {
+ return SNAPPY.compress(text);
+ }
+
+ public static int maxCompressedLength(int length)
+ {
+ return SNAPPY.maxCompressedLength(length);
+ }
+}
diff --git a/src/main/java/org/iq80/leveldb/util/TableIterator.java b/src/main/java/org/iq80/leveldb/util/TableIterator.java
new file mode 100644
index 0000000..d3e2c97
--- /dev/null
+++ b/src/main/java/org/iq80/leveldb/util/TableIterator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2011 the original author or authors.
+ * See the notice.md file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.iq80.leveldb.util;
+
+import org.iq80.leveldb.table.Block;
+import org.iq80.leveldb.table.BlockIterator;
+import org.iq80.leveldb.table.Table;
+
+import java.util.Map.Entry;
+
+public final class TableIterator
+ extends AbstractSeekingIterator
+{
+ private final Table table;
+ private final BlockIterator blockIterator;
+ private BlockIterator current;
+
+ public TableIterator(Table table, BlockIterator blockIterator)
+ {
+ this.table = table;
+ this.blockIterator = blockIterator;
+ current = null;
+ }
+
+ @Override
+ protected void seekToFirstInternal()
+ {
+ // reset index to before first and clear the data iterator
+ blockIterator.seekToFirst();
+ current = null;
+ }
+
+ @Override
+ protected void seekInternal(Slice targetKey)
+ {
+ // seek the index to the block containing the key
+ blockIterator.seek(targetKey);
+
+ // if indexIterator does not have a next, it mean the key does not exist in this iterator
+ if (blockIterator.hasNext()) {
+ // seek the current iterator to the key
+ current = getNextBlock();
+ current.seek(targetKey);
+ }
+ else {
+ current = null;
+ }
+ }
+
+ @Override
+ protected Entry getNextElement()
+ {
+ // note: it must be here & not where 'current' is assigned,
+ // because otherwise we'll have called inputs.next() before throwing
+ // the first NPE, and the next time around we'll call inputs.next()
+ // again, incorrectly moving beyond the error.
+ boolean currentHasNext = false;
+ while (true) {
+ if (current != null) {
+ currentHasNext = current.hasNext();
+ }
+ if (!(currentHasNext)) {
+ if (blockIterator.hasNext()) {
+ current = getNextBlock();
+ }
+ else {
+ break;
+ }
+ }
+ else {
+ break;
+ }
+ }
+ if (currentHasNext) {
+ return current.next();
+ }
+ else {
+ // set current to empty iterator to avoid extra calls to user iterators
+ current = null;
+ return null;
+ }
+ }
+
+ private BlockIterator getNextBlock()
+ {
+ Slice blockHandle = blockIterator.next().getValue();
+ Block dataBlock = table.openBlock(blockHandle);
+ return dataBlock.iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ConcatenatingIterator");
+ sb.append("{blockIterator=").append(blockIterator);
+ sb.append(", current=").append(current);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/org/iq80/leveldb/util/VariableLengthQuantity.java b/src/main/java/org/iq80/leveldb/util/VariableLengthQuantity.java
new file mode 100644
index 0000000..7913e13
--- /dev/null
+++ b/src/main/java/org/iq80/leveldb/util/VariableLengthQuantity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 the original author or authors.
+ * See the notice.md file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.iq80.leveldb.util;
+
+import java.nio.ByteBuffer;
+
+public final class VariableLengthQuantity
+{
+ private VariableLengthQuantity()
+ {
+ }
+
+ public static int variableLengthSize(int value)
+ {
+ int size = 1;
+ while ((value & (~0x7f)) != 0) {
+ value >>>= 7;
+ size++;
+ }
+ return size;
+ }
+
+ public static int variableLengthSize(long value)
+ {
+ int size = 1;
+ while ((value & (~0x7f)) != 0) {
+ value >>>= 7;
+ size++;
+ }
+ return size;
+ }
+
+ public static void writeVariableLengthInt(int value, SliceOutput sliceOutput)
+ {
+ int highBitMask = 0x80;
+ if (value < (1 << 7) && value >= 0) {
+ sliceOutput.writeByte(value);
+ }
+ else if (value < (1 << 14) && value > 0) {
+ sliceOutput.writeByte(value | highBitMask);
+ sliceOutput.writeByte(value >>> 7);
+ }
+ else if (value < (1 << 21) && value > 0) {
+ sliceOutput.writeByte(value | highBitMask);
+ sliceOutput.writeByte((value >>> 7) | highBitMask);
+ sliceOutput.writeByte(value >>> 14);
+ }
+ else if (value < (1 << 28) && value > 0) {
+ sliceOutput.writeByte(value | highBitMask);
+ sliceOutput.writeByte((value >>> 7) | highBitMask);
+ sliceOutput.writeByte((value >>> 14) | highBitMask);
+ sliceOutput.writeByte(value >>> 21);
+ }
+ else {
+ sliceOutput.writeByte(value | highBitMask);
+ sliceOutput.writeByte((value >>> 7) | highBitMask);
+ sliceOutput.writeByte((value >>> 14) | highBitMask);
+ sliceOutput.writeByte((value >>> 21) | highBitMask);
+ sliceOutput.writeByte(value >>> 28);
+ }
+ }
+
+ public static void writeVariableLengthLong(long value, SliceOutput sliceOutput)
+ {
+ // while value more than the first 7 bits set
+ while ((value & (~0x7f)) != 0) {
+ sliceOutput.writeByte((int) ((value & 0x7f) | 0x80));
+ value >>>= 7;
+ }
+ sliceOutput.writeByte((int) value);
+ }
+
+ public static int readVariableLengthInt(SliceInput sliceInput)
+ {
+ int result = 0;
+ for (int shift = 0; shift <= 28; shift += 7) {
+ int b = sliceInput.readUnsignedByte();
+
+ // add the lower 7 bits to the result
+ result |= ((b & 0x7f) << shift);
+
+ // if high bit is not set, this is the last byte in the number
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ }
+ throw new NumberFormatException("last byte of variable length int has high bit set");
+ }
+
+ public static int readVariableLengthInt(ByteBuffer sliceInput)
+ {
+ int result = 0;
+ for (int shift = 0; shift <= 28; shift += 7) {
+ int b = sliceInput.get();
+
+ // add the lower 7 bits to the result
+ result |= ((b & 0x7f) << shift);
+
+ // if high bit is not set, this is the last byte in the number
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ }
+ throw new NumberFormatException("last byte of variable length int has high bit set");
+ }
+
+ public static long readVariableLengthLong(SliceInput sliceInput)
+ {
+ long result = 0;
+ for (int shift = 0; shift <= 63; shift += 7) {
+ long b = sliceInput.readUnsignedByte();
+
+ // add the lower 7 bits to the result
+ result |= ((b & 0x7f) << shift);
+
+ // if high bit is not set, this is the last byte in the number
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ }
+ throw new NumberFormatException("last byte of variable length int has high bit set");
+ }
+}
diff --git a/src/test/java/ApiTest.java b/src/test/java/ApiTest.java
new file mode 100644
index 0000000..ff3b279
--- /dev/null
+++ b/src/test/java/ApiTest.java
@@ -0,0 +1,48 @@
+import org.iq80.leveldb.api.DB;
+import org.iq80.leveldb.api.Options;
+import org.iq80.leveldb.impl.Iq80DBFactory;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.iq80.leveldb.impl.Iq80DBFactory.asString;
+import static org.iq80.leveldb.impl.Iq80DBFactory.bytes;
+
+public final class ApiTest {
+
+ @Test
+ void testOpen() throws IOException {
+ Options options = new Options();
+ options.createIfMissing(true);
+ try (DB db = Iq80DBFactory.factory.open(new File("example"), options)) {
+ System.out.println(db != null);
+ }
+ }
+
+
+ @Test
+ void testPutAndGet() throws IOException {
+ Options options = new Options();
+ options.createIfMissing(true);
+ try (DB db = Iq80DBFactory.factory.open(new File("example"), options)) {
+ db.put(bytes("Tampa"), bytes("rocks"));
+ String value = asString(db.get(bytes("Tampa")));
+ System.out.println(value);
+ }
+ }
+
+
+ @Test
+ void testDelete() throws IOException {
+ Options options = new Options();
+ options.createIfMissing(true);
+ try (DB db = Iq80DBFactory.factory.open(new File("example"), options)) {
+ db.put(bytes("Tampa"), bytes("rocks"));
+ System.out.println(asString(db.get(bytes("Tampa"))));
+ db.delete(bytes("Tampa"));
+ System.out.println(asString(db.get(bytes("Tampa"))));
+ }
+ }
+
+}