Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions vespajlib/abi-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"com.yahoo.data.access.Inspector" : {
"superClass" : "java.lang.Object",
"interfaces" : [
"com.yahoo.data.access.Inspectable"
"com.yahoo.data.access.Inspectable",
"com.yahoo.data.disclosure.DataSource"
],
"attributes" : [
"public",
Expand Down Expand Up @@ -85,7 +86,8 @@
"public abstract com.yahoo.data.access.Inspector entry(int)",
"public abstract com.yahoo.data.access.Inspector field(java.lang.String)",
"public abstract java.lang.Iterable entries()",
"public abstract java.lang.Iterable fields()"
"public abstract java.lang.Iterable fields()",
"public void emit(com.yahoo.data.disclosure.DataSink)"
],
"fields" : [ ]
},
Expand Down Expand Up @@ -306,7 +308,8 @@
"public java.lang.String asString()",
"public java.lang.String asString(java.lang.String)",
"public byte[] asUtf8()",
"public byte[] asUtf8(byte[])"
"public byte[] asUtf8(byte[])",
"public void emit(com.yahoo.data.disclosure.DataSink)"
],
"fields" : [ ]
},
Expand Down
37 changes: 35 additions & 2 deletions vespajlib/src/main/java/com/yahoo/data/access/Inspector.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
package com.yahoo.data.access;


import com.yahoo.data.disclosure.DataSink;
import com.yahoo.data.disclosure.DataSource;

import java.util.Map;

