Versions Compared

Key

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


Code Block
languagejava
package nl.wldelft.fews.pi;

...



import nl.wldelft.util.Arguments;

...


import nl.wldelft.util.BinaryUtils;

...


import nl.wldelft.util.Clasz;

...


import nl.wldelft.util.FastDateFormat;

...


import nl.wldelft.util.FileUtils;

...


import nl.wldelft.util.MathUtils;

...


import nl.wldelft.util.NumberType;

...


import nl.wldelft.util.ObjectUtils;

...


import nl.wldelft.util.Period;

...


import nl.wldelft.util.Properties;

...


import nl.wldelft.util.TextUtils;

...


import nl.wldelft.util.TimeZoneUtils;

...


import nl.wldelft.util.function.Supplier;

...


import nl.wldelft.util.io.VirtualOutputDir;

...


import nl.wldelft.util.io.VirtualOutputDirConsumer;

...


import nl.wldelft.util.io.XmlChunkedSerializer;

...


import nl.wldelft.util.timeseries.Product;

...


import nl.wldelft.util.timeseries.ProductInfo;

...


import nl.wldelft.util.timeseries.Statistics;

...


import nl.wldelft.util.timeseries.TimeSeriesContent;

...


import nl.wldelft.util.timeseries.TimeSeriesHeader;

...


import nl.wldelft.util.timeseries.TimeStep;

...


import nl.wldelft.util.timeseries.TimesOfDayDaylightSavingTimeStep;

...


import nl.wldelft.util.timeseries.TimesOfDayTimeStep;

...


import nl.wldelft.util.timeseries.TimesOfHourTimeStep;

...


import nl.wldelft.util.timeseries.ValueSource;

...



import javax.xml.stream.XMLStreamException;

