diff options
author | Tor Brede Vekterli <vekterli@yahoo-inc.com> | 2017-01-20 15:13:02 +0100 |
---|---|---|
committer | Tor Brede Vekterli <vekterli@yahoo-inc.com> | 2017-01-23 11:18:57 +0100 |
commit | 4bc7e514ab6e930bc3bf25371eed40cd2dd1264a (patch) | |
tree | 10d297e375b0e060749ff8b53aaf5ad11ab82d25 | |
parent | dc644db693f8a6857cbb53e46c67aafa8f807828 (diff) |
Add ReferenceDataType and ReferenceFieldValue
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")); + } + +} |