/**
Expand All @@ -15,7 +18,7 @@
*
* @author havardpe
*/
public interface Inspector extends Inspectable {
public interface Inspector extends Inspectable, DataSource {

/**
* Check if the inspector is valid.
Expand Down Expand Up @@ -109,7 +112,7 @@ public interface Inspector extends Inspectable {
Inspector entry(int idx);

/**
* Access an field in an object.
* Access a field in an object.
*
* If the current Inspector doesn't connect to an object value, or
* the object value does not contain a field with the given symbol
Expand All @@ -132,4 +135,34 @@ public interface Inspector extends Inspectable {
*/
Iterable<Map.Entry<String,Inspector>> fields();

/**
* Default implementation of emitting an inspector. Defaults to converting
* strings to UTF-16.
*/
@Override
default void emit(DataSink sink) {
switch (type()) {
case EMPTY -> sink.emptyValue();
case BOOL -> sink.booleanValue(asBool());
case LONG -> sink.longValue(asLong());
case DOUBLE -> sink.doubleValue(asDouble());
case STRING -> sink.stringValue(asString());
case DATA -> sink.dataValue(asData());
case ARRAY -> {
sink.startArray();
for (var entry : entries()) {
entry.emit(sink);
}
sink.endArray();
}
case OBJECT -> {
sink.startObject();
for (var field : fields()) {
sink.fieldName(field.getKey());
field.getValue().emit(sink);
}
sink.endObject();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.yahoo.data.access.Inspector;
import com.yahoo.data.access.ObjectTraverser;
import com.yahoo.data.access.Type;
import com.yahoo.data.disclosure.DataSink;

import java.util.Collections;
import java.util.Map;
Expand Down Expand Up @@ -118,6 +119,11 @@ public byte[] asUtf8() {
return utf8_value;
}
public byte[] asUtf8(byte[] x) { return asUtf8(); }

@Override
public void emit(DataSink sink) {
sink.stringValue(string_value, utf8_value);
}
}
static public class DataValue extends Value {
private byte[] value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.data.access.slime;

import com.yahoo.data.disclosure.DataSink;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Type;
import com.yahoo.slime.Visitor;

import com.yahoo.slime.ArrayTraverser;
import com.yahoo.slime.ObjectTraverser;

import java.util.Map;
import java.util.AbstractMap;
Expand Down Expand Up @@ -183,4 +189,66 @@ public void field(String name, com.yahoo.slime.Inspector inspector) {
return list;
}

@Override
public void emit(DataSink sink) {
inspector.accept(new InspectorVisitor(sink));
}

private record InspectorVisitor(DataSink sink) implements Visitor {

@Override
public void visitInvalid() { /* no-op */ }

@Override
public void visitNix() {
sink.emptyValue();
}

@Override
public void visitBool(boolean bit) {
sink.booleanValue(bit);
}

@Override
public void visitLong(long l) {
sink.longValue(l);
}

@Override
public void visitDouble(double d) {
sink.doubleValue(d);
}

@Override
public void visitString(String str) {
sink.stringValue(str);
}

@Override
public void visitString(byte[] utf8) {
sink.stringValue(utf8);
}

@Override
public void visitData(byte[] data) {
sink.dataValue(data);
}

@Override
public void visitArray(Inspector arr) {
sink.startArray();
arr.traverse((ArrayTraverser) (idx, value) -> value.accept(this));
sink.endArray();
}

@Override
public void visitObject(Inspector obj) {
sink.startObject();
obj.traverse((ObjectTraverser) (fieldName, value) -> {
sink.fieldName(fieldName);
value.accept(this);
});
sink.endObject();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.data.access;

import com.yahoo.data.access.simple.Value;
import com.yahoo.data.access.slime.SlimeAdapter;
import com.yahoo.data.disclosure.DataSink;
import com.yahoo.data.disclosure.slime.SlimeDataSink;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import org.junit.Test;

import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Uses slime utils and slime adapter to make inspectors from JSON. Then, the slime data
* sink is used to convert this back to slime. These tests check that arrays and objects
* distribute as expected and leafs are preserved.
*
* @author johsol
*/
public class InspectorDataSourceTest {

private void assertSlime(Slime expected, Slime actual) {
assertTrue(expected.get().equalTo(actual.get()),
() -> "Expected " + SlimeUtils.toJson(expected) +
" but got " + SlimeUtils.toJson(actual));
}

@Test
public void testValuesInObjectIsPreserved() {
var expected = SlimeUtils.jsonToSlime("{ int: 1024, " +
" bool: true," +
" double: 3.5," +
" my_null: null," +
" string: 'hello' }");

var inspector = new SlimeAdapter(expected.get());
var actual = SlimeDataSink.buildSlime(inspector);
assertSlime(expected, actual);
}

@Test
public void testValuesInArrayIsPreserved() {
var expected = SlimeUtils.jsonToSlime("[1, true, 2.5, 'foo', null]");

var inspector = new SlimeAdapter(expected.get());
var actual = SlimeDataSink.buildSlime(inspector);
assertSlime(expected, actual);
}

@Test
public void testNestedObjectAndArrayArePreserved() {
var expected = SlimeUtils.jsonToSlime("{ nums: [1, 2], meta: { ok: true } }");

var inspector = new SlimeAdapter(expected.get());
var actual = SlimeDataSink.buildSlime(inspector);
assertSlime(expected, actual);
}

@Test
public void testArrayOfObjectsArePreserved() {
var expected = SlimeUtils.jsonToSlime("[ { id: 1 }, { id: 2 } ]");

var inspector = new SlimeAdapter(expected.get());
var actual = SlimeDataSink.buildSlime(inspector);
assertSlime(expected, actual);
}

@Test
public void testBinaryDataIsPreserved() {
byte[] bytes = new byte[]{0, 1, 2, (byte) 0xFF};
Inspector inspector = new Value.DataValue(bytes);
Slime actual = SlimeDataSink.buildSlime(inspector);

Slime expected = new Slime();
expected.setData(bytes);
assertSlime(expected, actual);
}

/**
* Captures the utf8 value from string value.
*/
static class Utf8CaptureSink implements DataSink {
byte[] lastUtf8;

public void stringValue(String u16, byte[] u8) {
lastUtf8 = u8;
}

public void fieldName(String utf16, byte[] utf8) { /* no-op */ }

public void fieldName(String u16) { /* no-op */ }

public void startObject() { /* no-op */ }

public void endObject() { /* no-op */ }

public void startArray() { /* no-op */ }

public void endArray() { /* no-op */ }

public void emptyValue() { /* no-op */ }

public void booleanValue(boolean v) { /* no-op */ }

public void longValue(long v) { /* no-op */ }

public void doubleValue(double v) { /* no-op */ }

public void dataValue(byte[] d) { /* no-op */ }

public void stringValue(String u16) { /* no-op */ }
}

@Test
public void testUtf8StringIsPreserved() {
byte[] utf8 = "hello world".getBytes(StandardCharsets.UTF_8);
Inspector inspector = new Value.StringValue(utf8);
var sink = new Utf8CaptureSink();
inspector.emit(sink);
assertArrayEquals(utf8, sink.lastUtf8);
}

}