...


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]);
    }

    private void writeTimeStep(TimeSeriesHeader header) throws XMLStreamException {
        writer.writeEmptyElement("timeStep");
        TimeStep timeStep = header.getTimeStep();

        // todo add support for month time step
        if (timeStep.isEquidistantMillis()) {
            writer.writeAttribute("unit", "second");
            int seconds = (int) (timeStep.getStepMillis() / 1000);
            if (cachedSeconds != seconds) {
                cachedSecondsString = TextUtils.toString(seconds);
                cachedSeconds = seconds;
            }
            writer.writeAttribute("multiplier", cachedSecondsString);
        } else if (timeStep instanceof TimesOfDayDaylightSavingTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) {
            writer.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayDaylightSavingTimeStep) timeStep));
        } else if (timeStep instanceof TimesOfDayTimeStep && version.getIntId() >= PiVersion.VERSION_1_18.getIntId()) {
            writer.writeAttribute("times", PiCastorUtils.getTimesOfDayString((TimesOfDayTimeStep) timeStep));
        } else if (timeStep instanceof TimesOfHourTimeStep && version.getIntId() >= PiVersion.VERSION_1_24.getIntId()) {
            writer.writeAttribute("minutes", PiCastorUtils.getTimesOfHourString((TimesOfHourTimeStep) timeStep));
        } else {
            writer.writeAttribute("unit", "nonequidistant");
        }
    }

    private void writeEvents() throws Exception {
        switch (eventDestination) {
            case ONLY_HEADERS:
                return;
            case SEPARATE_BINARY_FILE:
                writeBinEvents();
                return;
            case XML_EMBEDDED:
                writeXmlEvents();
        }
    }

    private void writeBinEvents() throws Exception {
        for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) {
            timeSeriesContent.setContentTimeIndex(i);
            if (!timeSeriesContent.isTimeAvailable()) continue;
            if (bufferPos == BUFFER_SIZE) flushBinEvents();
            floatBuffer[bufferPos++] = timeSeriesContent.getValue();
        }
    }

    private void flushBinEvents() throws IOException {
        if (bufferPos == 0) return;
        BinaryUtils.copy(floatBuffer, 0, bufferPos, byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE, ByteOrder.LITTLE_ENDIAN);
        binaryOutputSteam.write(byteBuffer, 0, bufferPos * NumberType.FLOAT_SIZE);
        bufferPos = 0;
    }

    private void writeXmlEvents() throws XMLStreamException {
        Properties properties = Properties.NONE;
        TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
        domainAxesValues = null;
        int domainParameterCount = header.getDomainParameterCount();
        float[] floatBuffer = this.floatBuffer;
        for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) {
            timeSeriesContent.setContentTimeIndex(i);
            if (!timeSeriesContent.isTimeAvailable()) continue;
            Properties newProperties = timeSeriesContent.getProperties();
            if (!newProperties.equals(properties)) {
                properties = newProperties;
                PiSerializerUtils.writeProperties(properties, version, writer, dateFormat, timeFormat);
            }
            boolean valueMissing = timeSeriesContent.isValueMissing();
            if (domainParameterCount > 0 && !valueMissing) {
                int valueCount = writeAxisValues();
                if (floatBuffer == null || floatBuffer.length != valueCount) {
                    floatBuffer = new float[valueCount];
                    this.floatBuffer = floatBuffer;
                }
                timeSeriesContent.readValues(floatBuffer);
            }
            if (domainParameterCount == 0 || valueMissing) {
                writer.writeEmptyElement("event");
            } else {
                writer.writeStartElement("event");
            }
            writeTime();
            if (domainParameterCount == 0) writeValue();
            PiSerializerUtils.writeFlagsUserComment(writer, timeSeriesContent, version);
            if (domainParameterCount == 0 || valueMissing) continue;
            int valuesPerRow = domainParameterCount == 1 ? 1 : domainAxesValues[1].length;
            writeValues(floatBuffer, valuesPerRow);
            writer.writeEndElement();
        }
    }

    private void writeValue() throws XMLStreamException {
        String stringValue = timeSeriesContent.getValue('.');
        writer.writeAttribute("value", stringValue);
        if (version.ordinal() >= PiVersion.VERSION_1_31.ordinal() && timeSeriesContent.getValueSource() == ValueSource.MANUAL) writer.writeAttribute("valueSource", "MAN");
        if (version.ordinal() < PiVersion.VERSION_1_23.ordinal()) return;
        if (timeSeriesContent.isValueMissing()) return;
        float value = timeSeriesContent.getValue();
        if (timeSeriesContent.getMinValue() != value) writer.writeAttribute("minValue", timeSeriesContent.getMinValue('.'));
        if (timeSeriesContent.getMaxValue() != value) writer.writeAttribute("maxValue", timeSeriesContent.getMaxValue('.'));
        if (version.ordinal() >= PiVersion.VERSION_1_29.ordinal() && !"".equals(timeSeriesContent.getDetectionSymbol())) writer.writeAttribute("detection", timeSeriesContent.getDetectionSymbol());
    }

    private void writeTime() throws XMLStreamException {
        long time = timeSeriesContent.getTime();
        String dateText = dateFormat.format(time);
        writer.writeAttribute("date", dateText);
        String timeText = timeFormat.format(time);
        writer.writeAttribute("time", timeText);
        if (version.ordinal() < PiVersion.VERSION_1_23.ordinal()) return;
        long startTime = timeSeriesContent.getRangeStartTime();
        long endTime = timeSeriesContent.getRangeEndTime();
        writeAttribute("startDate", dateFormat, startTime, time, dateText);
        writeAttribute("startTime", timeFormat, startTime, time, timeText);
        writeAttribute("endDate", dateFormat, endTime, time, dateText);
        writeAttribute("endTime" , timeFormat, endTime, time, timeText);
    }

    private void writeAttribute(String attributeName, FastDateFormat dateFormat, long time, long defaultTime, String defaultText) throws XMLStreamException {
        if (time == defaultTime) return;
        String text = dateFormat.format(time);
        if (TextUtils.equals(text, defaultText)) return;
        writer.writeAttribute(attributeName, text);
    }

    private int writeAxisValues() throws XMLStreamException {
        TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
        if (domainAxesValues == null) domainAxesValues = new float[header.getDomainParameterCount()][];
        if (newDomainValues == null) newDomainValues = new float[header.getDomainParameterCount()][];
        assert !timeSeriesContent.isValueMissing();
        int res = 1;
        for (int i = 0, n = header.getDomainParameterCount(); i < n; i++) {
            float[] values = newDomainValues[i];
            int valueCount = timeSeriesContent.getDomainAxisValueCount(i);
            res *= valueCount;
            if (values == null || values.length != valueCount) {
                values = new float[valueCount];
                newDomainValues[i] = values;
            }
            timeSeriesContent.readDomainAxisValues(i, values);
            if (Arrays.equals(values, domainAxesValues[i])) continue;

...


            writer.writeStartElement("domainAxisValues");

...


            writer.writeAttribute("parameterId", header.getDomainParameterId(i));

...


            int valuePerRow = i =

...

= 0 ? 1 : values.length;
            writeValues(values, valuePerRow);

...


            domainAxesValues[i] = Clasz.floats.copyOfArray(values);

...


            writer.writeEndElement();

...


        }
        return res;

...


    }

...



    private void writeValues(float[] values, int valuesPerRow) throws XMLStreamException {

...


        assert !timeSeriesContent.isValueMissing();

...


        char[] buffer = this.charBuffer;

...


        if (buffer == null

...

) {
            buffer = new char[15];

...


            this.charBuffer = buffer;

...


        }
        for (int i = 0; i < values.length; i++) {

...


            buffer[0] = i % valuesPerRow == 0 ? '\n' : '\t';

...


            float value = values[i];

...


            int endPos;

...


            if (MathUtils.equals(value,

...

 missingValue)) {
                missingValueText.getChars(0, missingValueText.length(), buffer, 1);

...


                endPos = missingValueText.length() + 1;

...


            } else {

...


                endPos = TextUtils.format(buffer, 1, value, '.', 0, 10);

...


            }
            writer.writeCharacters(buffer,

...

 0, endPos);  // start with space
        }
        writer.writeCharacters("\n        ");

...


    }

...



    private void writeOptionalElement(String elementName, String s) throws XMLStreamException {

...


        if (s == null) return;

...


        if (s.trim().isEmpty()) return;

...


        PiSerializerUtils.writeElement(writer, elementName, s);

...


    }

...



    private void writeDomainAxis(String parameterId, String unit) throws XMLStreamException {

...


        writer.writeEmptyElement("domainAxis");

...


        writer.writeAttribute("parameterId", parameterId);

...


        if (unit != null) writer.writeAttribute("units", unit);

...


    }

...


}