UcteRecordWriter.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * Copyright (c) 2017, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.ucte.network.io;

import com.google.common.math.IntMath;

import java.io.BufferedWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class UcteRecordWriter {

    private enum Alignment {
        RIGHT, LEFT
    }

    private final BufferedWriter writer;

    private final StringBuilder buffer = new StringBuilder();

    private final DecimalFormat numberFormatter = new DecimalFormat();

    UcteRecordWriter(BufferedWriter writer) {
        this.writer = writer;
        initNumberFormatter();
    }

    private void initNumberFormatter() {
        numberFormatter.setGroupingUsed(false);
        numberFormatter.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.US));
        numberFormatter.setMinimumIntegerDigits(1);
        numberFormatter.setMinimumFractionDigits(1);
        numberFormatter.setMaximumFractionDigits(5);
    }

    private void resizeBuffer(int length) {
        int n = length - buffer.length();
        if (n > 0) {
            buffer.append(" ".repeat(n));
        }
    }

    void writeChar(Character value, int index) {
        if (value == null) {
            return;
        }
        resizeBuffer(index + 1);
        buffer.setCharAt(index, value);
    }

    void writeString(String value, int beginIndex, int endIndex) {
        if (value == null) {
            return;
        }
        resizeBuffer(endIndex);
        buffer.replace(beginIndex, endIndex, value);
    }

    private String alignAndTruncate(String str, int strLen, Alignment alignment) {
        String format = String.format(Locale.US, alignment.equals(Alignment.LEFT) ? "%%-%ds" : "%%%ds", strLen);
        String formattedStr = String.format(Locale.US, format, str);
        return formattedStr.substring(0, strLen);
    }

    private int maxLimitInt(int numberOfChars) {
        return IntMath.pow(10, numberOfChars);
    }

    private int minLimitInt(int numberOfChars) {
        return -IntMath.pow(10, numberOfChars - 1);
    }

    // doubles are left aligned, zero padded to fill the field length
    void writeDouble(double value, int beginIndex, int endIndex) {
        if (Double.isNaN(value)) {
            return;
        }
        int fieldLength = endIndex - beginIndex;
        if (value >= maxLimitInt(fieldLength) || value <= minLimitInt(fieldLength)) {
            throw new IllegalArgumentException(String.format("Double value %f does not fit into %d characters", value, fieldLength));
        }
        String fieldStr = alignAndTruncate(numberFormatter.format(value), fieldLength, Alignment.LEFT);
        writeString(fieldStr.replace(' ', '0'), beginIndex, endIndex);
    }

    // integers are right aligned
    void writeInteger(Integer value, int beginIndex, int endIndex) {
        if (value == null) {
            return;
        }
        int fieldLength = endIndex - beginIndex;
        if (value >= maxLimitInt(fieldLength) || value <= minLimitInt(fieldLength)) {
            throw new IllegalArgumentException(String.format("Integer value %d does not fit into %d characters", value, fieldLength));
        }
        String fieldStr = alignAndTruncate(Integer.toString(value), fieldLength, Alignment.RIGHT);
        writeString(fieldStr, beginIndex, endIndex);
    }

    void writeInteger(Integer value, int index) {
        if (value == null) {
            return;
        }
        if (value < 0 || value > 9) {
            throw new UcteIoException("Integer value does not fit into one character");
        }
        writeChar(Integer.toString(value).charAt(0), index);
    }

    void writeEnumOrdinal(Enum<?> value, int index) {
        if (value == null) {
            return;
        }
        if (value.ordinal() > 9) {
            throw new UcteIoException("Enum ordinal cannot fit into one character");
        }
        writeChar(Integer.toString(value.ordinal()).charAt(0), index);
    }

    void writeEnumValue(Enum<?> value, int beginIndex, int endIndex) {
        if (value == null) {
            return;
        }
        int fieldLength = endIndex - beginIndex;
        if (value.name().length() > fieldLength) {
            throw new UcteIoException(String.format("Enum value cannot fit into %d characters", fieldLength));
        }
        writeString(value.name(), beginIndex, endIndex);
    }

    void writeEnumValue(Enum<?> value, int index) {
        if (value == null) {
            return;
        }
        if (value.name().length() > 1) {
            throw new UcteIoException("Enum value cannot fit into one characters");
        }
        writeChar(value.name().charAt(0), index);
    }

    private void flush() throws IOException {
        writer.write(buffer.toString());
        buffer.setLength(0); // reset buffer
    }

    void newLine() throws IOException {
        flush();
        writer.newLine();
    }

}