/* * Copyright 2016 Christoph Böhme * * 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.culturegraph.mf.biblio.iso2709; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.nio.charset.StandardCharsets; import org.culturegraph.mf.commons.StringUtil; import org.culturegraph.mf.framework.FormatException; import org.junit.Before; import org.junit.Test; /** * Tests for class {@link RecordBuilder}. * * @author Christoph Böhme * */ public final class RecordBuilderTest { private RecordFormat format; private RecordBuilder builder; @Before public void createSystemUnderTest() { format = RecordFormat.create() .withIndicatorLength(2) .withIdentifierLength(2) .withFieldStartLength(3) .withFieldLengthLength(2) .withImplDefinedPartLength(2) .build(); builder = new RecordBuilder(format); } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfFormatIsNull() { new RecordBuilder(null); // Exception expected } @Test public void shouldWriteRecordFormatToRecordLabel() { format = RecordFormat.create() .withIndicatorLength(2) .withIdentifierLength(3) .withFieldStartLength(4) .withFieldLengthLength(5) .withImplDefinedPartLength(6) .build(); final RecordBuilder builder = new RecordBuilder(format); final byte[] record = builder.build(); assertEquals("23", asString(record, 10, 12)); assertEquals("546", asString(record, 20, 23)); } @Test public void shouldWriteRecordStatusToRecordLabel() { builder.setRecordStatus('S'); final byte[] record = builder.build(); assertEquals(0x53, record[5]); } @Test public void shouldWriteSpaceIfRecordStatusNotSet() { final byte[] record = builder.build(); assertEquals(0x20, record[5]); } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfRecordStatusIsNot7BitAscii() { builder.setRecordStatus('\u00df'); // Exception expected } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfRecordStatusIsInformationSeparator() { builder.setRecordStatus('\u001e'); // Exception expected } @Test public void shouldWriteImplCodesToRecordLabel() { builder.setImplCodes(asChars("IMPL")); final byte[] record = builder.build(); assertEquals(0x49, record[6]); assertEquals(0x4d, record[7]); assertEquals(0x50, record[8]); assertEquals(0x4c, record[9]); } @Test public void shouldWriteSpacesIfImplCodesNotSet() { final byte[] record = builder.build(); assertEquals(0x20, record[6]); assertEquals(0x20, record[7]); assertEquals(0x20, record[8]); assertEquals(0x20, record[9]); } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfLengthOfImplCodesIsLessThanFour() { builder.setImplCodes(asChars("123")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfLengthOfImplCodesIsGreaterThanFour() { builder.setImplCodes(asChars("12345")); // Exception expected } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfImplCodesAreNot7BitAscii() { builder.setImplCodes(asChars("12\u00df4")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfImplCodesIsNull() { builder.setImplCodes(null); // Exception expected } @Test public void shouldWriteSystemCharsToRecordLabel() { builder.setSystemChars(asChars("USC")); final byte[] record = builder.build(); assertEquals(0x55, record[17]); assertEquals(0x53, record[18]); assertEquals(0x43, record[19]); } @Test public void shouldWriteSpacesIfSystemCharsNotSet() { final byte[] record = builder.build(); assertEquals(0x20, record[17]); assertEquals(0x20, record[18]); assertEquals(0x20, record[19]); } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfLengthOfSystemCharsIsLessThanThree() { builder.setSystemChars(asChars("12")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfLengthOfSystemCharsIsGreaterThanThree() { builder.setSystemChars(asChars("1234")); // Exception expected } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfSystemCharsAreNot7BitAscii() { builder.setSystemChars(asChars("1\u00df3")); // Exception expected } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfSystemCharIsInformationSeparator() { builder.setSystemChars(asChars("1\u001e3")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionIfSystemCharsIsNull() { builder.setSystemChars(null); // Exception expected } @Test public void shouldWriteReserverdCharToRecordLabel() { builder.setReservedChar('R'); final byte[] record = builder.build(); assertEquals(0x52, record[23]); } @Test public void shouldWriteSpaceIfReservedCharNotSet() { final byte[] record = builder.build(); assertEquals(0x20, record[23]); } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReservedCharIsNot7BitAscii() { builder.setReservedChar('\u00df'); // Exception expected } @Test(expected=IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReservedCharIsInformationSeparator() { builder.setReservedChar('\u001d'); // Exception expected } @Test public void shouldAppendReferenceFieldToRecord() { builder.appendReferenceField(asChars("002"), asChars("IM"), "Value"); final byte[] record = builder.build(); assertEquals("00206000IM", asString(record, 24, 34)); assertEquals("Value\u001e", asString(record, 35, 41)); } @Test public void shouldWriteTwoDirectoryEntriesForReferenceFieldsWithLongValue() { final String longValue = StringUtil.repeatChars('A', 110); builder.appendReferenceField(asChars("002"), asChars("IM"), longValue); final byte[] record = builder.build(); assertEquals("00200000", asString(record, 24, 32)); assertEquals("00212099", asString(record, 34, 42)); assertEquals(longValue + '\u001e', asString(record, 45, 156)); } @Test public void shouldFillImplDefinedPartOfReferenceFieldWithSpacesIfNotProvided() { builder.appendReferenceField(asChars("002"), "Value"); final byte[] record = builder.build(); assertEquals("00206000 ", asString(record, 24, 34)); } @Test public void shouldAppendReferenceFieldWithoutImplDefinedPart() { format = RecordFormat.createFrom(format) .withImplDefinedPartLength(0) .build(); final RecordBuilder builder = new RecordBuilder(format); builder.appendReferenceField(asChars("002"), "Value"); final byte[] record = builder.build(); assertEquals("00206000\u001e", asString(record, 24, 33)); } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfStartOfReferenceFieldIsNotInAddressRange() { final String longValue = StringUtil.repeatChars('A', 1000); builder.appendReferenceField(asChars("002"), asChars("IM"), longValue); builder.appendReferenceField(asChars("003"), asChars("IM"), "would not fit"); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfStartOfLastPartOfReferenceFieldIsNotInAddressRange() { final String tooLongValue = StringUtil.repeatChars('A', 1100); builder.appendReferenceField(asChars("002"), asChars("IM"), tooLongValue); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfReferenceFieldTagLengthIsNotThree() { builder.appendReferenceField(asChars("0020"), asChars("IM"), "Value"); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfTagDoesNotStartWithTwoZeros() { builder.appendReferenceField(asChars("012"), asChars("IM"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldTagIsNull() { builder.appendReferenceField(null, asChars("IM"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldImplDefinedPartLengthDoesNotMatchFormat() { builder.appendReferenceField(asChars("002"), asChars("IMP"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldImplDefinedPartIsNot7BitAscii() { builder.appendReferenceField(asChars("002"), asChars("I\u00df"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldImplDefinedPartIsInformationSeparator() { builder.appendReferenceField(asChars("002"), asChars("I\u001d"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldImplDefinedPartIsNull() { builder.appendReferenceField(asChars("002"), null, "value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfReferenceFieldValueIsNull() { builder.appendReferenceField(asChars("002"), asChars("IM"), null); // Exception expected } @Test public void shouldAppendDataFieldToRecord() { builder.startDataField(asChars("010"), asChars("IN"), asChars("IM")); builder.endDataField(); final byte[] record = builder.build(); assertEquals("01003000IM", asString(record, 24, 34)); assertEquals("IN\u001e", asString(record, 35, 38)); } @Test public void shouldFillImplDefinedPartOfDataFieldWithSpacesIfNotProvided() { builder.startDataField(asChars("012"), asChars("IN")); builder.endDataField(); final byte[] record = builder.build(); assertEquals("01203000 ", asString(record, 24, 34)); } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfImplDefinedPartIsNot7BitAscii() { builder.startDataField(asChars("012"), asChars("IN"), asChars("I\u00df")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfImplDefinedPartIsInformationSeparator() { builder.startDataField(asChars("012"), asChars("IN"), asChars("I\u001f")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIndicatorsAreNot7BitAscii() { builder.startDataField(asChars("012"), asChars("I\u00df")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIndicatorsIsInformationSeparator() { builder.startDataField(asChars("012"), asChars("I\u001e")); // Exception expected } @Test public void shouldFillIndicatorsOfDataFieldWithSpacesIfNotProvided() { builder.startDataField(asChars("012")); builder.endDataField(); final byte[] record = builder.build(); assertEquals(" \u001e", asString(record, 35, 38)); } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfDataFieldTagIsNull() { builder.startDataField(null, asChars("IN"), asChars("IM")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfDataFieldIndicatorsIsNull() { builder.startDataField(asChars("020"), null, asChars("IM")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfDataFieldImplDefinedPartIsNull() { builder.startDataField(asChars("020"), asChars("IN"), null); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfDataFieldTagLengthIsNotThree() { builder.startDataField(asChars("01"), asChars("IN"), asChars("IM")); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfDataFieldTagStartsWithTwoZeros() { builder.startDataField(asChars("002"), asChars("IN"), asChars("IM")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfDataFieldImplDefinedPartLengthDoesNotMatchFormat() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IMP")); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIndicatorsLenghtDoesNotMatchformat() { builder.startDataField(asChars("020"), asChars("INS"), asChars("IM")); // Exception expected } @Test(expected = IllegalStateException.class) public void shouldThrowIllegalStateExceptionIfAppendReferenceFieldIsCalledWhileAppendingDataField() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendReferenceField(asChars("002"), asChars("IM"), "Value"); // Exception expected } @Test(expected = IllegalStateException.class) public void shouldThrowExceptionIfBuildIsCalledWhileAppendingDataField() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.build(); // Exception expected } @Test(expected=IllegalStateException.class) public void shouldNotAllowAppendingReferenceFieldAfterFinishingDataField() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.endDataField(); builder.appendReferenceField(asChars("002"), asChars("IM"), "Value"); // Exception expected } @Test public void shouldAppendSubfieldsToRecord() { builder.startDataField(asChars("020"), asChars("IN"), asChars(" ")); builder.appendSubfield(asChars("A"), "val1"); builder.appendSubfield(asChars("B"), "val2"); builder.endDataField(); final byte[] record = builder.build(); assertEquals("02015000 ", asString(record, 24, 34)); assertEquals("\u001fAval1\u001fBval2\u001e", asString(record, 37, 50)); } @Test public void shouldCountStringLengthInBytes() { builder.startDataField(asChars("020"), asChars("IN"), asChars(" ")); // Letter ü requires two bytes when encoding in UTF-8: builder.appendSubfield(asChars("A"), "über"); builder.endDataField(); final byte[] record = builder.build(); assertEquals("02010000 ", asString(record, 24, 34)); assertEquals("\u001fAüber\u001e", asString(record, 37, 45)); } @Test public void shouldWriteTwoDirectoryEntriesForAFieldWithLongSubfields() { final String longValue1 = StringUtil.repeatChars('A', 60); final String longValue2 = StringUtil.repeatChars('B', 60); builder.startDataField(asChars("020"), asChars("IN"), asChars(" ")); builder.appendSubfield(asChars("A"), longValue1); builder.appendSubfield(asChars("B"), longValue2); builder.endDataField(); final byte[] record = builder.build(); assertEquals("02000000 ", asString(record, 24, 34)); assertEquals("02028099 ", asString(record, 34, 44)); } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIdentifierIsNull() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(null, "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIdentifierIsNot7BitAscii() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("\u00df"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIdentifierIsInformationSeparator() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("\u001d"), "Value"); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfDataFieldValueIsNull() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("A"), null); // Exception expected } @Test(expected = FormatException.class) public void shouldThrowFormatExceptionIfLastPartOfDataFieldIsNotInAddressRange() { final String longValue = StringUtil.repeatChars('A', 1100); builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("A"), longValue); builder.appendSubfield(asChars("B"), "Value"); builder.endDataField(); // Exception expected } @Test public void shouldLeaveRecordInACleanStateIfAppendingDataFieldFailed() { boolean exceptionThrown = false; final String longValue = StringUtil.repeatChars('A', 1100); builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("A"), longValue); builder.appendSubfield(asChars("B"), "Value"); try { builder.endDataField(); } catch (final FormatException e) { exceptionThrown = true; } final byte[] record = builder.build(); assertTrue(exceptionThrown); assertEquals("\u001e\u001d", asString(record, 24, 26)); } @Test(expected = IllegalStateException.class) public void shouldThrowIllegalStateExceptionIfAppendSubfieldIsNotCalledWithinAppendFieldSequence() { builder.appendSubfield(asChars("A"), "Value"); // Exception expected } @Test(expected = IllegalStateException.class) public void shouldThrowIllegalStateExceptionIfStartAppendFieldIsCalledTwice() { builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); builder.startDataField(asChars("020"), asChars("IN"), asChars("IM")); // Exception expected } @Test(expected = IllegalStateException.class) public void shouldThrowIllegalStateExceptionIfEndAppendFieldIsNotMatchedByStartAppendField() { builder.endDataField(); // Exception expected } @Test(expected = IllegalArgumentException.class) public void shouldThrowIllegalArgumentExceptionIfIdentifierLengthDoesNotMatchFormat() { builder.startDataField(asChars("200"), asChars("IN"), asChars("IM")); builder.appendSubfield(asChars("12"), "Value"); // Exception expected } @Test public void shouldFillIdentifierWithSpacesIfNotProvided() { builder.startDataField(asChars("200"), asChars("IN"), asChars("IM")); builder.appendSubfield("Value"); builder.endDataField(); final byte[] record = builder.build(); assertEquals("\u001f Value", asString(record, 37, 44)); } @Test public void shouldWriteOnlyIdentifierMarkerIfIdentifierLengthIsOne() { format = RecordFormat.createFrom(format) .withIdentifierLength(1) .build(); final RecordBuilder builder = new RecordBuilder(format); builder.startDataField(asChars("200"), asChars("IN"), asChars("IM")); builder.appendSubfield("Value"); builder.endDataField(); final byte[] record = builder.build(); assertEquals("\u001fValue", asString(record, 37, 43)); } @Test public void shouldWriteNoIdentifierMarkerIfIdentifierLengthIsZero() { format = RecordFormat.createFrom(format) .withIdentifierLength(0) .build(); final RecordBuilder builder = new RecordBuilder(format); builder.startDataField(asChars("200"), asChars("IN"), asChars("IM")); builder.appendSubfield("Ada"); builder.appendSubfield("Lovelace"); builder.endDataField(); final byte[] record = builder.build(); assertEquals("AdaLovelace", asString(record, 37, 48)); } @Test public void baseAddressShouldPointToEndOfDirectory() { final RecordBuilder builder = new RecordBuilder(format); builder.appendReferenceField(asChars("001"), asChars(" "), "value"); final byte[] record = builder.build(); assertEquals("00035", asString(record, 12, 17)); } @Test(expected = FormatException.class) public void shouldThrowExceptionIfBaseAddressIsNotInAddressRange() { format = RecordFormat.createFrom(format) .withFieldLengthLength(9) .withFieldStartLength(9) .withImplDefinedPartLength(9) .build(); final RecordBuilder builder = new RecordBuilder(format); final int dirEntries = Iso2709Constants.MAX_PAYLOAD_LENGTH / (9 * 3 + 3) + 1; for (int i = 0; i < dirEntries; ++i) { builder.appendReferenceField(asChars("002"), asChars("123456789"), ""); } builder.build(); // Exception expected } @Test public void recordLengthShouldMatchLengthOfRecordString() { builder.appendReferenceField(asChars("001"), asChars(" "), "value"); final byte[] record = builder.build(); assertEquals(String.format("%05d", record.length), asString(record, 0, 5)); } @Test(expected = FormatException.class) public void shouldThrowExceptionIfRecordLengthIsExceeded() { format = RecordFormat.createFrom(format) .withFieldLengthLength(9) .build(); final RecordBuilder builder = new RecordBuilder(format); final String longValue = StringUtil.repeatChars('C', 100000); builder.appendReferenceField(asChars("002"), asChars(" "), longValue); builder.build(); // Exception expected } @Test public void shouldEndWithRecordSeparator() { final byte[] record = builder.build(); assertEquals('\u001d', record[record.length - 1]); } @Test public void shouldResetBuilder() { builder.setRecordStatus('S'); builder.setImplCodes(asChars("IMPL")); builder.setSystemChars(asChars("USC")); builder.appendReferenceField(asChars("002"), asChars(" "), "record1"); builder.reset(); final byte[] record = builder.build(); assertEquals(26, record.length); assertEquals(0x20, record[5]); assertEquals(0x20, record[6]); assertEquals(0x20, record[7]); assertEquals(0x20, record[8]); assertEquals(0x20, record[9]); assertEquals(0x20, record[17]); assertEquals(0x20, record[18]); assertEquals(0x20, record[19]); assertEquals(0x20, record[23]); } private char[] asChars(final String value) { return value.toCharArray(); } private String asString(final byte[] record, final int beginIndex, final int endIndex) { return new String(record, beginIndex, endIndex - beginIndex, StandardCharsets.UTF_8); } }