Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Code Block
languagejava

package nl.wldelft.fews.pi;

import com.sun.org.apache.xml.internal.serialize.OutputFormatnl.wldelft.util.Arguments;
import nl.wldelft.util.BinaryUtils;
import nl.wldelft.util.Clasz;
import comnl.sunwldelft.org.apache.xml.internal.serialize.XMLSerializerutil.FastDateFormat;
import nl.wldelft.util.FileUtils;
import nl.wldelft.util.FastDateFormatMathUtils;
import nl.wldelft.util.FileUtilsNumberType;
import nl.wldelft.util.IOUtilsObjectUtils;
import nl.wldelft.util.Period;
import nl.wldelft.util.TimeUnitProperties;
import nl.wldelft.util.io.FileSerializer.TextUtils;
import nl.wldelft.util.TimeZoneUtils;
import nl.wldelft.util.iofunction.LineWriterSupplier;
import nl.wldelft.util.io.LittleEndianDataOutputStreamVirtualOutputDir;
import nl.wldelft.util.io.TextSerializerVirtualOutputDirConsumer;
import nl.wldelft.util.timeseriesio.TimeSeriesContentXmlChunkedSerializer;
import nl.wldelft.util.timeseries.TimeSeriesHeaderProduct;
import nl.wldelft.util.timeseries.TimeStepProductInfo;
import orgnl.xmlwldelft.util.saxtimeseries.ContentHandlerStatistics;
import orgnl.wldelft.xmlutil.saxtimeseries.SAXExceptionTimeSeriesContent;
import orgnl.xmlwldelft.saxutil.helperstimeseries.AttributesImplTimeSeriesHeader;

import java.io.BufferedOutputStreamnl.wldelft.util.timeseries.TimeStep;
import java.io.Filenl.wldelft.util.timeseries.TimesOfDayDaylightSavingTimeStep;
import java.io.FileOutputStreamnl.wldelft.util.timeseries.TimesOfDayTimeStep;
import java.io.IOExceptionnl.wldelft.util.timeseries.TimesOfHourTimeStep;
import java.io.OutputStreamnl.wldelft.util.timeseries.ValueSource;

import javajavax.xml.iostream.OutputStreamWriterXMLStreamException;

public class PiTimeSeriesSerializer implements FileSerializer<TimeSeriesContent>, TextSerializer<TimeSeriesContent> {
    public enum EventDestination {XML_EMBEDDED, SEPARATE_BINARY_FILE, ONLY_HEADERS}

    private EventDestination eventDestination = EventDestination.XML_EMBEDDED;
    private PiVersion version = PiVersion.VERSION_1_2;

    private final FastDateFormat dateFormat = new FastDateFormat("yyyy-MM-dd");
    private final FastDateFormat timeFormat = new FastDateFormat("HH:mm:ss");
    private TimeSeriesContent timeSeriesContent = null;
    private ContentHandler xmlContentHandler = null;
    private LittleEndianDataOutputStream binaryOutputSteam = null;

    private final AttributesImpl attributesBuffer = new AttributesImpl();

