aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@yahoo-inc.com>2017-01-20 15:13:02 +0100
committerTor Brede Vekterli <vekterli@yahoo-inc.com>2017-01-23 11:18:57 +0100
commit4bc7e514ab6e930bc3bf25371eed40cd2dd1264a (patch)
tree10d297e375b0e060749ff8b53aaf5ad11ab82d25
parentdc644db693f8a6857cbb53e46c67aafa8f807828 (diff)
Add ReferenceDataType and ReferenceFieldValue
-rw-r--r--document/src/main/java/com/yahoo/document/ReferenceDataType.java39
-rw-r--r--document/src/main/java/com/yahoo/document/datatypes/ReferenceFieldValue.java104
-rw-r--r--document/src/test/java/com/yahoo/document/ReferenceDataTypeTestCase.java80
-rw-r--r--document/src/test/java/com/yahoo/document/datatypes/ReferenceFieldValueTestCase.java145
4 files changed, 368 insertions, 0 deletions
diff --git a/document/src/main/java/com/yahoo/document/ReferenceDataType.java b/document/src/main/java/com/yahoo/document/ReferenceDataType.java
new file mode 100644
index 00000000000..0c8d8fcc2d8
--- /dev/null
+++ b/document/src/main/java/com/yahoo/document/ReferenceDataType.java
@@ -0,0 +1,39 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.document;
+
+import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.document.datatypes.ReferenceFieldValue;
+
+public class ReferenceDataType extends DataType {
+
+ private final DocumentType targetType;
+
+ public ReferenceDataType(DocumentType targetType, int id) {
+ super("Reference<" + targetType.getName() + ">", id);
+ this.targetType = targetType;
+ }
+
+ public DataType getTargetType() { return targetType; }
+
+ @Override
+ public ReferenceFieldValue createFieldValue() {
+ return new ReferenceFieldValue(this);
+ }
+
+ @Override
+ public Class<? extends ReferenceFieldValue> getValueClass() {
+ return ReferenceFieldValue.class;
+ }
+
+ @Override
+ public boolean isValueCompatible(FieldValue value) {
+ if (value == null) {
+ return false;
+ }
+ if (!ReferenceFieldValue.class.isAssignableFrom(value.getClass())) {
+ return false;
+ }
+ ReferenceFieldValue rhs = (ReferenceFieldValue)value;
+ return rhs.getDataType().equals(this);
+ }
+}
diff --git a/document/src/main/java/com/yahoo/document/datatypes/ReferenceFieldValue.java b/document/src/main/java/com/yahoo/document/datatypes/ReferenceFieldValue.java
new file mode 100644
index 00000000000..7480d42a405
--- /dev/null
+++ b/document/src/main/java/com/yahoo/document/datatypes/ReferenceFieldValue.java
@@ -0,0 +1,104 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.document.datatypes;
+
+import com.yahoo.document.DataType;
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.Field;
+import com.yahoo.document.ReferenceDataType;
+import com.yahoo.document.serialization.FieldReader;
+import com.yahoo.document.serialization.FieldWriter;
+import com.yahoo.document.serialization.XmlStream;
+
+import java.util.Objects;
+import java.util.Optional;
+
+public class ReferenceFieldValue extends FieldValue {
+
+ private final ReferenceDataType referenceType;
+ private Optional<DocumentId> documentId;
+
+ public ReferenceFieldValue(ReferenceDataType referenceType) {
+ this.referenceType = referenceType;
+ this.documentId = Optional.empty();
+ }
+
+ public ReferenceFieldValue(ReferenceDataType referenceType, DocumentId documentId) {
+ requireIdOfMatchingType(referenceType, documentId);
+ this.referenceType = referenceType;
+ this.documentId = Optional.of(documentId);
+ }
+
+ public static ReferenceFieldValue createEmptyWithType(ReferenceDataType referenceType) {
+ return new ReferenceFieldValue(referenceType);
+ }
+
+ private void requireIdOfMatchingType(ReferenceDataType referenceType, DocumentId id) {
+ final String expectedTypeName = referenceType.getTargetType().getName();
+ if (!id.getDocType().equals(expectedTypeName)) {
+ throw new IllegalArgumentException(String.format(
+ "Can't assign document ID '%s' (of type '%s') to reference of document type '%s'",
+ id, id.getDocType(), expectedTypeName));
+ }
+ }
+
+ @Override
+ public DataType getDataType() {
+ return referenceType;
+ }
+
+ public Optional<DocumentId> getDocumentId() {
+ return documentId;
+ }
+
+ public void setDocumentId(DocumentId documentId) {
+ this.documentId = Optional.of(documentId);
+ }
+
+ @Override
+ public void printXml(XmlStream xml) {
+ // TODO do we need/want this?
+ }
+
+ @Override
+ public void clear() {
+ this.documentId = Optional.empty();
+ }
+
+ @Override
+ public void assign(Object o) {
+ if (o == null) {
+ clear();
+ } else if (o instanceof DocumentId) {
+ this.documentId = Optional.of((DocumentId)o);
+ } else {
+ throw new IllegalArgumentException(String.format(
+ "Can't assign value of type '%s' to field of type '%s'. Expected value of type '%s'",
+ o.getClass().getName(), getClass().getName(), DocumentId.class.getName()));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ ReferenceFieldValue that = (ReferenceFieldValue) o;
+ return Objects.equals(referenceType, that.referenceType) &&
+ Objects.equals(documentId, that.documentId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), referenceType, documentId);
+ }
+
+ @Override
+ public void serialize(Field field, FieldWriter writer) {
+
+ }
+
+ @Override
+ public void deserialize(Field field, FieldReader reader) {
+
+ }
+}
diff --git a/document/src/test/java/com/yahoo/document/ReferenceDataTypeTestCase.java b/document/src/test/java/com/yahoo/document/ReferenceDataTypeTestCase.java
new file mode 100644
index 00000000000..3b24af860ad
--- /dev/null
+++ b/document/src/test/java/com/yahoo/document/ReferenceDataTypeTestCase.java
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.document;
+
+import com.yahoo.document.datatypes.ReferenceFieldValue;
+import com.yahoo.document.datatypes.StringFieldValue;
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class ReferenceDataTypeTestCase {
+
+ private static DocumentType createDocumentType(String name) {
+ DocumentType type = new DocumentType(name);
+ type.addField(new Field("foo", DataType.STRING));
+ return type;
+ }
+
+ @Test
+ public void parameters_are_propagated_to_base_data_type() {
+ DocumentType docType = createDocumentType("bjarne");
+ ReferenceDataType refType = new ReferenceDataType(docType, 1234);
+ assertEquals("Reference<bjarne>", refType.getName());
+ assertEquals(1234, refType.getId());
+ assertEquals(docType, refType.getTargetType());
+ }
+
+ @Test
+ public void empty_reference_field_value_instance_can_be_created_from_type() {
+ ReferenceDataType refType = new ReferenceDataType(createDocumentType("foo"), 123);
+ ReferenceFieldValue fv = refType.createFieldValue();
+ assertNotNull(fv);
+ assertEquals(refType, fv.getDataType());
+ }
+
+ @Test
+ public void reference_data_type_has_reference_field_value_class() {
+ ReferenceDataType refType = new ReferenceDataType(createDocumentType("foo"), 123);
+ assertEquals(ReferenceFieldValue.class, refType.getValueClass());
+ }
+
+ private static class MultiTypeFixture {
+ final DocumentType docType = createDocumentType("bar");
+ final ReferenceDataType refType = new ReferenceDataType(docType, 123);
+ final ReferenceDataType refTypeClone = new ReferenceDataType(docType, 123);
+ final ReferenceDataType typeWithDifferentId = new ReferenceDataType(docType, 456);
+ // Technically this shouldn't be possible, right? But test it anyway.
+ final ReferenceDataType typeWithDifferentDocType = new ReferenceDataType(createDocumentType("stuff"), 123);
+ }
+
+ @Test
+ public void equals_checks_document_type_and_type_id() {
+ final MultiTypeFixture fixture = new MultiTypeFixture();
+
+ // Note: the default DataType.equals method actually satisfies this, since we already
+ // give it a type-parameterized name and id
+ assertFalse(fixture.refType.equals(null));
+ assertFalse(fixture.refType.equals(DataType.STRING));
+ assertFalse(fixture.refType.equals(fixture.typeWithDifferentId));
+ assertFalse(fixture.refType.equals(fixture.typeWithDifferentDocType));
+ assertTrue(fixture.refType.equals(fixture.refType));
+ assertTrue(fixture.refType.equals(fixture.refTypeClone));
+ }
+
+ @Test
+ public void type_value_compatibility_checks_target_type() {
+ final MultiTypeFixture fixture = new MultiTypeFixture();
+
+ assertFalse(fixture.refType.isValueCompatible(null));
+ assertFalse(fixture.refType.isValueCompatible(new StringFieldValue("baz")));
+ assertFalse(fixture.refType.isValueCompatible(fixture.typeWithDifferentId.createFieldValue()));
+ assertFalse(fixture.refType.isValueCompatible(fixture.typeWithDifferentDocType.createFieldValue()));
+ assertTrue(fixture.refType.isValueCompatible(fixture.refType.createFieldValue()));
+ assertTrue(fixture.refType.isValueCompatible(fixture.refTypeClone.createFieldValue()));
+ }
+
+}
diff --git a/document/src/test/java/com/yahoo/document/datatypes/ReferenceFieldValueTestCase.java b/document/src/test/java/com/yahoo/document/datatypes/ReferenceFieldValueTestCase.java
new file mode 100644
index 00000000000..45585e7d874
--- /dev/null
+++ b/document/src/test/java/com/yahoo/document/datatypes/ReferenceFieldValueTestCase.java
@@ -0,0 +1,145 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.document.datatypes;
+
+import com.yahoo.document.DataType;
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.DocumentType;
+import com.yahoo.document.Field;
+import com.yahoo.document.ReferenceDataType;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class ReferenceFieldValueTestCase {
+
+ // TODO de-dupe
+ private static DocumentType createDocumentType(String name) {
+ DocumentType type = new DocumentType(name);
+ type.addField(new Field("foo", DataType.STRING));
+ return type;
+ }
+
+ private static ReferenceDataType createReferenceType(String documentTypeName, int id) {
+ return new ReferenceDataType(createDocumentType(documentTypeName), id);
+ }
+
+ private static ReferenceDataType referenceTypeFoo() {
+ return createReferenceType("foo", 1234);
+ }
+
+ private static ReferenceDataType referenceTypeFooDifferentId() {
+ return createReferenceType("foo", 5678);
+ }
+
+ private static ReferenceDataType referenceTypeBar() {
+ return createReferenceType("bar", 7654);
+ }
+
+ private static DocumentId docId(String idString) {
+ return new DocumentId(idString);
+ }
+
+ @Test
+ public void default_constructed_reference_is_empty_and_bound_to_type() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo());
+ assertFalse(value.getDocumentId().isPresent());
+ assertEquals(referenceTypeFoo(), value.getDataType());
+ }
+
+ @Test
+ public void factory_method_creates_empty_reference_bound_to_type() {
+ ReferenceFieldValue value = ReferenceFieldValue.createEmptyWithType(referenceTypeFoo());
+ assertFalse(value.getDocumentId().isPresent());
+ assertEquals(referenceTypeFoo(), value.getDataType());
+ }
+
+ @Test
+ public void reference_can_be_constructed_with_id() {
+ DocumentId id = docId("id:ns:foo::itsa-me");
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo(), id);
+ assertTrue(value.getDocumentId().isPresent());
+ assertEquals(id, value.getDocumentId().get());
+ }
+
+ @Test
+ public void can_explicitly_set_new_id_for_existing_reference() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo());
+ DocumentId newId = docId("id:ns:foo::wario-time");
+ value.setDocumentId(newId);
+ assertTrue(value.getDocumentId().isPresent());
+ assertEquals(newId, value.getDocumentId().get());
+ }
+
+ @Test
+ public void can_assign_new_id_for_existing_reference() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo());
+ DocumentId newId = docId("id:ns:foo::wario-time");
+ value.assign(newId);
+ assertTrue(value.getDocumentId().isPresent());
+ assertEquals(newId, value.getDocumentId().get());
+ }
+
+ // This is legacy behaviour and does not smell entirely nice.
+ @Test
+ public void assigning_null_implies_clearing_id() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo());
+ value.assign(null);
+ assertFalse(value.getDocumentId().isPresent());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void assigning_non_reference_field_value_instance_throws_exception() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo());
+ value.assign("nope!");
+ }
+
+ @Test
+ public void reference_value_can_be_cleared() {
+ ReferenceFieldValue value = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::yoshi-egg-feast"));
+ value.clear();
+ assertFalse(value.getDocumentId().isPresent());
+ }
+
+ @Test
+ public void references_with_different_type_ids_are_not_equal() {
+ ReferenceFieldValue lhs = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::toad"));
+ ReferenceFieldValue rhs = new ReferenceFieldValue(referenceTypeFooDifferentId(), docId("id:ns:foo::toad"));
+ assertNotEquals(lhs, rhs);
+ }
+
+ @Test
+ public void references_with_different_document_ids_are_not_equal() {
+ ReferenceFieldValue lhs = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::peach"));
+ ReferenceFieldValue rhs = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::bowser"));
+ assertNotEquals(lhs, rhs);
+ }
+
+ @Test
+ public void references_with_same_type_and_id_are_equal() {
+ ReferenceFieldValue lhs = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::toad"));
+ ReferenceFieldValue rhs = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::toad"));
+ assertEquals(lhs, rhs);
+ }
+
+ @Test
+ public void hash_code_takes_type_and_id_into_account() {
+ ReferenceFieldValue fooField = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::toad"));
+ ReferenceFieldValue barField = new ReferenceFieldValue(referenceTypeBar(), docId("id:ns:foo::toad"));
+ ReferenceFieldValue fooFieldWithDifferentId = new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:foo::luigi"));
+
+ // ... with a very high probability:
+ assertNotEquals(fooField.hashCode(), barField.hashCode());
+ assertNotEquals(fooField.hashCode(), fooFieldWithDifferentId.hashCode());
+ assertNotEquals(barField.hashCode(), fooFieldWithDifferentId.hashCode());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void reference_constructor_requires_that_id_has_same_document_type_as_data_type() {
+ new ReferenceFieldValue(referenceTypeFoo(), docId("id:ns:bar::mismatch"));
+ }
+
+}