Implemented exists()

This commit is contained in:
Andrea Cavalli 2019-03-10 00:21:52 +01:00
parent 1def19d219
commit 2e9d989a6c
10 changed files with 303 additions and 114 deletions

34
pom.xml
View File

@ -27,9 +27,15 @@
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
@ -110,6 +116,28 @@
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>it.cavallium.strangedb.server.Server</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>

View File

@ -4,12 +4,14 @@ import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import it.cavallium.strangedb.database.IReferencesIO;
import it.cavallium.strangedb.database.references.DatabaseReferencesMetadata;
import org.json.*;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import static it.cavallium.strangedb.server.TreePath.stringToNode;
@ -18,7 +20,9 @@ public class DatabaseNodesIO {
private final IReferencesIO referencesIO;
private static final int isArrayMask = 0b10000000000000000000000000000000;
private static final int isValueMask = 0b01000000000000000000000000000000;
private static final int isNumberMask = 0b11000000000000000000000000000000;
private static final int isValueNumberMask = 0b00000000000000000000000000000001;
private static final int isValueStringMask = 0b00000000000000000000000000000010;
private static final int isValueBooleanMask = 0b00000000000000000000000000000100;
private static final int nodeCountMask = 0b01111111111111111111111111111111;
private static final int arrayCountMask = 0b01111111111111111111111111111111;
@ -37,8 +41,12 @@ public class DatabaseNodesIO {
}
public String get(TreePath path) throws IOException {
try {
Node foundNode = getNode(path);
return toJson(foundNode);
} catch (NullPointerException ex) {
return "null";
}
}
private String toJson(Node foundNode) throws IOException {
@ -82,12 +90,7 @@ public class DatabaseNodesIO {
}
case VALUE: {
ValueNode valueNode = (ValueNode) foundNode;
sb.append(org.json.JSONObject.quote(loadValue(valueNode.getValueReference())));
break;
}
case NUMBER: {
NumberNode valueNode = (NumberNode) foundNode;
sb.append(loadNumber(valueNode.getNumberReference()));
sb.append(loadValue(valueNode));
break;
}
}
@ -98,16 +101,19 @@ public class DatabaseNodesIO {
referencesIO.writeToReference(reference, valueBytes.limit(), valueBytes);
}
private String loadValue(long reference) throws IOException {
ByteBuffer buffer = referencesIO.readFromReference(reference);
return StandardCharsets.UTF_8.decode(buffer).toString();
private String loadValue(ValueNode valueNode) throws IOException {
ByteBuffer buffer = referencesIO.readFromReference(valueNode.getReference());
ByteBuffer valueBuffer = referencesIO.readFromReference(valueNode.getValueReference());
switch (valueNode.getValueType()) {
case STRING:
return JSONObject.quote(StandardCharsets.UTF_8.decode(valueBuffer).toString());
case NUMBER:
return JSONObject.doubleToString(valueBuffer.getDouble());
case BOOLEAN:
return valueBuffer.get() == 1 ? "true" : "false";
default:
throw new RuntimeException();
}
private void setNumber(long reference, double value) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(Double.BYTES);
buffer.putDouble(value);
buffer.flip();
referencesIO.writeToReference(reference, buffer.limit(), buffer);
}
private double loadNumber(long reference) throws IOException {
@ -149,7 +155,6 @@ public class DatabaseNodesIO {
classNode.setProperty(propertyName, valueReference);
break;
}
case NUMBER:
case VALUE: {
throw new IOException("WTF you shouldn't be here!");
}
@ -171,6 +176,15 @@ public class DatabaseNodesIO {
case '{':
JSONObject obj = new JSONObject(value);
return importJsonNode(obj, reference);
case 'u':
if (value.equals("undefined"))
return importJsonNode(value, reference);
case 'N':
if (value.equals("NaN"))
return importJsonNode(value, reference);
case 'n':
if (value.equals("null"))
return importJsonNode(value, reference);
case '"':
return importJsonNode(value.substring(1, value.length() - 1), reference);
case '0':
@ -185,6 +199,12 @@ public class DatabaseNodesIO {
case '9':
case '.':
return importJsonNode(Double.parseDouble(value), reference);
case 't':
if (value.equals("true"))
return importJsonNode(true, reference);
case 'f':
if (value.equals("false"))
return importJsonNode(false, reference);
default:
throw new IOException("Unrecognized input");
}
@ -199,6 +219,9 @@ public class DatabaseNodesIO {
if (o instanceof ArrayList<?>) {
o = new JSONArray((ArrayList<?>) o);
}
if (o instanceof HashMap<?, ?>) {
o = new JSONObject((HashMap<?, ?>) o);
}
if (o instanceof JSONArray) {
JSONArray array = (JSONArray) o;
int length = array.length();
@ -217,12 +240,13 @@ public class DatabaseNodesIO {
for (Map.Entry<String, Object> entry : jsonProperties.entrySet()) {
NodeProperty nodeProperty = new NodeProperty(stringToNode(entry.getKey()), importJsonNode(entry.getValue()));
properties[i] = nodeProperty;
i++;
}
Node node = createNewNodeNode(properties, reference);
setNode(node);
return node.getReference();
} else if (o instanceof String) {
Node node = createNewValueNode((String) o, reference);
Node node = createNewValueNode(o, ValueType.STRING, reference);
setNode(node);
return node.getReference();
} else if (o instanceof Double || o instanceof Float || o instanceof Integer || o instanceof Long) {
@ -233,12 +257,14 @@ public class DatabaseNodesIO {
number = (Long) o;
} else if (o instanceof Float) {
number = (Float) o;
} else if (o instanceof Double) {
number = (Double) o;
} else {
throw new RuntimeException();
number = (Double) o;
}
Node node = createNewNumberNode(number+0, reference);
Node node = createNewValueNode(number, ValueType.NUMBER, reference);
setNode(node);
return node.getReference();
} else if (o instanceof Boolean) {
Node node = createNewValueNode(o, ValueType.BOOLEAN, reference);
setNode(node);
return node.getReference();
} else {
@ -252,6 +278,7 @@ public class DatabaseNodesIO {
public boolean exists(TreePath path) throws IOException {
TreePathWalker pathWalker = new TreePathWalker(path);
pathWalker.walkToRoot();
Node node = loadNode(0);
while (pathWalker.hasNext()) {
TreePath nodePath = pathWalker.next();
@ -260,7 +287,7 @@ public class DatabaseNodesIO {
return false;
}
int offset = nodePath.getArrayOffset();
if (!((ArrayNode)node).hasItem(offset)) {
if (!((ArrayNode) node).hasItem(offset)) {
return false;
}
long nodeReference = ((ArrayNode) node).getItem(offset);
@ -280,6 +307,52 @@ public class DatabaseNodesIO {
return node != null;
}
public int size(CharSequence path) throws IOException {
return size(TreePath.get(path));
}
public int size(TreePath path) throws IOException {
TreePathWalker pathWalker = new TreePathWalker(path);
pathWalker.walkToRoot();
Node node = loadNode(0);
while (pathWalker.hasNext()) {
TreePath nodePath = pathWalker.next();
if (nodePath.isArrayOffset()) {
if (node.getType() != NodeType.ARRAY) {
throw new NullPointerException("Node not found");
}
int offset = nodePath.getArrayOffset();
if (!((ArrayNode) node).hasItem(offset)) {
throw new NullPointerException("Node not found");
}
long nodeReference = ((ArrayNode) node).getItem(offset);
node = loadNode(nodeReference);
} else if (nodePath.isNodeProperty()) {
if (node.getType() == NodeType.ARRAY) {
throw new NullPointerException("Node not found");
}
byte[] propertyName = nodePath.getNodeValue();
if (!((ClassNode) node).hasProperty(propertyName)) {
throw new NullPointerException("Node not found");
}
long nodeReference = ((ClassNode) node).getProperty(propertyName);
node = loadNode(nodeReference);
}
}
if (node != null) {
switch (node.getType()) {
case ARRAY:
return ((ArrayNode) node).countItems();
case CLASS:
return ((ClassNode) node).countProperties();
default:
throw new NullPointerException("You can't get the size of this node!");
}
} else {
throw new NullPointerException("Node not found");
}
}
private Node getNode(TreePath path) throws IOException {
TreePathWalker pathWalker = new TreePathWalker(path);
pathWalker.walkToRoot();
@ -320,27 +393,43 @@ public class DatabaseNodesIO {
return new ArrayNode(reference, items);
}
private Node createNewValueNode() throws IOException {
private Node createNewValueNode(ValueType type) throws IOException {
long reference = referencesIO.allocateReference();
return new ValueNode(reference, referencesIO.allocateReference());
return new ValueNode(reference, referencesIO.allocateReference(), type);
}
private Node createNewValueNode(String value) throws IOException {
return createNewValueNode(value, referencesIO.allocateReference());
return createNewValueNode(value, ValueType.STRING, referencesIO.allocateReference());
}
private Node createNewValueNode(String value, long reference) throws IOException {
byte[] string = value.getBytes(StandardCharsets.UTF_8);
long dataReference = referencesIO.allocateReference(string.length, ByteBuffer.wrap(string));
return new ValueNode(reference, dataReference);
private Node createNewValueNode(double value) throws IOException {
return createNewValueNode(value, ValueType.NUMBER, referencesIO.allocateReference());
}
private Node createNewNumberNode(double value, long reference) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(Double.BYTES);
buffer.putDouble(value);
private Node createNewValueNode(Object value, ValueType valueType, long reference) throws IOException {
ByteBuffer buffer = null;
switch (valueType) {
case STRING:
buffer = StandardCharsets.UTF_8.encode((String) value);
break;
case NUMBER:
buffer = ByteBuffer.allocate(Double.BYTES);
buffer.putDouble((Double) value);
buffer.flip();
long dataReference = referencesIO.allocateReference(buffer.limit(), buffer);
return new NumberNode(reference, dataReference);
break;
case BOOLEAN:
buffer = ByteBuffer.allocate(Byte.BYTES);
buffer.put(((boolean) value) ? (byte) 1 : 0);
buffer.flip();
break;
default:
throw new RuntimeException();
}
long dataReference = -1;
if (buffer != null) {
dataReference = referencesIO.allocateReference(buffer.limit(), buffer);
}
return new ValueNode(reference, dataReference, valueType);
}
private Node createNewNodeNode() throws IOException {
@ -383,15 +472,22 @@ public class DatabaseNodesIO {
buffer = ByteBuffer.wrap(dataOutput.toByteArray());
break;
case VALUE:
ValueNode valueNode = (ValueNode) node;
buffer = ByteBuffer.allocate(Integer.BYTES + Long.BYTES);
buffer.putInt(isValueMask);
buffer.putLong(((ValueNode) node).getValueReference());
buffer.flip();
switch (valueNode.getValueType()) {
case STRING:
buffer.putInt(isValueMask | isValueStringMask);
break;
case NUMBER:
buffer = ByteBuffer.allocate(Integer.BYTES + Long.BYTES);
buffer.putInt(isNumberMask);
buffer.putLong(((NumberNode) node).getNumberReference());
buffer.putInt(isValueMask | isValueNumberMask);
break;
case BOOLEAN:
buffer.putInt(isValueMask | isValueBooleanMask);
break;
default:
throw new RuntimeException();
}
buffer.putLong(valueNode.getValueReference());
buffer.flip();
break;
default:
@ -404,8 +500,7 @@ public class DatabaseNodesIO {
Node node;
ByteBuffer nodeData = referencesIO.readFromReference(reference);
int propertiesCount = nodeData.getInt();
boolean isNumber = (propertiesCount & isNumberMask) == isNumberMask;
boolean isArray = !isNumber && (propertiesCount & isArrayMask) != 0;
boolean isArray = (propertiesCount & isArrayMask) != 0;
if (isArray) {
int arrayElementsCount = propertiesCount & arrayCountMask;
long[] arrayElementsReferences = new long[arrayElementsCount];
@ -414,13 +509,18 @@ public class DatabaseNodesIO {
}
node = new ArrayNode(reference, arrayElementsReferences);
} else {
boolean isValue = !isNumber && (propertiesCount & isValueMask) != 0;
boolean isValue = (propertiesCount & isValueMask) != 0;
if (isValue) {
long valueReference = nodeData.getLong();
node = new ValueNode(reference, valueReference);
} else if (isNumber) {
long numberReference = nodeData.getLong();
node = new NumberNode(reference, numberReference);
long dataReference = nodeData.getLong();
if ((propertiesCount & isValueStringMask) != 0) {
node = new ValueNode(reference, dataReference, ValueType.STRING);
} else if ((propertiesCount & isValueNumberMask) != 0) {
node = new ValueNode(reference, dataReference, ValueType.NUMBER);
} else if ((propertiesCount & isValueBooleanMask) != 0) {
node = new ValueNode(reference, dataReference, ValueType.BOOLEAN);
} else {
throw new IOException();
}
} else {
NodeProperty[] nodeProperties = new NodeProperty[propertiesCount & nodeCountMask];
for (int i = 0; i < propertiesCount; i++) {

View File

@ -24,4 +24,8 @@ public class DatabaseSimple extends DatabaseCore {
public boolean exists(CharSequence path) throws IOException {
return databaseNodesIO.exists(path);
}
public int size(CharSequence path) throws IOException {
return databaseNodesIO.size(path);
}
}

View File

@ -3,6 +3,5 @@ package it.cavallium.strangedb.server;
public enum NodeType {
VALUE,
CLASS,
ARRAY,
NUMBER;
ARRAY
}

View File

@ -1,35 +0,0 @@
package it.cavallium.strangedb.server;
public class NumberNode implements Node {
private final long reference;
private long value;
public NumberNode(long reference, long value) {
this.reference = reference;
this.value = value;
}
public long getNumberReference() {
return value;
}
public void setValue(long value) {
this.value = value;
}
@Override
public NodeType getType() {
return NodeType.NUMBER;
}
@Override
public long getReference() {
return reference;
}
@Override
public Node copy() {
return new NumberNode(reference, value);
}
}

View File

@ -4,6 +4,7 @@ import com.sun.net.httpserver.HttpServer;
import it.cavallium.strangedb.server.http.ExistsHandler;
import it.cavallium.strangedb.server.http.GetHandler;
import it.cavallium.strangedb.server.http.SetHandler;
import it.cavallium.strangedb.server.http.SizeHandler;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -19,6 +20,7 @@ public class Server {
server.createContext("/get", new GetHandler(this));
server.createContext("/set", new SetHandler(this));
server.createContext("/exists", new ExistsHandler(this));
server.createContext("/size", new SizeHandler(this));
server.setExecutor(null);
server.start();
}
@ -41,6 +43,10 @@ public class Server {
return database.exists(path);
}
public int size(CharSequence path) throws IOException {
return database.size(path);
}
public String get(CharSequence path) throws IOException {
return database.get(path);
}

View File

@ -5,11 +5,13 @@ import java.util.Arrays;
public class ValueNode implements Node {
private final long reference;
private final ValueType valueType;
private long value;
public ValueNode(long reference, long value) {
public ValueNode(long reference, long value, ValueType valueType) {
this.reference = reference;
this.value = value;
this.valueType = valueType;
}
public long getValueReference() {
@ -25,6 +27,10 @@ public class ValueNode implements Node {
return NodeType.VALUE;
}
public ValueType getValueType() {
return valueType;
}
@Override
public long getReference() {
return reference;
@ -32,6 +38,6 @@ public class ValueNode implements Node {
@Override
public Node copy() {
return new ValueNode(reference, value);
return new ValueNode(reference, value, valueType);
}
}

View File

@ -0,0 +1,7 @@
package it.cavallium.strangedb.server;
public enum ValueType {
STRING,
NUMBER,
BOOLEAN
}

View File

@ -0,0 +1,37 @@
package it.cavallium.strangedb.server.http;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import it.cavallium.strangedb.server.Server;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class SizeHandler implements HttpHandler {
private final Server server;
public SizeHandler(Server server) {
this.server = server;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
String requestPath = exchange.getRequestURI().toString().split("/size/", 2)[1].replace('(', '[').replace(')', ']');
int responseCode = 500;
String response;
try {
response = "" + server.size(requestPath);
responseCode = 200;
} catch (Exception ex) {
ex.printStackTrace();
response = "Error";
}
byte[] responseBytes = response.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(responseCode, responseBytes.length);
OutputStream os = exchange.getResponseBody();
os.write(responseBytes);
os.close();
}
}

View File

@ -1,14 +1,15 @@
package it.cavallium.strangedb.server;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static junit.framework.Assert.*;
import static org.junit.jupiter.api.Assertions.*;
public class GetSetExistsTest {
@ -17,7 +18,7 @@ public class GetSetExistsTest {
private Path path3;
private DatabaseSimple db;
@Before
@BeforeEach
public void setUp() throws Exception {
path1 = Files.createTempFile("db-tests-", ".db");
path2 = Files.createTempFile("db-tests-", ".db");
@ -32,22 +33,58 @@ public class GetSetExistsTest {
db.set("", "{}");
assertTrue(db.exists(""));
assertEquals(db.get(""), "{}");
db.set("brightness", "\"15\"");
db.set("value", "{}");
db.set("value.int", "12");
db.set("value.number", "12.13");
db.set("value.null", "null");
db.set("value.boolean", "true");
db.set("value.undefined", "undefined");
db.set("value.string", "\"test\"");
db.set("value.array", "[1,2,3,4,5]");
db.set("value.object", "{a: 1, b: \"2\", c: [3], d: {}}");
}
@Test
public void shouldGetObject() throws IOException {
assertFalse(db.exists("brightness"));
db.set("brightness", "\"15\"");
assertTrue(db.exists("brightness"));
assertEquals(db.get("brightness"), "\"15\"");
assertEquals(db.get(""), "{brightness:\"15\"}");
db.set("brightness", "\"16\"");
assertEquals(db.get("brightness"), "\"16\"");
assertEquals(db.get(""), "{brightness:\"16\"}");
db.set("", "{\n" +
"\t\"value\": {\n" +
"\t\t\"int\": 12,\n" +
"\t\t\"number\": 12.13," +
"nil: null,\n" +
"\t\t\"boolean\": true,\n" +
"\t\t\"string\": \"test\",\n" +
"\t\t\"array\": [1,2,3,4],\n" +
"\t\t\"object\": {\n" +
"\t\t\t\"a\": 1,\n" +
"\t\t\t\"b\": \"2\",\n" +
"\t\t\t\"c\": [3],\n" +
"\t\t\t\"d\": {}\n" +
"\t\t}\n" +
"\t}\n" +
"}");
assertFalse(db.exists("random"));
assertTrue(db.exists("value"));
assertEquals("12", db.get("value.int"));
assertEquals("12.13", db.get("value.number"));
assertEquals("null", db.get("value.nil"));
assertEquals("true", db.get("value.boolean"));
assertEquals("\"test\"", db.get("value.string"));
assertEquals("[1,2,3,4]", db.get("value.array"));
assertEquals("1", db.get("value.object.a"));
assertEquals("\"2\"", db.get("value.object.b"));
assertEquals("[3]", db.get("value.object.c"));
assertEquals("{}", db.get("value.object.d"));
}
@After
@Test
public void shouldGetSize() throws IOException {
db.set("", "{array:[1,2,3,4,5], object: {a: 1, b: 2, c: 3}, string: \"test\"}");
assertEquals(5, db.size("array"));
assertEquals(3, db.size("object"));
assertThrows(NullPointerException.class, () -> db.size("test"));
}
@AfterEach
public void tearDown() throws Exception {
db.close();
Files.deleteIfExists(path1);