/* * 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.culturegraph.mf.biblio.iso2709.Iso2709Constants.IMPL_CODES_LENGTH; import static org.culturegraph.mf.biblio.iso2709.Iso2709Constants.MAX_PAYLOAD_LENGTH; import static org.culturegraph.mf.biblio.iso2709.Iso2709Constants.RECORD_LABEL_LENGTH; import static org.culturegraph.mf.biblio.iso2709.Iso2709Constants.SYSTEM_CHARS_LENGTH; import static org.culturegraph.mf.biblio.iso2709.Iso2709Constants.TAG_LENGTH; import java.nio.charset.Charset; import java.util.Arrays; import java.util.regex.Pattern; import org.culturegraph.mf.commons.Require; import org.culturegraph.mf.framework.FormatException; /** * Builds records in ISO2709:2008 format. * * @author Christoph Böhme * */ public final class RecordBuilder { private static final char[] EMPTY_IDENTIFIER = new char[0]; private static final char[] ID_FIELD_TAG = { '0', '0', '1' }; private static final Pattern REFERENCE_FIELD_TAG_PATTERN = Pattern.compile( "^00[1-9a-zA-Z]$"); private static final Pattern DATA_FIELD_TAG_PATTERN = Pattern.compile( "^(0[1-9a-zA-Z][0-9a-zA-Z])|([1-9a-zA-Z][0-9a-zA-Z]{2})$"); private final LabelBuilder label; private final DirectoryBuilder directory; private final FieldsBuilder fields; private final int indicatorLength; private final int identifierLength; private final int implDefinedPartLength; private final char[] defaultImplDefinedPart; private final char[] defaultIndicators; private final char[] defaultIdentifier; private final char[] tag = new char[TAG_LENGTH]; private final char[] implDefinedPart; private AppendState appendState; private int fieldStart; public RecordBuilder(final RecordFormat recordFormat) { Require.notNull(recordFormat); label = new LabelBuilder(recordFormat); directory = new DirectoryBuilder(recordFormat); fields = new FieldsBuilder(recordFormat, MAX_PAYLOAD_LENGTH); indicatorLength = recordFormat.getIndicatorLength(); identifierLength = recordFormat.getIdentifierLength(); implDefinedPartLength = recordFormat.getImplDefinedPartLength(); defaultImplDefinedPart = arrayOfNSpaceChars(implDefinedPartLength); defaultIndicators = arrayOfNSpaceChars(indicatorLength); if (identifierLength > 1) { defaultIdentifier = arrayOfNSpaceChars(identifierLength - 1); } else { defaultIdentifier = EMPTY_IDENTIFIER; } implDefinedPart = new char[implDefinedPartLength]; appendState = AppendState.ID_FIELD; } private char[] arrayOfNSpaceChars(final int count) { final char[] chars = new char[count]; Arrays.fill(chars, ' '); return chars; } public void setCharset(final Charset charset) { fields.setCharset(Require.notNull(charset)); } public Charset getCharset() { return fields.getCharset(); } public void setRecordStatus(final char recordStatus) { require7BitAscii(recordStatus); label.setRecordStatus(recordStatus); } public void setImplCodes(final char[] implCodes) { Require.notNull(implCodes); Require.that(implCodes.length == IMPL_CODES_LENGTH); for (final char implCode : implCodes) { Require.that(implCode < Iso646Constants.MAX_CHAR_CODE); } label.setImplCodes(implCodes); } public void setImplCode(final int index, final char value) { Require.that(0 <= index && index < IMPL_CODES_LENGTH); Require.that(value < Iso646Constants.MAX_CHAR_CODE); label.setImplCode(index, value); } public void setSystemChars(final char[] systemChars) { Require.notNull(systemChars); Require.that(systemChars.length == SYSTEM_CHARS_LENGTH); require7BitAscii(systemChars); label.setSystemChars(systemChars); } public void setSystemChar(final int index, final char value) { Require.that(0 <= index && index < IMPL_CODES_LENGTH); Require.that(value < Iso646Constants.MAX_CHAR_CODE); label.setSystemChar(index, value); } public void setReservedChar(final char reservedChar) { require7BitAscii(reservedChar); label.setReservedChar(reservedChar); } public void appendIdentifierField(final String value) { appendIdentifierField(defaultImplDefinedPart,value); } public void appendIdentifierField(final char[] implDefinedPart, final String value) { requireNotInDataField(); requireNotAppendingReferenceFields(); requireNotAppendingDataFields(); if (appendState != AppendState.ID_FIELD) { throw new IllegalStateException("no id field allowed"); } appendReferenceField(ID_FIELD_TAG, implDefinedPart, value); } public void appendReferenceField(final char[] tag, final String value) { appendReferenceField(tag, defaultImplDefinedPart, value); } public void appendReferenceField(final char[] tag, final char[] implDefinedPart, final String value) { requireNotInDataField(); requireNotAppendingDataFields(); Require.notNull(tag); Require.notNull(implDefinedPart); Require.that(implDefinedPart.length == implDefinedPartLength); require7BitAscii(implDefinedPart); Require.notNull(value); checkValidReferenceFieldTag(tag); final int fieldStart = fields.startField(); fields.appendValue(value); final int fieldEnd = fields.endField(); try { directory.addEntries(tag, implDefinedPart, fieldStart, fieldEnd); } catch (final FormatException e) { fields.undoLastField(); throw e; } appendState = AppendState.REFERENCE_FIELD; } private void checkValidReferenceFieldTag(final char[] tag) { if (!REFERENCE_FIELD_TAG_PATTERN.matcher(String.valueOf(tag)).matches()) { throw new FormatException("invalid tag format for reference field"); } } public void startDataField(final char[] tag) { startDataField(tag, defaultIndicators); } public void startDataField(final char[] tag, final char[] indicators) { startDataField(tag, indicators, defaultImplDefinedPart); } public void startDataField(final char[] tag, final char[] indicators, final char[] implDefinedPart) { requireNotInDataField(); Require.notNull(tag); Require.notNull(indicators); Require.that(indicators.length == indicatorLength); require7BitAscii(indicators); Require.notNull(implDefinedPart); Require.that(implDefinedPart.length == implDefinedPartLength); require7BitAscii(implDefinedPart); checkValidDataFieldTag(tag); copyArray(tag, this.tag); copyArray(implDefinedPart, this.implDefinedPart); fieldStart = fields.startField(indicators); appendState = AppendState.IN_DATA_FIELD; } private void checkValidDataFieldTag(final char[] tag) { if (!DATA_FIELD_TAG_PATTERN.matcher(String.valueOf(tag)).matches()) { throw new FormatException("invalid tag format for data field"); } } private void copyArray(final char[] source, final char[] destination) { System.arraycopy(source, 0, destination, 0, destination.length); } public void endDataField() { requireInDataField(); final int fieldEnd = fields.endField(); appendState = AppendState.DATA_FIELD; try { directory.addEntries(tag, implDefinedPart, fieldStart, fieldEnd); } catch (final FormatException e) { fields.undoLastField(); throw e; } } public void appendSubfield(final String value) { requireInDataField(); Require.notNull(value); fields.appendSubfield(defaultIdentifier, value); } public void appendSubfield(final char[] identifier, final String value) { requireInDataField(); Require.notNull(identifier); require7BitAscii(identifier); Require.that(identifier.length + 1 == identifierLength); Require.notNull(value); fields.appendSubfield(identifier, value); } public byte[] build() { requireNotInDataField(); final int baseAddress = RECORD_LABEL_LENGTH + directory.length(); final int recordLength = baseAddress + fields.length(); label.setBaseAddress(baseAddress); label.setRecordLength(recordLength); final byte[] recordBuffer = new byte[recordLength]; label.copyToBuffer(recordBuffer); directory.copyToBuffer(recordBuffer, RECORD_LABEL_LENGTH); fields.copyToBuffer(recordBuffer, baseAddress); return recordBuffer; } private void requireNotAppendingReferenceFields() { if (appendState == AppendState.REFERENCE_FIELD) { throw new IllegalStateException("must not be appending reference fields"); } } private void requireNotAppendingDataFields() { if (appendState == AppendState.DATA_FIELD) { throw new IllegalStateException("must not be appending data fields"); } } private void requireInDataField() { if (appendState != AppendState.IN_DATA_FIELD) { throw new IllegalStateException(); } } private void requireNotInDataField() { if (appendState == AppendState.IN_DATA_FIELD) { throw new IllegalStateException("must not be in field"); } } private void require7BitAscii(final char[] charArray) { for (final char charCode : charArray) { require7BitAscii(charCode); } } private void require7BitAscii(final char charCode) { Require.that(charCode <= Iso646Constants.MAX_CHAR_CODE); Require.that(charCode != Iso646Constants.INFORMATION_SEPARATOR_1); Require.that(charCode != Iso646Constants.INFORMATION_SEPARATOR_2); Require.that(charCode != Iso646Constants.INFORMATION_SEPARATOR_3); } public void reset() { label.reset(); directory.reset(); fields.reset(); appendState = AppendState.ID_FIELD; } @Override public String toString() { return "label: " + label.toString() + "\n" + "directory: " + directory.toString() + "\n" + "fields: " + fields.toString(); } private enum AppendState { ID_FIELD, REFERENCE_FIELD, DATA_FIELD, IN_DATA_FIELD } }