    public PiTimeSeriesSerializer() {import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;

public class PiTimeSeriesSerializer implements XmlChunkedSerializer<TimeSeriesContent>, VirtualOutputDirConsumer {
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_2 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_2);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_3 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_3);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_4 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_4);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_5 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_5);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_6 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_6);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_7 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_7);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_8 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_8);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_9 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_9);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_10 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_10);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_11 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_11);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_12 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_12);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_13 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_13);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_14 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_14);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_15 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_15);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_16 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_16);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_17 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_17);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_18 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_18);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_19 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_19);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_20 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_20);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_21 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_21);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_22 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_22);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_23 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_23);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_24 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_24);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_25 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_25);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_26 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_26);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_27 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_27);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_28 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_28);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_29 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_29);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_30 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_30);
    public static final Supplier<PiTimeSeriesSerializer, Error> VERSION_1_31 = () -> new PiTimeSeriesSerializer(PiVersion.VERSION_1_31);

    private static final int BUFFER_SIZE = 2048;
    private TimeZone timeZone = null;

    private String missingValueText = "NaN";
    private float missingValue = Float.NaN;
    private String virtualFileName = null;
    private boolean skipEmptyTimeSeries = false;

    public void setSkipEmptyTimeSeries(boolean skipEmptyTimeSeries) {
        this.skipEmptyTimeSeries = skipEmptyTimeSeries;
    }

    public void setUseMilliseconds (boolean useMilliseconds) {this.useMilliseconds = useMilliseconds; }

    public enum EventDestination {
        XML_EMBEDDED, SEPARATE_BINARY_FILE, ONLY_HEADERS
    }

    public enum EnsembleMemberFormat {
        INDEX, ID, HIDE
    }

    private EventDestination eventDestination = EventDestination.XML_EMBEDDED;
    private PiVersion version = PiVersion.VERSION_1_2;
    private EnsembleMemberFormat ensembleMemberFormat = EnsembleMemberFormat.INDEX;

    private boolean anyTimeSeriesWritten = false;
    private FastDateFormat dateFormat = null;
    private FastDateFormat timeFormat = null;
    private boolean useMilliseconds = false;
    private TimeSeriesContent timeSeriesContent = null;
    private XMLStreamWriter writer = null;
    private VirtualOutputDir virtualOutputDir = null;
    private OutputStream binaryOutputSteam = null;
    private byte[] byteBuffer = null;
    private char[] charBuffer = null;
    private float[] floatBuffer = null;
    private int bufferPos = 0;
    private long[] cachedTimes = new long[6];
    private String[] cachedDateStrings = new String[6];
    private String[] cachedTimeStrings = new String[6];
    private int cachedSeconds = -1;
    private String cachedSecondsString = null;
    private float[][] domainAxesValues = null;
    private float[][] newDomainValues = null;

    public PiTimeSeriesSerializer() {
    }

    public PiTimeSeriesSerializer(PiVersion version) {
        this.version = version;
    }

    public void setEventDestination(EventDestination eventDestination) {
        this.eventDestination = eventDestination;
    }

    public PiVersion getVersion() {
        return version;
    }

    public void setVersion(PiVersion version) {
        Arguments.require.notNull(version);
        this.version = version;
    }

    public void setEnsembleMemberFormat(EnsembleMemberFormat ensembleMemberFormat) {
        Arguments.require.notNull(ensembleMemberFormat);
        this.ensembleMemberFormat = ensembleMemberFormat;
    }

    @Override
    public void setVirtualOutputDir(VirtualOutputDir virtualOutputDir) {
        this.virtualOutputDir = virtualOutputDir;
    }

    @Override
    public void serializeStartDocument(XMLStreamWriter streamWriter, String virtualFileName) throws Exception {
        anyTimeSeriesWritten = false;
        this.virtualFileName = virtualFileName;
        this.writer = streamWriter;
        if (ensembleMemberFormat == EnsembleMemberFormat.ID && version.getIntId() < PiVersion.VERSION_1_10.getIntId()) {
            throw new IOException("ensembleMemberId not supported for pi version " + version);
        }

        writeStartDocument();

        String binFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin");
        if (virtualOutputDir != null) virtualOutputDir.delete(binFileName);

        if (eventDestination != EventDestination.SEPARATE_BINARY_FILE) return;
        if (virtualOutputDir == null)
            throw new IllegalStateException("virtualOutputDir == null");

        binaryOutputSteam = virtualOutputDir.getOutputStream(binFileName);
        bufferPos = 0;
        if (byteBuffer != null) return;
        byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE];
        floatBuffer = new float[BUFFER_SIZE];
    }

    public void writeStartDocument() throws XMLStreamException {
        writer.writeStartDocument();
        writer.writeStartElement("TimeSeries");
        writer.setDefaultNamespace("http://www.wldelft.nl/fews/PI");
        writer.writeNamespace("", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
//        writer.writeAttribute("xmlns", "http://www.wldelft.nl/fews/PI");
        writer.writeAttribute("xsi:schemaLocation", PiSchemaLocations.get("pi_timeseries.xsd"));
        writer.writeAttribute("version", version.toString());
        if (version.getIntId() >= PiVersion.VERSION_1_19.getIntId()) {
            writer.writeAttribute("xmlns:fs", "http://www.wldelft.nl/fews/fs");
        }
    }

    @Override
    public void serializeContent(TimeSeriesContent timeSeriesContent) throws Exception {
        if (timeSeriesContent.getTimeSeriesCount() == 0) return;
        this.timeSeriesContent = timeSeriesContent;
        timeSeriesContent.setRequireEnsembleMemberIndices(ensembleMemberFormat == EnsembleMemberFormat.INDEX);
        if (!anyTimeSeriesWritten) {
            anyTimeSeriesWritten = true;
            initMissingValueText();
            initTimeZone();
            writeTimeZone();
        }

        for (int i = 0, n = timeSeriesContent.getTimeSeriesCount(); i < n; i++) {
            timeSeriesContent.setTimeSeriesIndex(i);
            if (skipEmptyTimeSeries && timeSeriesContent.isTimeSeriesEmpty()) continue;
            if (!timeSeriesContent.isTimeSeriesEmpty()) timeSeriesContent.setContentTimeIndex(0);
            writer.writeStartElement("series");
            writeHeader();
            writeEvents();
            writer.writeEndElement();
        }
    }

    @Override
    public void serializeEndDocument() throws Exception {
        if (!anyTimeSeriesWritten) throw new PiTimeSeriesNotFoundException("No TimeSeries to be Written to " + virtualFileName);
        writer.writeEndElement();
        writer.writeEndDocument();
        if (eventDestination != EventDestination.SEPARATE_BINARY_FILE) return;
        flushBinEvents();
        binaryOutputSteam.close();
        binaryOutputSteam = null;
    }

    private void initMissingValueText() {
        missingValueText = timeSeriesContent.getDefaultMissingValue('.');
        try {
            missingValue = Float.parseFloat(missingValueText);
        } catch (NumberFormatException e) {
            timeSeriesContent.setMissingValue(Float.NaN);
            missingValue = Float.NaN;
            missingValueText = "NaN";
        }
    }

    private void initTimeZone() {
        if (!ObjectUtils.equals(timeZone, this.timeSeriesContent.getDefaultTimeZone())) {
            Arrays.fill(cachedTimes, Long.MIN_VALUE);
            timeZone = timeSeriesContent.getDefaultTimeZone();
        }

        dateFormat = FastDateFormat.getInstance("yyyy-MM-dd", timeZone, Locale.US, dateFormat);
        timeFormat = useMilliseconds?FastDateFormat.getInstance("HH:mm:ss.SSS", timeZone, Locale.US, timeFormat) : FastDateFormat.getInstance("HH:mm:ss", timeZone, Locale.US, timeFormat);
    }

    private void writeTimeZone() throws XMLStreamException {
        if (timeZone.useDaylightTime()) {
            PiSerializerUtils.writeElement(writer, "daylightSavingObservingTimeZone", timeZone.getID());
        } else {
            PiSerializerUtils.writeElement(writer, "timeZone", String.valueOf(TimeZoneUtils.getTimeZoneOffsetHours(timeZone)));
        }
    }

    private void writeHeader() throws Exception {
        TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
        PiTimeSeriesHeader piHeader = header instanceof PiTimeSeriesHeader ? (PiTimeSeriesHeader) header : new PiTimeSeriesHeader();

        writer.writeStartElement("header");
        PiSerializerUtils.writeElement(writer, "type", header.getParameterType() == null ? "instantaneous" : header.getParameterType().getName());
        if (version.getIntId() >= PiVersion.VERSION_1_17.getIntId() && header.getModuleInstanceId() != null) PiSerializerUtils.writeElement(writer, "moduleInstanceId", header.getModuleInstanceId());
        PiSerializerUtils.writeElement(writer, "locationId", header.getLocationId() == null ? "unknown" : header.getLocationId());
        PiSerializerUtils.writeElement(writer, "parameterId", header.getParameterId() == null ? "unknown" : header.getParameterId());
        if (version.getIntId() >= PiVersion.VERSION_1_4.getIntId()) {
            for (int i = 0, n = header.getQualifierCount(); i < n; i++) {
                PiSerializerUtils.writeElement(writer, "qualifierId", header.getQualifierId(i));
            }
        }
        if (version.getIntId() >= PiVersion.VERSION_1_4.getIntId() && header.getEnsembleId() != null && !header.getEnsembleId().equals("main")) {
            if (ensembleMemberFormat != EnsembleMemberFormat.HIDE) {
                writeOptionalElement("ensembleId", header.getEnsembleId());
                writeOptionalElement(ensembleMemberFormat == EnsembleMemberFormat.INDEX
                        ? "ensembleMemberIndex" : "ensembleMemberId", header.getEnsembleMemberId());
            }
        }

        writeTimeStep(header);
        writePeriod();
        if (version.getIntId() >= PiVersion.VERSION_1_5.getIntId()) writeTime("forecastDate", header.getForecastTime(), 2);
        if (version.getIntId() >= PiVersion.VERSION_1_20.getIntId()) writeTime("approvedDate", header.getApprovedTime(), 4);

        PiSerializerUtils.writeElement(writer, "missVal", timeSeriesContent.getDefaultMissingValue('.'));
        writeOptionalElement("longName", piHeader.getLongName());
        writeOptionalElement("stationName", header.getLocationName());

        if (version.getIntId() >= PiVersion.VERSION_1_7.getIntId()) PiSerializerUtils.writeCoordinates(writer, header, version);
        writeOptionalElement("units", header.getUnit());
        for (int i = 0, n = header.getDomainParameterCount(); i < n; i++) {
            writeDomainAxis(header.getDomainParameterId(i), header.getDomainUnit(i));
        }

        writeOptionalElement("sourceOrganisation", piHeader.getSourceOrganisation());
        writeOptionalElement("sourceSystem", piHeader.getSourceSystem());
        writeOptionalElement("fileDescription", piHeader.getFileDescription());


        if (header.getCreationTime() != Long.MIN_VALUE) {
            updateDateTimeStringCache(header.getCreationTime(), 3);
            PiSerializerUtils.writeElement(writer, "creationDate", cachedDateStrings[3]);
            PiSerializerUtils.writeElement(writer, "creationTime", cachedTimeStrings[3]);
        }

        writeOptionalElement("region", piHeader.getRegion());

        if (header.getHighLevelThresholdCount() > 0) writeHighThresholds(header);

        if (version.getIntId() >= PiVersion.VERSION_1_16.getIntId() && timeSeriesContent.hasStatistics()) writeTimeSeriesStatistics();

        if (version.getIntId() >= PiVersion.VERSION_1_26.getIntId() && timeSeriesContent.getProduct() != null) writeProduct();

        writer.writeEndElement();
    }

    private void writeProduct() throws XMLStreamException {
        Product product = timeSeriesContent.getProduct();
        writer.writeStartElement("product");
        writer.writeAttribute("id", product.getId());
        writer.writeAttribute("name", product.getName());

        writeTime("productDate", product.getProductTime(), 5);

        writer.writeEmptyElement("category");
        writer.writeAttribute("id", product.getCategory().getId());
        writer.writeAttribute("name", product.getCategory().getName());

        if (product.getProductInfos() != null && product.getProductInfos().length > 0) {
            for (int i = 0; i < product.getProductInfos().length; i++) {
                ProductInfo productInfo = product.getProductInfos()[i];
                writer.writeStartElement("productInfo");

                writer.writeStartElement("user");
                writer.writeCharacters(productInfo.getUser());
                writer.writeEndElement();

                writer.writeStartElement("confidence");
                writer.writeCharacters(productInfo.getConfidence());
                writer.writeEndElement();

                writer.writeStartElement("classification");
                writer.writeCharacters(productInfo.getClassification());
                writer.writeEndElement();

                writer.writeStartElement("comment");
                writer.writeCharacters(productInfo.getComment());
                writer.writeEndElement();

                writer.writeEndElement(); // productInfo
            }
        }
        writer.writeEndElement(); // product
    }

    private void writeTimeSeriesStatistics() throws XMLStreamException {
        Statistics timeSeriesInfoStatistics = timeSeriesContent.getStatistics();
        if (timeSeriesInfoStatistics == null) return;
        if (timeSeriesInfoStatistics.hasFirstValueTime()) writeTime("firstValueTime", timeSeriesInfoStatistics.getFirstValueTime(), 0);
        if (timeSeriesInfoStatistics.hasLastValueTime()) writeTime("lastValueTime", timeSeriesInfoStatistics.getLastValueTime(), 1);
        if (timeSeriesInfoStatistics.hasMaxValue()) PiSerializerUtils.writeElement(writer, "maxValue", timeSeriesInfoStatistics.getMaxValue('.'));
        if (timeSeriesInfoStatistics.hasMinValue()) PiSerializerUtils.writeElement(writer, "minValue", timeSeriesInfoStatistics.getMinValue('.'));
        if (timeSeriesInfoStatistics.hasValueCount()) PiSerializerUtils.writeElement(writer,"valueCount", Integer.toString(timeSeriesInfoStatistics.getValueCount()));
        if (timeSeriesInfoStatistics.hasMaxWarningLevelName()) PiSerializerUtils.writeElement(writer, "maxWarningLevelName", timeSeriesInfoStatistics.getMaxWarningLevelName());
    }

    private void writeHighThresholds(TimeSeriesHeader header) throws XMLStreamException {
        boolean thresholdsAvailable = false;
        boolean firstThreshold = true;

        for (int i = 0; i < header.getHighLevelThresholdCount(); i++) {
            TimeSeriesHeader.Threshold threshold = header.getHighLevelThreshold(i);
            String id = threshold.getId();
            String name = threshold.getName();
            //Note: here value can be NaN for LevelThresholdValues that have different values for different aggregationTimeSpans configured.
            float value = threshold.getValue();
            if (Float.isNaN(value)) {
                continue;
            }
            thresholdsAvailable = true;
            if (firstThreshold) {
                writer.writeStartElement("thresholds");
                firstThreshold = false;
            }

            if (version.getIntId() >= PiVersion.VERSION_1_14.getIntId()) {
                String label = null;
                String comment = null;
                String description = null;
                if (version.getIntId() >= PiVersion.VERSION_1_22.getIntId()){
                    label = threshold.getLabel();
                }
                if (version.getIntId() >= PiVersion.VERSION_1_27.getIntId()){
                    comment = threshold.getComment();
                    description = threshold.getDescription();
                }
                if (threshold.getGroupCount() > 0) {
                    for (int j = 0, m = threshold.getGroupCount(); j < m; j++) {
                        String groupId = threshold.getGroupId(j);
                        String groupName = threshold.getGroupName(j);
                        if (threshold.getGroupCount() == 1 && groupId != null && groupId.equalsIgnoreCase("DeprecatedThresholdsConfigured")) {
                            groupId = null;
                            groupName = null;
                        }
                        writeThreshold(id, name, value, groupId, groupName, label, description, comment);
                    }
                } else {
                    writeThreshold(id, name, value, null, null, label, description, comment);
                }
            } else {
                writeThreshold(id, name, value, null, null, null, null, null);
            }
        }

        if (thresholdsAvailable) {
            writer.writeEndElement();
        }
    }

    private void writeThreshold(String id, String name, float value, String groupId, String groupName, String label, String description, String comment) throws XMLStreamException {
        writer.writeStartElement("highLevelThreshold");
        writer.writeAttribute("id", id);
        writer.writeAttribute("name", name);
        if (label != null) writer.writeAttribute("label", label);
        if (description != null) writer.writeAttribute("description", description);
        if (comment != null) writer.writeAttribute("comment", comment);
        writer.writeAttribute("value", Float.toString(value));
        if (groupId != null) writer.writeAttribute("groupId", groupId);
        if (groupName != null) writer.writeAttribute("groupName", groupName);

        writer.writeEndElement();
    }

    private void writePeriod() throws XMLStreamException {
        TimeStep timeStep = timeSeriesContent.getTimeSeriesHeader().getTimeStep();
        Period period = timeSeriesContent.getTimeSeriesPeriod();
        Period headerPeriod;
        if (period == Period.NEVER) {
            // create a dummy period
            long now = timeStep.nearestTime(System.currentTimeMillis());
            headerPeriod = new Period(now, now);
        } else {
            headerPeriod = period;
        }

        writeTime("startDate", headerPeriod.getStartTime(), 0);
        writeTime("endDate", headerPeriod.getEndTime(), 1);
    }

    private void updateDateTimeStringCache(long time, int cacheIndex) {
        if (cachedTimes[cacheIndex] == time) return;
        cachedTimes[cacheIndex] = time;
        cachedDateStrings[cacheIndex] = dateFormat.format(time);
        cachedTimeStrings[cacheIndex] = timeFormat.format(time);
    }

    private void writeTime(String name, long time, int cacheIndex) throws XMLStreamException {
        if (time == Long.MIN_VALUE) return;
        writer.writeEmptyElement(name);

        updateDateTimeStringCache(time, cacheIndex);
        writer.writeAttribute("date", cachedDateStrings[cacheIndex]);
        writer.writeAttribute("time", cachedTimeStrings[cacheIndex]);
    }

    publicprivate EventDestinationvoid getEventDestinationwriteTimeStep(TimeSeriesHeader header) throws XMLStreamException {
        return eventDestinationwriter.writeEmptyElement("timeStep");
    }

    TimeStep publictimeStep void= setEventDestination(EventDestination eventDestination) {
header.getTimeStep();

        //  this.eventDestination = eventDestination;todo add support for month time step
    }

    public PiVersion getVersion(if (timeStep.isEquidistantMillis()) {
        return version;
    }

 writer.writeAttribute("unit", "second");
   public void setVersion(PiVersion version) {
     int seconds = if (version == null)(int) (timeStep.getStepMillis() / 1000);
            throw newif IllegalArgumentException("versioncachedSeconds !== null");
seconds) {
        this.version = version;
    }

  cachedSecondsString  @Override
    public void serialize(TimeSeriesContent content, File file) throws Exception {
= TextUtils.toString(seconds);
               File binFilecachedSeconds = FileUtils.getFileWithOtherExtension(file, "bin")seconds;

        if (eventDestination != EventDestination.SEPARATE_BINARY_FILE ) {}
            binFilewriter.delete(writeAttribute("multiplier", cachedSecondsString);
        } else if (timeStep instanceof TimesOfDayDaylightSavingTimeStep if&& (binFile.exists()) 
version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) {
            writer.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayDaylightSavingTimeStep) timeStep));
        } throwelse newif IOException("Can not delete " + binFile);

 timeStep instanceof TimesOfDayTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) {
           LineWriter writer = new LineWriter(new OutputStreamWriter(new FileOutputStream(file), IOUtils.UTF8_CHARSET.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayTimeStep) timeStep));
        } else if (timeStep tryinstanceof {
TimesOfHourTimeStep && version.getIntId() >= PiVersion.VERSION_1_24.getIntId()) {
           serialize(content, writer.writeAttribute("minutes", filePiCastorUtils.getPath(getTimesOfHourString((TimesOfHourTimeStep) timeStep));
            } finallyelse {
                writer.close(writeAttribute("unit", "nonequidistant");
        }
    }

    private void writeEvents() throws Exception {
   return;
     switch   }
(eventDestination) {
        LineWriter  writer = new LineWriter(new OutputStreamWriter(new FileOutputStream(file), IOUtils.UTF8_CHARSET));
case ONLY_HEADERS:
           try {
    return;
        binaryOutputSteam = new LittleEndianDataOutputStream(new BufferedOutputStream(new FileOutputStream(binFile))); case SEPARATE_BINARY_FILE:
            try  {
  writeBinEvents();
              serialize(content, writer, file.getPath()) return;
            } finally {case XML_EMBEDDED:
                binaryOutputSteam.closewriteXmlEvents();
        }
    }

    private void writeBinEvents() throws } finallyException {
        for (int i = 0, n = writertimeSeriesContent.closegetContentTimeCount(); i < n; i++) {
        }
    }
timeSeriesContent.setContentTimeIndex(i);
    public void serialize(LineWriter writer, OutputStream binOutputStream, String fileName, TimeSeriesContent content) throws Exception {
if (!timeSeriesContent.isTimeAvailable()) continue;
            if (writerbufferPos == null)BUFFER_SIZE) flushBinEvents();
            throw new IllegalArgumentException("writer == null");
floatBuffer[bufferPos++] = timeSeriesContent.getValue();
        if (binOutputStream == null)}
    }

    private void flushBinEvents() throws throw new IllegalArgumentException("binaryOutputSteam == null");
IOException {
        if (contentbufferPos == null0) return;
        BinaryUtils.copy(floatBuffer, 0, bufferPos,  throw new IllegalArgumentException("content == null");
byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE, ByteOrder.LITTLE_ENDIAN);
        binaryOutputSteam = new LittleEndianDataOutputStream(binOutputStream.write(byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE);
        bufferPos try= {0;
    }

    private void writeXmlEvents() throws serialize(content, writer, fileName);
XMLStreamException {
        Properties }properties finally {= Properties.NONE;
        TimeSeriesHeader header =  binaryOutputSteamtimeSeriesContent.flushgetTimeSeriesHeader();
        }domainAxesValues = null;
    }

    @Override
int domainParameterCount = header.getDomainParameterCount();
 public void serialize(TimeSeriesContent content, LineWriter writer, String virtualFileName) throws Exception {float[] floatBuffer = this.floatBuffer;
        iffor (writerint i = 0, n = null)
    timeSeriesContent.getContentTimeCount(); i < n; i++) {
        throw new IllegalArgumentException("writer == null" timeSeriesContent.setContentTimeIndex(i);

            if (content == null)!timeSeriesContent.isTimeAvailable()) continue;
            throw new IllegalArgumentException("contentProperties newProperties == null" timeSeriesContent.getProperties();

        this.timeSeriesContent = content;

       if dateFormat.setTimeZone(content!newProperties.getDefaultTimeZoneequals(properties)); {
        timeFormat.setTimeZone(content.getDefaultTimeZone());

        XMLSerializer serializerproperties = new XMLSerializer()newProperties;
        OutputFormat     of = new OutputFormat("XML", "UTF-8", truePiSerializerUtils.writeProperties(properties, version, writer, dateFormat, timeFormat);
         serializer.setOutputFormat(of);
   }
     serializer.setOutputCharStream(writer);
       boolean xmlContentHandlervalueMissing = serializertimeSeriesContent.asContentHandlerisValueMissing();

            if xmlContentHandler.startDocument();
(domainParameterCount > 0 && !valueMissing) {
           attributesBuffer.clear();
     int valueCount = addAttribute("xmlns", "http://www.wldelft.nl/fews/PI");
writeAxisValues();
                if addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
(floatBuffer == null || floatBuffer.length != valueCount) {
            addAttribute("xsi:schemaLocation", PiSchemaLocations.get("pi_timeseries.xsd"));
        floatBuffer  addAttribute("version", version.toString())= new float[valueCount];
        xmlContentHandler.startElement("", "TimeSeries", "TimeSeries", attributesBuffer);
        writeElement("timeZone", String.valueOf((double) content.getDefaultTimeZone().getRawOffset() / (double) TimeUnit.HOUR_MILLIS)) this.floatBuffer = floatBuffer;
        for (int i = 0, n = content.getTimeSeriesCount(); i < n; i++) {
 }
                contenttimeSeriesContent.setTimeSeriesIndexreadValues(ifloatBuffer);
            }
  xmlContentHandler.startElement(null, null, "series", null);
       if (domainParameterCount == 0 || writeHeader(valueMissing);
 {
                writeEvents(writer.writeEmptyElement("event");
            xmlContentHandler.endElement(null, null, "series");} else {
        }
        xmlContentHandlerwriter.endElement(null, null, "TimeSerieswriteStartElement("event");
        xmlContentHandler.endDocument();
    }

          private void writeEvents() throws Exception {
writeTime();
            forif (intdomainParameterCount i == 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) {) writeValue();
            timeSeriesContentPiSerializerUtils.setContentTimeIndex(iwriteFlagsUserComment(writer, timeSeriesContent, version);
            if (!timeSeriesContent.isTimeAvailable())domainParameterCount == 0 || valueMissing) continue;
            int valuesPerRow  writeEvent(timeSeriesContent.getTime())= domainParameterCount == 1 ? 1 : domainAxesValues[1].length;
        }
    }

    private void writeHeader() throws Exception {
writeValues(floatBuffer, valuesPerRow);
          TimeSeriesHeader header = timeSeriesContentwriter.getTimeSeriesHeaderwriteEndElement();
        PiTimeSeriesHeader}
 piHeader = header instanceof PiTimeSeriesHeader ? (PiTimeSeriesHeader) header : new PiTimeSeriesHeader(); }

    private void   xmlContentHandler.startElement(null, null, "header", null);
   writeValue() throws XMLStreamException {
     writeElement("type", header.getParameterType() == null ?String "instantaneous"stringValue := headertimeSeriesContent.getParameterTypegetValue()'.getName('));
        writeElementwriter.writeAttribute("locationIdvalue", header.getLocationId() == null ? "unknown" : header.getLocationId());
        writeElement("parameterId", header.getParameterIdstringValue);
        if (version.ordinal() >= PiVersion.VERSION_1_31.ordinal() && timeSeriesContent.getValueSource() == null ? "unknown" : header.getParameterId()ValueSource.MANUAL) writer.writeAttribute("valueSource", "MAN");
        if (version.ordinal() >=< PiVersion.VERSION_1_423.ordinal()) {return;
        if (timeSeriesContent.isValueMissing()) return;
    for (int i = 0,float nvalue = headertimeSeriesContent.getQualifierCountgetValue(); i < n; i++) {
        if (timeSeriesContent.getMinValue() !=      writeElementvalue) writer.writeAttribute("qualifierIdminValue", headertimeSeriesContent.getQualifierIdgetMinValue(i'.'));
        if (timeSeriesContent.getMaxValue() !=  }
        }value) writer.writeAttribute("maxValue", timeSeriesContent.getMaxValue('.'));
        if (version.ordinal() >= PiVersion.VERSION_1_429.ordinal() && header.getEnsembleId() != null && !header.getEnsembleId().equals("main"))!"".equals(timeSeriesContent.getDetectionSymbol())) writer.writeAttribute("detection", timeSeriesContent.getDetectionSymbol());
    }

    private void writeTime() throws XMLStreamException {
        long time   writeOptionalElement("ensembleId", header.getEnsembleId()= timeSeriesContent.getTime();
        String dateText   writeOptionalElement("ensembleMemberIndex", header.getEnsembleMemberIndex())= dateFormat.format(time);
        }

writer.writeAttribute("date", dateText);
        String timeText = writeTimeSteptimeFormat.format(headertime);
        writePeriod(writer.writeAttribute("time", timeText);
        if (version.ordinal() >=< PiVersion.VERSION_1_523.ordinal()) writeTime("forecastDate", header.getForecastTime()) return;

        long  writeElement("missVal", Float.toString(timeSeriesContent.getDefaultMissingValue()));
startTime = timeSeriesContent.getRangeStartTime();
        long  writeOptionalElement("longName", piHeader.getLongName()endTime = timeSeriesContent.getRangeEndTime();
        writeOptionalElementwriteAttribute("stationNamestartDate", dateFormat, header.getLocationName()startTime, time, dateText);
        writeOptionalElementwriteAttribute("units", header.getUnit())startTime", timeFormat, startTime, time, timeText);
        writeOptionalElementwriteAttribute("sourceOrganisationendDate", piHeader.getSourceOrganisation()dateFormat, endTime, time, dateText);
        writeOptionalElementwriteAttribute("sourceSystem", piHeader.getSourceSystem()endTime" , timeFormat, endTime, time, timeText);
    }

     writeOptionalElement("fileDescription", piHeader.getFileDescription());

        if (header.getCreationTime() != Long.MIN_VALUE)private void writeAttribute(String attributeName, FastDateFormat dateFormat, long time, long defaultTime, String defaultText) throws XMLStreamException {
        if (time   writeElement("creationDate", dateFormat.format(header.getCreationTime()))== defaultTime) return;
        String text   writeElement("creationTime", timeFormat= dateFormat.format(header.getCreationTime()time));
        }

        writeOptionalElement("region", piHeader.getRegion());
if (TextUtils.equals(text, defaultText)) return;
        xmlContentHandlerwriter.endElementwriteAttribute(nullattributeName, null, "header"text);
    }

    private voidint writePeriodwriteAxisValues() throws SAXExceptionXMLStreamException {
        TimeStepTimeSeriesHeader timeStepheader = timeSeriesContent.getTimeSeriesHeader().getTimeStep();
        Periodif period(domainAxesValues == timeSeriesContent.getTimeSeriesPeriod();
        Period headerPeriodnull) domainAxesValues = new float[header.getDomainParameterCount()][];
        if (periodnewDomainValues == null) newDomainValues = new Period.NEVER) {float[header.getDomainParameterCount()][];
        assert !timeSeriesContent.isValueMissing();
      //  createint ares dummy= period1;
        for (int i = long0, nown = timeStepheader.nearestTime(System.currentTimeMillis());getDomainParameterCount(); i < n; i++) {
            float[] headerPeriodvalues = new Period(now, now);
 newDomainValues[i];
            int }valueCount else {= timeSeriesContent.getDomainAxisValueCount(i);
            headerPeriodres *= periodvalueCount;
        }

    if (values ==  writeTime("startDate", headerPeriod.getStartTime());
        writeTime("endDate", headerPeriod.getEndTime());null || values.length != valueCount) {
    }

     private void writeTime(String name, long time) throws SAXExceptionvalues {
= new float[valueCount];
      if (time == Long.MIN_VALUE) return;
      newDomainValues[i] = attributesBuffer.clear()values;
        addAttribute("date", dateFormat.format(time));
   }
     addAttribute("time", timeFormat.format(time));
        writeAttributes(nametimeSeriesContent.readDomainAxisValues(i, values);
       }

    private void writeTimeStep(TimeSeriesHeader header) throws SAXException {
if (Arrays.equals(values, domainAxesValues[i])) continue;
         TimeStep timeStep = headerwriter.getTimeStepwriteStartElement("domainAxisValues");
            attributesBuffer.clear(writer.writeAttribute("parameterId", header.getDomainParameterId(i));

        // todo add support forint monthvaluePerRow time= step
i == 0 ? 1    if (timeStep.isEquidistantMillis()) {: values.length;
            long seconds = timeStep.getStepMillis() / 1000;
writeValues(values, valuePerRow);
            domainAxesValues[i] = addAttribute("unit","second"Clasz.floats.copyOfArray(values);
            addAttribute("multiplier", String.valueOf(seconds)writer.writeEndElement();
        } else {
            addAttribute("unit", "nonequidistant");
    return res;
    }

    private void writeValues(float[]  writeAttributes("timeStep");
    }

values, int valuesPerRow) throws XMLStreamException {
    private void writeEvent(long time) throws Exception {assert !timeSeriesContent.isValueMissing();
        if (eventDestinationchar[] buffer == EventDestination.ONLY_HEADERS) returnthis.charBuffer;
        if (eventDestinationbuffer == EventDestination.SEPARATE_BINARY_FILEnull) {
            buffer =  binaryOutputSteam.writeFloat(timeSeriesContent.getValue());
new char[15];
            this.charBuffer = returnbuffer;
        }

        attributesBuffer.clear();

        addAttribute("date", dateFormat.format(time));
for (int i = 0; i < values.length; i++) {
            addAttribute("time", timeFormat.format(time));
        addAttribute("value", timeSeriesContent.getValue('.'));
buffer[0] = i % valuesPerRow == 0 ? '\n' : '\t';
             addAttribute("flag", timeSeriesContent.getStringFlag())float value = values[i];
        if (version.ordinal() >= PiVersion.VERSION_1_3.ordinal()) {    int endPos;
            String comment = timeSeriesContent.getComment();if (MathUtils.equals(value, missingValue)) {
            if (comment != null) addAttribute("comment"missingValueText.getChars(0, timeSeriesContentmissingValueText.getCommentlength(), buffer, 1);
          }
      endPos = writeAttributes("event")missingValueText.length() + 1;
    }

    private void writeOptionalElement(String elementName, int index) throws SAXException} else {
        if (index == -1) return;
    endPos = TextUtils.format(buffer, 1, writeElement(elementNamevalue, Integer.toString(index));
'.', 0, 10);
            }

          private void writeOptionalElement(String elementName, String s) throws SAXException {
    writer.writeCharacters(buffer, 0, endPos);  // start with space
    if (s == null) return;}
        if (s.trim().length() == 0) return;
writer.writeCharacters("\n        writeElement(elementName, s");
    }

    private  void writeElementwriteOptionalElement(String nameelementName, String values) throws SAXExceptionXMLStreamException {
        if xmlContentHandler.startElement(null, null, name,s == null) return;
        if xmlContentHandler.characters(values.toCharArraytrim(), 0, value.length.isEmpty()) return;
       xmlContentHandler PiSerializerUtils.endElementwriteElement(nullwriter, nullelementName, names);
    }

    private void writeAttributeswriteDomainAxis(String parameterId, String nameunit) throws SAXExceptionXMLStreamException {
        xmlContentHandlerwriter.startElementwriteEmptyElement("", name, name, attributesBufferdomainAxis");
        xmlContentHandlerwriter.endElementwriteAttribute(null"parameterId", null, nameparameterId);
    }

    private voidif addAttribute(Stringunit name, String value) {
        attributesBuffer.addAttribute("", name, name, "CDATA", value!= null) writer.writeAttribute("units", unit);
    }
}