...
Code Block |
---|
/* ================================================================ * Delft FEWS * ================================================================ * * Project Info: http://www.wldelft.nl/soft/fews/index.html * Project Lead: Karel Heynert (karel.heynert@wldelft.nl) * * (C) Copyright 2003, by WL | Delft Hydraulics * P.O. Box 177 * 2600 MH Delft * The Netherlands * http://www.wldelft.nl * * DELFT-FEWS is a sophisticated collection of modules designed * for building a FEWS customised to the specific requirements * of individual agencies. An open modelling approach allows users * to add their own modules in an efficient way. * * ---------------------------------------------------------------- * PiTimeSeriesParser.java * ---------------------------------------------------------------- * (C) Copyright 2003, by WL | Delft Hydraulics * * Original Author: Erik de Rooij * Original Author: Onno van den Akker */ package nl.wldelft.fews.pi; import nl.wldelft.util.*BinaryUtils; import nl.wldelft.util.coverage.PointGeometryCharArrayUtils; import nl.wldelft.util.geodatum.GeoDatumClasz; import nl.wldelft.util.io.VirtualInputDirExceptionUtils; import nl.wldelft.util.io.VirtualInputDirConsumerFastDateFormat; import nl.wldelft.util.io.XmlParserFileUtils; import nl.wldelft.util.timeseries.*FloatArrayUtils; import javaxnl.xmlwldelft.streamutil.XMLStreamConstantsNumberType; import javaxnl.xmlwldelft.streamutil.XMLStreamExceptionPeriod; import javaxnl.xmlwldelft.streamutil.XMLStreamReaderProperties; import javanl.wldelft.ioutil.EOFExceptionStringArrayUtils; import javanl.wldelft.ioutil.IOExceptionTextUtils; import javanl.wldelft.ioutil.InputStreamTimeUnit; import javanl.wldelft.nioutil.ByteOrderTimeZoneUtils; import java.text.ParseExceptionnl.wldelft.util.geodatum.GeoDatum; import javanl.wldelft.util.io.ArrayListVirtualInputDir; import javanl.wldelft.util.Listio.VirtualInputDirConsumer; import javanl.wldelft.util.io.LocaleXmlParser; import javanl.wldelft.util.TimeZonetimeseries.DefaultTimeSeriesHeader; public class PiTimeSeriesParser implements XmlParser<TimeSeriesContentHandler>, VirtualInputDirConsumer { private static final int BUFFER_SIZE = 2048; private final Properties.Builder propertyBuilder = new Properties.Builder(); @Override public void setVirtualInputDir(VirtualInputDir virtualInputDir) {import nl.wldelft.util.timeseries.IrregularTimeStep; import nl.wldelft.util.timeseries.OutOfDetectionRangeFlag; import nl.wldelft.util.timeseries.ParameterType; import nl.wldelft.util.timeseries.RelativeEquidistantTimeStep; import nl.wldelft.util.timeseries.SimpleEquidistantTimeStep; import nl.wldelft.util.timeseries.TimeSeriesArray; import nl.wldelft.util.timeseries.TimeSeriesContentHandler; import nl.wldelft.util.timeseries.TimeSeriesHeader; import nl.wldelft.util.timeseries.TimeStep; import nl.wldelft.util.timeseries.ValueSource; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Locale; import java.util.TimeZone; public class PiTimeSeriesParser implements XmlParser<TimeSeriesContentHandler>, VirtualInputDirConsumer { private static final int BUFFER_SIZE = 2048; private final Properties.Builder propertyBuilder = new Properties.Builder(); private char[] charBuffer = null; private float minValueResolution = Float.NaN; private float[][] axisValues = null; private float[] axisValueResolutions = Clasz.floats.emptyArray(); private boolean axesDirty = false; private String[] domainParameterIds = Clasz.strings.emptyArray(); private String[] domainUnits = Clasz.strings.emptyArray(); private int domainCount = 0; @Override public void setVirtualInputDir(VirtualInputDir virtualInputDir) { this.virtualInputDir = virtualInputDir; } private enum HeaderElement { type(F.R), moduleInstanceId, locationId(F.R), parameterId(F.R), qualifierId(F.M), ensembleId, ensembleMemberIndex, ensembleMemberId, timeStep(F.R | F.A), startDate(F.R | F.A), endDate(F.R | F.A), forecastDate(F.A), approvedDate(F.A), missVal, longName, stationName, lat, lon, x, y, z, units, domainAxis(F.M | F.A), sourceOrganisation, sourceSystem, fileDescription, creationDate, creationTime, region, thresholds, firstValueTime(F.A), lastValueTime(F.A), maxValue, minValue, valueCount, maxWarningLevelName; interface F { int A = 1 << 0; // attributes int R = 1 << 1; // required; int M = 1 << 2; // multiple; } private final int flags; HeaderElement() { this.flags = 0; } HeaderElement(int flags) { this.flags = flags; } public boolean isRequired() { return (flags & F.R) != 0; } public boolean hasAttributes() { return (flags & F.A) != 0; } public boolean isMultipleAllowed() { return (flags & F.M) != 0; } } // fastDateFormat is used to keep track of last time zone and lenient private FastDateFormat fastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd", "HH:mm:ss", TimeZoneUtils.GMT, Locale.US, null); private FastDateFormat fastDateFormatWithMillies = FastDateFormat.getInstance("yyyy-MM-dd", "HH:mm:ss.SSS", TimeZoneUtils.GMT, Locale.US, null); private HeaderElement currentHeaderElement = null; private static final HeaderElement[] HEADER_ELEMENTS = HeaderElement.class.getEnumConstants(); private PiTimeSeriesHeader header = new PiTimeSeriesHeader(); private List<String> qualifiers = new ArrayList<>(); private long timeStepMillis = 0; private TimeStep timeStep = null; private long startTime = Long.MIN_VALUE; private long endTime = Long.MIN_VALUE; private float missingValue = Float.NaN; private String creationDateText = null; private String creationTimeText = null; private long lastTime = Long.MIN_VALUE; private long lastStartTime = Long.MIN_VALUE; private long lastEndTime = Long.MIN_VALUE; private boolean timeAmbiguous = false; private boolean lastTimeAmbiguous = false; private boolean lastStartTimeAmbiguous = false; private boolean lastEndTimeAmbiguous = false; private TimeSeriesContentHandler timeSeriesContentHandler = null; private PiTimeSeriesSerializer.EventDestination eventDestination = null; /** * For performance reasons the pi time series format allows that the values are stored in * a separate bin file instead of embedded in the xml file. * The bin file should have same name as the xml file except the extension equals bin * In this case all time series should be equidistant. */ private VirtualInputDir virtualInputDir = VirtualInputDir.NONE; private InputStream binaryInputStream = null; private byte[] byteBuffer = null; private float[] floatBuffer = null; private int bufferPos = 0; private int bufferCount = 0; private XMLStreamReader reader = null; private String virtualFileName = null; private static boolean lenient = false; private double lat = Double.NaN; private double lon = Double.NaN; private double z = Double.NaN; /** * For backwards compatibility. Earlier versions of the PiTimeSeriesParser were tolerant about the date/time format * and the case insensitive for header element names. * This parser should not accept files that are not valid according to pi_timeseries.xsd * When old adapters are not working you can UseLenientPiTimeSeriesParser temporary till the adapter is fixed * * @param lenient */ public static void setLenient(boolean lenient) { PiTimeSeriesParser.lenient = lenient; } public static boolean isLenient() { return lenient; } public PiTimeSeriesParser() { fastDateFormat.setLenient(lenient); fastDateFormatWithMillies.setLenient(lenient); } @Override public void parse(XMLStreamReader reader, String virtualFileName, TimeSeriesContentHandler timeSeriesContentHandler) throws Exception { this.reader = reader; this.virtualFileName = virtualFileName; this.timeSeriesContentHandler = timeSeriesContentHandler; String virtualBinFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin"); // time zone can be overruled by one or more time zone elements in the pi file this.fastDateFormat.setTimeZone(timeSeriesContentHandler.getDefaultTimeZone()); this.fastDateFormatWithMillies.setTimeZone(timeSeriesContentHandler.getDefaultTimeZone()); if (!virtualInputDir.exists(virtualBinFileName)) { eventDestination = PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED; parse(); return; } eventDestination = PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE; binaryInputStream = virtualInputDir.getInputStream(virtualBinFileName); try { if (byteBuffer == null) { byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE]; floatBuffer = new float[BUFFER_SIZE]; } parse(); boolean eof = bufferPos == bufferCount && binaryInputStream.read() == -1; if (!eof) throw new IOException("More values available in bin file than expected based on time step and start and end time\n" + FileUtils.getPathWithOtherExtension(virtualFileName, "bin")); } finally { bufferPos = 0; bufferCount = 0; binaryInputStream.close(); binaryInputStream = null; } } private void parse() throws Exception { reader.require(XMLStreamConstants.START_DOCUMENT, null, null); reader.nextTag(); reader.require(XMLStreamConstants.START_ELEMENT, null, "TimeSeries"); reader.nextTag(); while (reader.getEventType() != XMLStreamConstants.END_ELEMENT) { TimeZone timeZone = PiParserUtils.parseTimeZone(reader); //returns null if the timeZone is not present in the file if (timeZone != null) { this.fastDateFormat.setTimeZone(timeZone); this.fastDateFormatWithMillies.setTimeZone(timeZone); } if (noTimeSeries()) continue; readTimeSeries(); } reader.require(XMLStreamConstants.END_ELEMENT, null, "TimeSeries"); reader.next(); reader.require(XMLStreamConstants.END_DOCUMENT, null, null); } private boolean noTimeSeries() { return reader.getEventType() == XMLStreamConstants.END_ELEMENT && reader.getLocalName().equals("TimeSeries"); } private void readTimeSeries() throws Exception { reader.require(XMLStreamConstants.START_ELEMENT, null, "series"); reader.nextTag(); parseHeader(); lastTime = Long.MIN_VALUE; timeAmbiguous = false; lastTimeAmbiguous = false; lastStartTimeAmbiguous = false; lastEndTimeAmbiguous = false; timeSeriesContentHandler.setProperties(Properties.NONE); if (eventDestination == PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED) { while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { String localName = reader.getLocalName(); if (TextUtils.equals(localName, "event")) { parseEvent(); } else if (TextUtils.equals(localName, "domainAxisValues")) { parseDomainAxisValues(); } else if (TextUtils.equals(localName, "properties")) { parseProperties(); } else { break; } } } else { assert eventDestination == PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE; readValuesFromBinFile(); } if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { // skip comment reader.require(XMLStreamConstants.START_ELEMENT, null, "comment"); reader.getElementText(); reader.nextTag(); } reader.require(XMLStreamConstants.END_ELEMENT, null, "series"); reader.nextTag(); } private void parseDomainAxisValues() throws Exception { String domainParameterId = reader.getAttributeValue(null, "parameterId"); if (domainParameterId == null) throw new Exception("Attribute parameterId for domainAxisValues is missing"); int index = StringArrayUtils.indexOf(domainParameterIds, 0, domainCount, domainParameterId); if (index == -1) throw new Exception("parameterId " + domainParameterId + " for domainAxisValues not defined in header"); if (axisValues == null) axisValues = new float[domainCount][]; if (axisValueResolutions.length < domainCount) axisValueResolutions = new float[domainCount]; int count = parseValueList(); reader.require(XMLStreamConstants.END_ELEMENT, null, "domainAxisValues"); reader.nextTag(); axisValues[index] = Clasz.floats.copyOfArrayRange(floatBuffer, 0, count); axisValueResolutions[index] = minValueResolution; axesDirty = true; } private int parseValueList() throws Exception { if (charBuffer == null) charBuffer = new char[100]; char[] charBuffer = this.charBuffer; if (floatBuffer == null) floatBuffer = new float[100]; minValueResolution = Float.POSITIVE_INFINITY; int i = 0; int length = 0; int j = 0; while (reader.next() == XMLStreamConstants.CHARACTERS) { length -= i; assert length >= 0; // shift the remaining part of the buffer to the start if (length > 0) CharArrayUtils.arraycopy(charBuffer, i, charBuffer, 0, length); int textLength = reader.getTextLength(); if (charBuffer.length < length + textLength) { charBuffer = Clasz.chars.copyOfArrayRange(charBuffer, 0, length, length + textLength); this.charBuffer = charBuffer; } int n = reader.getTextCharacters(0, charBuffer, length, textLength); assert n == textLength; length += textLength; i = 0; for (; ; ) { int start = CharArrayUtils.indexOfNonWhitespace(charBuffer, i, length - i); if (start == -1) { i = length; break; } int end = CharArrayUtils.indexOfWhitespace(charBuffer, start, length - start); if (end == -1) { i = start; break; } i = end; parseValue(charBuffer, j++, start, end - start + 1); } } if (i != length) parseValue(charBuffer, j++, i, length - i); return j; } private void parseValue(char[] charBuffer, int pos, int start, int length) { float value = (float) TextUtils.parseDouble(charBuffer, start, length, '.'); if (value == missingValue) value = Float.NaN; float valueResolution = TextUtils.getValueResolution(charBuffer, start, length, '.'); if (valueResolution < minValueResolution) minValueResolution = valueResolution; if (floatBuffer.length == pos) floatBuffer = Clasz.floats.ensureCapacity(floatBuffer, pos + 1); floatBuffer[pos] = value; } private void parseHeader() throws Exception { domainCount = 0; Arrays.fill(domainParameterIds, null); Arrays.fill(domainUnits, null); if (axisValues != null) Arrays.fill(axisValues, null); reader.require(XMLStreamConstants.START_ELEMENT, null, "header"); if (reader.getAttributeCount() > 0) { throw new Exception("Attributes are not allowed for header element "); } reader.nextTag(); initHeader(); do { detectHeaderElement(); parseHeaderElement(); } while (reader.getEventType() != XMLStreamConstants.END_ELEMENT); if (header.getForecastTime() == Long.MIN_VALUE) header.setForecastTime(startTime); if (!Double.isNaN(lat)) header.setGeometry(GeoDatum.WGS_1984.createLatLongZ(lat, lon, z)); initiateTimeStep(); header.setTimeStep(timeStep); if (!qualifiers.isEmpty()) header.setQualifierIds(Clasz.strings.newArrayFrom(qualifiers)); if (creationDateText != null) { try { long creationTime = fastDateFormat.parseToMillis(creationDateText, creationTimeText); header.setCreationTime(creationTime); } catch (ParseException e) { throw new Exception("Can not parse creation date/time " + creationDateText + ' ' + creationTimeText); } } if (startTime != Long.MIN_VALUE && endTime != Long.MIN_VALUE) { timeSeriesContentHandler.setEstimatedPeriod(new Period(startTime, endTime)); } domainParameterIds = Clasz.strings.resizeArray(domainParameterIds, domainCount); domainUnits = Clasz.strings.resizeArray(domainUnits, domainCount); header.setDomainParameterIds(domainParameterIds); header.setDomainUnits(domainUnits); timeSeriesContentHandler.setNewTimeSeriesHeader(header); reader.require(XMLStreamConstants.END_ELEMENT, null, "header"); reader.nextTag(); } @SuppressWarnings("OverlyLongMethod") private void parseEvent() throws Exception { assert binaryInputStream == null; reader.require(XMLStreamConstants.START_ELEMENT, null, "event"); timeSeriesContentHandler.clearFlagSourceColumns(); String dateText = null; String timeText = null; String startDateText = null; String startTimeText = null; String endDateText = null; String endTimeText = null; String valueText = null; String valueSource = null; String minValueText = null; String maxValueText = null; String flagText = null; String flagSource = null; String comment = null; this.virtualInputDirString user = virtualInputDirnull; String limitText = }null; private enum HeaderElement { for (int i = 0, n = type(F.R), locationId(F.R), reader.getAttributeCount(); i < n; i++) { parameterId(F.R), qualifierId(F.M), ensembleId, ensembleMemberIndex, ensembleMemberId, String localName = reader.getAttributeLocalName(i); timeStep(F.R | F.A), startDate(F.R | F.A), endDate(F.R | F.A), forecastDate(F.A), String attributeValue = reader.getAttributeValue(i); missVal, longName, stationName, lat, lon, x, y, z, units, if (dateText == null && TextUtils.equals(localName, "date")) { sourceOrganisation, sourceSystem, fileDescription, dateText = attributeValue; creationDate, creationTime, region, thresholds; } else if (timeText == null interface F&& TextUtils.equals(localName, "time")) { int A = 1timeText <<= 0attributeValue; // attributes } else intif R(valueText == 1 << 1; // required; null && TextUtils.equals(localName, "value")) { int MvalueText = 1attributeValue; << 2; // multiple; } else if (valueSource == null && private final int flags; TextUtils.equals(localName, "valueSource")) { HeaderElement() { valueSource = attributeValue; this.flags = 0; } else if (flagText == null HeaderElement(int flags&& TextUtils.equals(localName, "flag")) { this.flagsflagText = flagsattributeValue; } } else if (flagSource == null public boolean isRequired(&& TextUtils.equals(localName, "flagSource")) { return (flags & F.R)flagSource != 0attributeValue; } } else if (comment == publicnull boolean hasAttributes(&& TextUtils.equals(localName, "comment")) { return (flags & F.A)comment != 0attributeValue; } } else if (user == publicnull boolean isMultipleAllowed(&& TextUtils.equals(localName, "user")) { return (flags & F.M)user != 0attributeValue; } else if (startDateText == null && TextUtils.equals(localName, "startDate")) { } // fastDateFormatstartDateText is= usedattributeValue; to keep track of last time zone and lenient } privateelse FastDateFormatif fastDateFormat(startTimeText == FastDateFormat.getInstance("yyyy-MM-dd", "HH:mm:ss", TimeZoneUtils.GMT, Locale.US, null); null && TextUtils.equals(localName, "startTime")) { private HeaderElement currentHeaderElementstartTimeText = nullattributeValue; private static final HeaderElement[] HEADER_ELEMENTS = HeaderElement.class.getEnumConstants(); private PiTimeSeriesHeader header = new PiTimeSeriesHeader(); } else if (endDateText == null && TextUtils.equals(localName, "endDate")) { private List<String> qualifiers = new ArrayList<>(); private long timeStepMillisendDateText = 0attributeValue; private TimeStep timeStep = null; } privateelse longif startTime(endTimeText = Long.MIN_VALUE; private long endTime = Long.MIN_VALUE; = null && TextUtils.equals(localName, "endTime")) { private float missingValue = Float.NaN; private String creationDateTextendTimeText = nullattributeValue; private String creationTimeText = null; } privateelse TimeSeriesContentHandlerif timeSeriesContentHandler(minValueText == null; private PiTimeSeriesSerializer.EventDestination eventDestination = null; && TextUtils.equals(localName, "minValue")) { /** * For performance reasons the pi timeminValueText series= formatattributeValue; allows that the values are stored in *} aelse separateif bin(maxValueText file== insteadnull of embedded in the xml file. && TextUtils.equals(localName, "maxValue")) { * The bin file should have same name as themaxValueText xml= fileattributeValue; except the extension equals bin * In this} caseelse allif time(maxValueText series== shouldnull be&& equidistant. TextUtils.equals(localName, "detection")) { */ private VirtualInputDir virtualInputDirlimitText = VirtualInputDir.NONEattributeValue; private InputStream binaryInputStream = null; } privateelse byte[] byteBuffer if (reader.getAttributePrefix(i) != null; && private float[] floatBuffer = null; TextUtils.equals(reader.getAttributePrefix(i), "fs")) { private int bufferPos = 0; private int bufferCountindex = 0timeSeriesContentHandler.addFlagSourceColumn(localName); private XMLStreamReader reader = null; private String virtualFileName = null timeSeriesContentHandler.setColumnFlagSource(index, attributeValue); private static boolean lenient = false; private} doubleelse lat{ = Double.NaN; private double lon = Double.NaN; private double zif = Double.NaN(lenient) continue; /** * For backwards compatibility. Earlier versions ofthrow the PiTimeSeriesParser were tolerant about the date/time format * and the case insensitive for header element names. * This parser should not accept files that are not valid according to pi_timeseries.xsd * When old adapters are not working you can UseLenientPiTimeSeriesParser temporary till the adapter is fixed * * @param lenient */ public static void setLenient(boolean lenient) {new Exception("Unknown or duplicate attribute " + localName + " in event"); } } parseTime(dateText, timeText, startDateText, startTimeText, endDateText, endTimeText); parseFlagsUserComment(flagText, flagSource, comment, user); parseValue(valueText, valueSource, minValueText, maxValueText, limitText); timeSeriesContentHandler.applyCurrentFields(); reader.require(XMLStreamConstants.END_ELEMENT, null, "event"); PiTimeSeriesParser.lenient = lenientreader.nextTag(); } public static boolean isLenient() { return lenient;private void parseValue(String valueText, String valueSource, String minValueText, String maxValueText, String limit) throws Exception { } publicif PiTimeSeriesParser(domainCount == 0) { fastDateFormat.setLenient(lenient); try }{ @Override public void parse(XMLStreamReader reader, String virtualFileName, TimeSeriesContentHandler timeSeriesContentHandler) throws Exception { float value = valueText == null ? thisFloat.readerNaN = reader: TextUtils.parseFloat(valueText); this.virtualFileName = virtualFileName; float minValue = this.timeSeriesContentHandlerminValueText == timeSeriesContentHandler; null ? value : TextUtils.parseFloat(minValueText); String virtualBinFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin"); //float timemaxValue zone= canmaxValueText be== overrulednull by? onevalue or more time zone elements in the pi file : TextUtils.parseFloat(maxValueText); this.fastDateFormat.setTimeZone(timeSeriesContentHandler.getDefaultTimeZone()); if (!virtualInputDir.exists(virtualBinFileName)) { eventDestination = PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED; // we can not use the automatic missing value detection of the content handler because the missing value is different for each time series parse(); if (value == missingValue) { return; } eventDestinationvalue = PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILEFloat.NaN; binaryInputStream = virtualInputDir.getInputStream(virtualBinFileName); } tryelse { if (byteBuffer == null) { timeSeriesContentHandler.setValueResolution(TextUtils.getValueResolution(valueText, '.')); byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE]; } floatBuffer = new float[BUFFER_SIZE]timeSeriesContentHandler.setValueAndRange(value, minValue, maxValue); } timeSeriesContentHandler.setValueSource(TextUtils.equals(valueSource, "MAN") ? ValueSource.MANUAL parse(: ValueSource.AUTOMATIC); boolean eof = bufferPos == bufferCount && binaryInputStream.read() == -1; timeSeriesContentHandler.setOutOfDetectionRangeFlag(OutOfDetectionRangeFlag.get(getOutOfDetectionFlag(limit))); } ifcatch (!eof)NumberFormatException e) { throw new IOException("More values available in bin file than expected based on time step and start and end time\n" + FileUtils.getPathWithOtherExtension(virtualFileName, "bin")); Exception("Value should be a float " + valueText); } reader.nextTag(); } finally { return; } if bufferPos(valueText != 0;null) bufferCountthrow = 0; binaryInputStream.close(new Exception("Attribute value not allowed when having domain parameters, use event element text instead"); int count binaryInputStream = nullparseValueList(); } if (count == 0) } private void parse() throws Exception { { readertimeSeriesContentHandler.require(XMLStreamConstants.START_DOCUMENT, null, nullsetValues(null); reader.nextTag() return; reader.require(XMLStreamConstants.START_ELEMENT, null, "TimeSeries"); } int expectedCount = reader.nextTag()1; while (reader.getEventType() != XMLStreamConstants.END_ELEMENTfor (int i = 0, n = domainCount; i < n; i++) { float[] axis parseTimeZone(); = axisValues[i]; if readTimeSeries(axis == null); } throw new reader.require(XMLStreamConstants.END_ELEMENT, null, "TimeSeries"); reader.next(); Exception("Domain axis values for domain parameter " + domainParameterIds[i] + " are missing"); reader.require(XMLStreamConstants.END_DOCUMENT, null, null); expectedCount } *= axis.length; private void readTimeSeries() throws Exception {} reader.require(XMLStreamConstants.START_ELEMENT, null, "series"); if (expectedCount != count) reader.nextTag(); throw new Exception("Length of value parseHeader(); list for event does not matches the timeSeriesContentHandler.setProperties(Properties.NONEaxes lengths"); if (eventDestination == PiTimeSeriesSerializer.EventDestination.XML_EMBEDDEDaxesDirty) { while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { timeSeriesContentHandler.setDomainAxesValueResolutions(axisValueResolutions); timeSeriesContentHandler.setDomainAxesValues(axisValues); String localNameaxesDirty = reader.getLocalName()false; } if (TextUtils.equals(localName, "event")) {count != floatBuffer.length) floatBuffer = FloatArrayUtils.resize(floatBuffer, expectedCount); timeSeriesContentHandler.setValueResolution(minValueResolution); parseEvent(timeSeriesContentHandler.setValues(floatBuffer); } private byte getOutOfDetectionFlag(String text) { } else if (TextUtils.equals(localName, "properties")) {text == null || text.isEmpty()) return TimeSeriesArray.INSIDE_DETECTION_RANGE; char ch parseProperties(= text.charAt(0); if (ch == '<') return TimeSeriesArray.BELOW_DETECTION_RANGE; } else { if (ch == '>') return TimeSeriesArray.ABOVE_DETECTION_RANGE; if (ch == '~') return breakTimeSeriesArray.VARYING; return TimeSeriesArray.INSIDE_DETECTION_RANGE; } private void parseFlagsUserComment(String flagText, String flagSource, String comment, } String user) throws Exception { if (reader.getEventType()flagText == XMLStreamConstants.START_ELEMENTnull) { // skip commenttimeSeriesContentHandler.setFlag(0); } reader.require(XMLStreamConstants.START_ELEMENT, null, "comment");else { reader.getElementText();try { reader.nextTag(timeSeriesContentHandler.setFlag(TextUtils.parseInt(flagText)); } catch (NumberFormatException e) { } else { throw new Exception("Flag should be an assertinteger eventDestination" == PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE+ flagText); readValuesFromBinFile();} } readertimeSeriesContentHandler.require(XMLStreamConstants.END_ELEMENT, null, "series"setComment(comment); timeSeriesContentHandler.setUser(user); readertimeSeriesContentHandler.nextTagsetFlagSource(flagSource); } private void parseHeader() throws Exception { reader.require(XMLStreamConstants.START_ELEMENT, null, "header");parseTime(String dateText, String timeText, String startDateText, String startTimeText, String endDateText, String endTimeText) throws Exception { if (reader.getAttributeCount() > 0) {timeText == null) throw new Exception("AttributesAttribute aretime not allowed for header element is missing"); } if (dateText == null) reader.nextTag(); throw new Exception("Attribute date is initHeader(missing"); dotry { detectHeaderElement(long time = parseTime(dateText, timeText, null, null, Long.MIN_VALUE, lastTime, lastTimeAmbiguous); parseHeaderElement()lastTime = time; } while (reader.getEventType() != XMLStreamConstants.END_ELEMENT); lastTimeAmbiguous = timeAmbiguous; long startTime if= (header.getForecastTime() == Long.MIN_VALUE) header.setForecastTime(startTime); parseTime(startDateText, startTimeText, dateText, timeText, time, lastStartTime, lastStartTimeAmbiguous); if (!Double.isNaN(lat)) header.setGeometry(new PointGeometry(GeoDatum.WGS_1984.createLatLongZ(lat, lon, z))) lastStartTime = startTime; initiateTimeStep() lastStartTimeAmbiguous = timeAmbiguous; header.setTimeStep(timeStep); long endTime = if (!qualifiers.isEmpty()) header.setQualifierIds(qualifiers.toArray(new String[qualifiers.size()])); parseTime(endDateText, endTimeText, dateText, dateText, time, lastEndTime, lastEndTimeAmbiguous); if (creationDateText != null) { lastEndTime = endTime; try { lastEndTimeAmbiguous = timeAmbiguous; long creationTime = fastDateFormattimeSeriesContentHandler.parseToMillis(creationDateTextsetTimeAndRange(time, startTime, creationTimeTextendTime); } catch (ParseException e) { header.setCreationTime(creationTime); throw new Exception("Can not parse " + dateText + }' catch' (ParseException+ etimeText) {; } } throwprivate newlong Exception("Can not parse creation date/time " + creationDateText + ' ' + creationTimeText); parseTime(String dateText, String timeText, String defaultDateText, String defaultTimeText, long defaultTime, long lastTime, boolean lastTimeAmbiguous) throws ParseException { if } (dateText == null && timeText == null) return }defaultTime; if (startTimedateText !== Long.MIN_VALUE && endTime != Long.MIN_VALUE) {null) dateText = defaultDateText; if (timeText == timeSeriesContentHandler.setEstimatedPeriod(new Period(startTime, endTime));null) timeText = defaultTimeText; } boolean useMillis timeSeriesContentHandler.setNewTimeSeriesHeader(header= timeText.contains("."); if reader(!fastDateFormat.requiregetTimeZone(XMLStreamConstants.END_ELEMENT, null, "header"); reader.nextTag(); ).useDaylightTime()) return useMillis? fastDateFormatWithMillies.parseToMillis(dateText, timeText): fastDateFormat.parseToMillis(dateText, timeText); } @SuppressWarnings("OverlyLongMethod")long t1; private void parseEvent() throws Exceptiontry { assert binaryInputStream == null; t1 = useMillis?fastDateFormatWithMillies.parseToMillis(dateText, timeText): readerfastDateFormat.require(XMLStreamConstants.START_ELEMENT, null, "event"parseToMillis(dateText, timeText); String} dateTextcatch = null; String timeText = null; (ParseException e) { String valueText = null; if (!timeText.equals("02:00:00")) String flagText = nullthrow e; String flagSource = null; // see FEWS-17782 also accept 02:00:00 on Stringdaylight commentsaving =time null;switch String user =try null;{ for (int i = 0, n =return readerfastDateFormat.getAttributeCount(parseToMillis(dateText, "03:00:00"); i < n; i++) { } catch (ParseException e1) String{ localName = reader.getAttributeLocalName(i); String attributeValue = reader.getAttributeValue(i)throw e; } if (dateText == null && TextUtils.equals(localName, "date")) {} Calendar calendar = dateText = attributeValueuseMillis? fastDateFormatWithMillies.getCalendar() : fastDateFormat.getCalendar(); calendar.setTimeInMillis(t1); } else if (timeText ==int nulltimeOfDay && TextUtils.equals(localName, "time")) {= getTimeOfDay(calendar); long t2 = getOtherTime(calendar, t1, timeOfDay); timeText = attributeValue; timeAmbiguous = t1 != t2; } else iflong (valueTextminTime == null && TextUtils.equals(localName, "value")) { Math.min(t1, t2); long maxTime = Math.max(t1, t2); long valueTextres = attributeValue; minTime > lastTime ? minTime : maxTime; } else if (flagText == null && TextUtils.equals(localName, "flag")) { !lastTimeAmbiguous) return res; // see FEWS-17782 also accept two times 02:00:00 instead of two times flagText01:00:00 = attributeValue; on daylight saving time switch } else if (flagSourcetimeStepMillis !== null0L && TextUtils.equals(localName, "flagSource")) { lastTime + timeStepMillis != res) return lastTime + timeStepMillis; if (timeStep != null && timeStep.isRegular() && timeStep.nextTime(lastTime) != flagSourceres) = attributeValuereturn timeStep.nextTime(lastTime); return res; } else if (comment ==private nullstatic && TextUtils.equals(localName, "comment"))int getTimeOfDay(Calendar calendar) { return (int) (calendar.get(Calendar.HOUR_OF_DAY) * TimeUnit.HOUR_MILLIS + calendar.get(Calendar.MINUTE) * comment = attributeValue; } else if (user == null && TextUtils.equals(localName, "user")) {TimeUnit.MINUTE_MILLIS + calendar.get(Calendar.SECOND) * TimeUnit.SECOND_MILLIS + calendar.get(Calendar.MILLISECOND)); } private static long getOtherTime(Calendar calendar, long time, int user = attributeValue;timeOfDay) { try } else { throw new Exception("Unknown attribute " + localName + " in event"); } calendar.setTimeInMillis(time + TimeUnit.HOUR_MILLIS); } if (timeTextgetTimeOfDay(calendar) == null) timeOfDay) return time + TimeUnit.HOUR_MILLIS; throw new Exception("Attribute calendar.setTimeInMillis(time is missing"- TimeUnit.HOUR_MILLIS); if (getTimeOfDay(dateTextcalendar) == nulltimeOfDay) return time - TimeUnit.HOUR_MILLIS; throw new Exception("Attribute date isreturn missing")time; } finally if{ (valueText == null) calendar.setTimeInMillis(time); throw new Exception("Attribute value is missing"); } } private long putValueparseTime(dateText,) timeText,throws valueText, flagText, flagSource, comment, user); Exception { String dateText = reader.nextTag(getAttributeValue(null, "date"); reader.require(XMLStreamConstants.END_ELEMENT, null, "event");if (dateText == null) { reader.nextTag(); } throw new Exception("Attribute " + currentHeaderElement + private void putValue(String dateText, String timeText, String valueText, String flagText, String flagSource, String comment, String user) throws"-date Exception {is missing"); try {} String timeText = timeSeriesContentHandlerreader.setTime(fastDateFormat.parseToMillis(dateTextgetAttributeValue(null, timeText"time")); }if catch (ParseException e(timeText == null) { throw new Exception("CanAttribute not" parse+ "currentHeaderElement + dateText + ' ' + timeText); } "-time is missing"); if (flagText == null) {} boolean useMillis timeSeriesContentHandler.setFlag(0= timeText.contains("."); } else { long time; try { time = useMillis? timeSeriesContentHandlerfastDateFormatWithMillies.setFlag(TextUtils.parseInt(flagText))parseToMillis(dateText, timeText) : fastDateFormat.parseToMillis(dateText, timeText); } catch (NumberFormatExceptionParseException e) { throw new Exception("FlagNot a shouldvalid bedata antime integerfor " + flagText); } + currentHeaderElement + ' } ' + dateText + ' ' + timeSeriesContentHandler.setComment(comment); timeText, e); } timeSeriesContentHandlerreader.setUsernextTag(user); timeSeriesContentHandler.setFlagSource(flagSource)return time; } private void parseTimeStep() throws tryException { String times float value = TextUtilsreader.parseFloat(valueTextgetAttributeValue(null, "times"); if (times != // we can not use the automatic missing value detection of the content handler because the missing value is different for each time seriesnull) { timeStep = PiCastorUtils.createTimesOfDayTimeStep(times, getTimeZone()); if (value == missingValue) {reader.nextTag(); return; value = Float.NaN; } String unit } else {= reader.getAttributeValue(null, "unit"); TimeUnit tu = unit == null ? timeSeriesContentHandler.setValueResolution(TextUtils.getValueResolution(valueText, '.'))null : TimeUnit.get(unit); if (tu != null) }{ timeSeriesContentHandler.setValue(valueString multiplierText = reader.getAttributeValue(null, "multiplier"); timeSeriesContentHandler.applyCurrentFields()int multiplier; } catchif (NumberFormatException emultiplierText == null) { throw new Exception("Value should bemultiplier a float " + valueText)= 1; } } private long parseTime() throws Exception { else { String dateText = reader.getAttributeValue(null, "date"); try if{ (dateText == null) { throw new Exception("Attribute " +multiplier currentHeaderElement += Integer.parseInt(multiplierText); } catch (NumberFormatException e) { "-date is missing"); } String timeTextthrow =new readerException(ExceptionUtils.getAttributeValuegetMessage(nulle), "time"e); if (timeText == null) {} throw newif Exception("Attribute " + currentHeaderElement + (multiplier == 0) { throw new Exception("-timeMultiplier is missing0"); } } long time; try {} timeString dividerText = fastDateFormatreader.parseToMillisgetAttributeValue(dateText, timeText); } catch (ParseException e) { null, "divider"); int throwdivider; new Exception("Not a valid data time for " if (dividerText == null) { + currentHeaderElement + ' 'divider += dateText1; + ' ' + timeText, e); } else }{ reader.nextTag(); returntry time;{ } private long parseTimeStep() throws Exception { String unitdivider = readerInteger.getAttributeValue(null, "unit"parseInt(dividerText); if (unit == null) { throw new Exception("Attribute unit is missing in " + currentHeaderElement); } catch (NumberFormatException e) { } TimeUnit tuthrow =new TimeUnitException(ExceptionUtils.get(unitgetMessage(e), e); if (tu != null) { } String multiplierText = reader.getAttributeValue(null, "multiplier"); if (divider == 0) { int multiplier; throw ifnew Exception(multiplierText"divider ==is null0"); { } multiplier = 1; } } else { reader.nextTag(); try { timeStepMillis = tu.getMillis() * multiplier / divider; multipliertimeStep = Integer.parseInt(multiplierText); null; } else { } catch reader.nextTag(NumberFormatException e); { timeStepMillis = 0; throw new Exception(ExceptionUtils.getMessage(e), e); timeStep = IrregularTimeStep.INSTANCE; } } private void initHeader() { header.clear(); if (multiplier == 0) { header.setFileDescription(virtualFileName); currentHeaderElement = null; timeStep = thrownull; new Exception("Multiplier is 0"); timeStepMillis = 0; startTime } = Long.MIN_VALUE; endTime = Long.MIN_VALUE; } missingValue = Float.NaN; String dividerTextcreationDateText = reader.getAttributeValue(null, "divider"); creationTimeText = "00:00:00"; int divider; qualifiers.clear(); if (dividerTextlat == null) { Double.NaN; lon = Double.NaN; dividerz = 1Double.NaN; } private void readValuesFromBinFile() throws } elseException { TimeStep timeStep = header.getTimeStep(); tryif (!timeStep.isRegular()) { throw new Exception("Only equidistant time step supported when dividerpi = Integer.parseInt(dividerText); events are stored in bin file instead of xml"); } catch (NumberFormatException e) { boolean equidistantMillis = timeStep.isEquidistantMillis(); long stepMillis = equidistantMillis ? throw new Exception(ExceptionUtils.getMessage(e), e)timeStep.getStepMillis() : Long.MIN_VALUE; for (long time = startTime; time <= endTime;) }{ timeSeriesContentHandler.setTime(time); if (divider == 0) { if (bufferPos == bufferCount) fillBuffer(); throwfloat new Exception("divider is 0")value = floatBuffer[bufferPos++]; // we can not } use the automatic missing value detection of the content handler because } reader.nextTag();the missing value is different for each time series returnif tu.getMillis((value == missingValue) *value multiplier / divider= Float.NaN; } else { timeSeriesContentHandler.setValue(value); readertimeSeriesContentHandler.nextTagapplyCurrentFields(); if return 0;(equidistantMillis) { } } privatetime void initHeader() {+= stepMillis; header.clear(); header.setFileDescription(virtualFileName)continue; currentHeaderElement = null; } timeSteptime = nulltimeStep.nextTime(time); timeStepMillis} = 0;} private void fillBuffer() throws startTime = Long.MIN_VALUE;IOException { endTimeint byteBufferCount = Long.MIN_VALUE0; while missingValue(byteBufferCount =% Float.NaN; creationDateText = null; NumberType.FLOAT_SIZE != 0 || byteBufferCount == 0) { creationTimeText = "00:00:00"; qualifiers.clear(int count = binaryInputStream.read(byteBuffer, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE - byteBufferCount); lat = Double.NaN; assert count != 0; // see lonread = Double.NaN; javadoc z = Double.NaN; if (count == } private void readValuesFromBinFile() throws Exception { -1) throw new EOFException("Bin file is too short"); TimeStep timeStepbyteBufferCount += header.getTimeStep()count; if (!timeStep.isRegular()) {} bufferCount = byteBufferCount throw new Exception("Only equidistant time step supported when pi events are stored in bin file instead of xml"/ NumberType.FLOAT_SIZE; BinaryUtils.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIAN); } bufferPos = 0; } booleanprivate equidistantMillis = timeStep.isEquidistantMillisvoid initiateTimeStep(); { longif stepMillis(timeStep != equidistantMillis ? timeStep.getStepMillis() : Long.MIN_VALUE;null) { for (long time = startTime;assert timetimeStepMillis <== endTime0;) { timeSeriesContentHandler.setTime(time)return; } if (bufferPostimeStepMillis == bufferCount0) fillBuffer(); { //no timestep in header. floatFix valuefor = floatBuffer[bufferPos++];backward compatibility timeStep = IrregularTimeStep.INSTANCE; // we can not use thereturn; automatic missing value detection of the content handler} because the missing value is different for eachif time series (timeStepMillis >= TimeUnit.HOUR_MILLIS && getTimeZone().useDaylightTime()) { if (value == missingValue) valuetimeStep = FloatIrregularTimeStep.NaNINSTANCE; timeSeriesContentHandler.setValue(value)return; } timeSeriesContentHandler.applyCurrentFields(); long startTime = this.startTime == Long.MIN_VALUE ? if0L (equidistantMillis) {: this.startTime; if (timeStepMillis % TimeUnit.SECOND_MILLIS != time += stepMillis;0) { timeStep = continueRelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime); }return; } time = timeStep.nextTime(time); long timeZoneOffsetMillis = -startTime % }timeStepMillis; } if private(timeZoneOffsetMillis void fillBuffer() throws IOException% TimeUnit.MINUTE_MILLIS != 0) { int byteBufferCounttimeStep = 0 RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime); while (byteBufferCount % NumberType.FLOAT_SIZE != 0 || byteBufferCount == 0) { return; } inttimeStep count = binaryInputStreamSimpleEquidistantTimeStep.readgetInstance(byteBuffer, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE - byteBufferCount);timeStepMillis, timeZoneOffsetMillis); } @SuppressWarnings({"OverlyLongMethod"}) private void parseHeaderElement() throws Exception { assert count != 0; // seeswitch read(currentHeaderElement) javadoc{ case if (count == -1) throw new EOFException("Bin file is too short"); type: header.setParameterType(parseType(reader.getElementText())); byteBufferCount += countbreak; } case moduleInstanceId: bufferCount = byteBufferCount / NumberType.FLOAT_SIZE; BinaryUtilsheader.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIANsetModuleInstanceId(reader.getElementText()); bufferPos = 0; }break; private void initiateTimeStep() { case locationId: if (timeStepMillis == 0) { // see timeStep = IrregularTimeStep.INSTANCE; return;FEWS-9858, when there is no location id the time series are assigned to all locations } long startTime =// this.startTime ==is a Long.MIN_VALUE ? 0L : this.startTime; if (timeStepMillis % TimeUnit.SECOND_MILLIS != 0) { flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty strings timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTimeheader.setLocationId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none")); return; break; } long timeZoneOffsetMilliscase =parameterId: -startTime % timeStepMillis; if (timeZoneOffsetMillis % TimeUnit.MINUTE_MILLIS != 0) { timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime);// see FEWS-9858, when there is no parameter id the time series are assigned to all locations return; // this is a } flaw in the pi_timeSeries.xsd, the location element is timeSteprequired = SimpleEquidistantTimeStep.getInstance(timeStepMillis, timeZoneOffsetMillis); } but is allowed empty strings private void parseTimeZone() throws Exception { if header.setParameterId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getEventTypegetElementText()) != XMLStreamConstants.START_ELEMENT) return; , "none")); if (!TextUtils.equals(reader.getLocalName(), "timeZone")) return break; try { case qualifierId: String timeZoneText = qualifiers.add(reader.getElementText()); // element name="timeZone" type="fews:TimeZoneSimpleType" default="0.0" minOccurs="0"/> break; // when default is used in schema for element the consequence is that empty strings are allowedcase ensembleId: header.setEnsembleId(reader.getElementText()); double offset = timeZoneText.isEmpty() ? 0d : Double.parseDouble(timeZoneText); break; case ensembleMemberIndex: TimeZone timeZoneFromDouble = TimeZoneUtils.createTimeZoneFromDouble(offset); this.fastDateFormat.setTimeZone(timeZoneFromDoubleheader.setEnsembleMemberIndex(parseEnsembleMemberIndex(reader.getElementText())); } catch (NumberFormatException e) { break; throw new Exception("Not valid timeZone format", e);case ensembleMemberId: } header.setEnsembleMemberId(reader.require(XMLStreamConstants.END_ELEMENT, null, "timeZone"getElementText()); reader.nextTag() break; } @SuppressWarnings({"OverlyLongMethod"}) case timeStep: private void parseHeaderElement() throws Exception { switch parseTimeStep(currentHeaderElement); { case type:break; case startDate: header.setParameterType(parseType(reader.getElementText())); startTime = breakparseTime(); case locationId: break; case //endDate: see FEWS-9858, when there is no location id the time series are assigned to all locations endTime = parseTime(); // this isbreak; a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty stringscase forecastDate: header.setLocationId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementTextsetForecastTime(parseTime()), "none")); break; case parameterIdapprovedDate: // see FEWS-9858, when there is no parameter id the time series are assigned to all locations header.setApprovedTime(parseTime()); // this is a flawbreak; in the pi_timeSeries.xsd, the location element is required but is allowed emptycase stringsmissVal: header.setParameterId(TextUtils.defaultIfNull(TextUtils.trimToNull(missingValue = parseString(reader.getElementText()), "none")); break; case qualifierIdlongName: qualifiersheader.addsetLongName(reader.getElementText()); break; case ensembleIdstationName: header.setEnsembleIdsetLocationName(reader.getElementText()); break; case ensembleMemberIndexunits: header.setEnsembleMemberIndex(parseEnsembleMemberIndexsetUnit(reader.getElementText())); break; case ensembleMemberIddomainAxis: header.setEnsembleMemberId(reader.getElementText())parseDomainAxis(); break; case timeStepsourceOrganisation: timeStepMillis = parseTimeStep(header.setSourceOrganisation(reader.getElementText()); break; case startDatesourceSystem: startTime = parseTimeheader.setSourceSystem(reader.getElementText()); break; case endDatefileDescription: endTime = parseTimeheader.setFileDescription(reader.getElementText()); break; case forecastDatecreationDate: header.setForecastTime(parseTime()creationDateText = reader.getElementText(); break; case missValcreationTime: missingValuecreationTimeText = parseValue(reader.getElementText()); break; case longNameregion: header.setLongNamesetRegion(reader.getElementText()); break; case stationNamethresholds: header.setLocationName(reader.getElementTextparseThresholds()); break; case unitslat: header.setUnitlat = parseString(reader.getElementText()); break; case sourceOrganisationlon: header.setSourceOrganisationlon = parseString(reader.getElementText()); break; case sourceSystemx: header.setSourceSystem(reader.getElementText()); break; case fileDescriptiony: header.setFileDescription(reader.getElementText()); break; case creationDatez: creationDateTextz = parseString(reader.getElementText()); break; case creationTimefirstValueTime: creationTimeText = reader.getElementText(); break; case regionlastValueTime: header.setRegion(reader.getElementText()); break; case thresholdsmaxValue: parseThresholdsreader.getElementText(); break; case latminValue: lat = parseValue(reader.getElementText()); break; case lonvalueCount: lon = parseValue(reader.getElementText()); break; case xmaxWarningLevelName: reader.getElementText(); break; } case y: reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name()); reader.getElementTextnextTag(); } private void parseDomainAxis() throws Exception break;{ String parameterId = case z:reader.getAttributeValue(null, "parameterId"); if z = parseValue(reader.getElementText());(parameterId == null) throw new Exception("Attribute parameterId break; for domainUnits is missing"); } domainParameterIds reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name()= Clasz.strings.ensureCapacity(domainParameterIds, domainCount + 1); domainUnits reader.nextTag(= Clasz.strings.ensureCapacity(domainUnits, domainCount + 1); } private void parseThresholds() throws XMLStreamException { domainParameterIds[domainCount] = parameterId; domainUnits[domainCount] = reader.nextTag(getAttributeValue(null, "units"); ArrayList<String> ids = new ArrayList<>()domainCount++; ArrayList<String> names = new ArrayList<>reader.nextTag(); } ArrayList<String>private stringValues = new ArrayList<>void parseThresholds(); throws XMLStreamException { ArrayList<String> groupIds = new ArrayList<> reader.nextTag(); ArrayList<String>ArrayList<DefaultTimeSeriesHeader.DefaultThreshold> groupNamesthresholds = new ArrayList<>(); do { if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { ids.add(String id = reader.getAttributeValue(null, "id")); names.add(reader.getAttributeValue(null, "name")); String name = stringValues.add(reader.getAttributeValue(null, "valuename")); float value = groupIdsTextUtils.addparseFloat(reader.getAttributeValue(null, "groupIdvalue")); groupNames.add(String groupId = reader.getAttributeValue(null, "groupNamegroupId")); } String groupName = reader.nextTag(getAttributeValue(null, "groupName"); } while (!reader.getLocalName().equals(currentHeaderElement.name())); floatString[] groupIds = valuesgroupId == null new float[stringValues.size()]; for (int i = 0; i < values.length; i++) { ? Clasz.strings.emptyArray() : new String[] {groupId}; valuesString[i] groupNames = Float.parseFloat(stringValues.get(i))groupName == null ? Clasz.strings.emptyArray() : new String[] {groupName}; } headerthresholds.setHighLevelThresholdsadd(ids.toArray(new String[idsDefaultTimeSeriesHeader.sizeDefaultThreshold()])id, names.toArray(new String[names.size()]), values, name, value, groupIds, groupNames)); } groupIds.toArray(new String[groupIds.size()]), groupNames.toArray(new String[groupNames.size()]) reader.nextTag(); } while (!reader.getLocalName().equals(currentHeaderElement.name())); header.setHighLevelThresholds(TimeSeriesHeader.Threshold.clasz.newArrayFrom(thresholds)); } private void parseProperties() throws XMLStreamException { reader.require(XMLStreamConstants.START_ELEMENT, null, "properties"); reader.nextTag(); propertyBuilder.clear(); while (!TextUtils.equals(reader.getLocalName(), "properties")){ if (reader.getEventType() != XMLStreamConstants.START_ELEMENT) { // eg <int key="a" value=12><int> reader.nextTag(); continue; } String key = reader.getAttributeValue(null, "key"); String value = reader.getAttributeValue(null, "value"); String date = reader.getAttributeValue(null, "date"); String time = reader.getAttributeValue(null, "time"); switch (reader.getLocalName()) { case "string" : propertyBuilder.addString(key, value); break; case "int": propertyBuilder.addInt(key, TextUtils.parseInt(value)); break; case "float": propertyBuilder.addFloat(key, TextUtils.parseFloat(value)); break; case "double": propertyBuilder.addDouble(key, TextUtils.parseDouble(value)); break; case "bool": propertyBuilder.addBoolean(key, Boolean.parseBoolean(value)); ; break; case "dateTime": break; try { case "bool": if propertyBuilder(time.addBoolean(key, Boolean.parseBoolean(value));contains(".")) { break; propertyBuilder.addDateTime(key, fastDateFormatWithMillies.parseToMillis(date, time)); case "dateTime": } else { try { propertyBuilder.addDateTime(key, fastDateFormat.parseToMillis(date, time)); } break; } catch (ParseException e) { throw new XMLStreamException("Invalid date time "+ date + ' ' + time); } default: throw new XMLStreamException("Invalid property type " + reader.getLocalName()); } reader.nextTag(); } timeSeriesContentHandler.setProperties(propertyBuilder.build()); reader.require(XMLStreamConstants.END_ELEMENT, null, "properties"); reader.nextTag(); } private static float parseValueparseString(String gotString) throws Exception { // <element name="missVal" type="double" default="NaN"> // when default is used in schema for element the consequence is that empty strings are allowed if (gotString.isEmpty()) return Float.NaN; try { return TextUtils.parseFloat(gotString); } catch (NumberFormatException e) { throw new Exception(ExceptionUtils.getMessage(e), e); } } private static int parseEnsembleMemberIndex(String gotString) throws Exception { int index = Integer.parseInt(gotString); if (index < 0) { throw new Exception("Negative ensemble member index not allowed " + gotString); } return index; } private static ParameterType parseType(String gotString) throws Exception { ParameterType type = ParameterType.get(gotString); if (type == null) { throw new Exception("Type in header should be instantaneous or accumulative and not " + gotString); } return type; } private void detectHeaderElement() throws Exception { if (reader.getEventType() != XMLStreamConstants.START_ELEMENT) throw new Exception("header element expected"); String localName = reader.getLocalName(); HeaderElement element; try { element = Enum.valueOf(HeaderElement.class, localName); } catch (Exception e) { throw new Exception("Unknown header element: " + localName); } if (currentHeaderElement == element && currentHeaderElement.isMultipleAllowed()) return; if (currentHeaderElement != null && element.ordinal() < currentHeaderElement.ordinal()) { throw new Exception("Header elements in wrong order: " + localName); } if (currentHeaderElement == HeaderElement.ensembleMemberIndex && element == HeaderElement.ensembleMemberId) { throw new Exception("Duplicate header element, both ensembleMemberIndex and ensembleMemberId in header"); } if (currentHeaderElement == element) { throw new Exception("Duplicate header element: " + localName); } if (reader.getAttributeCount() > 0 && !element.hasAttributes()) { throw new Exception("Attributes are not allowed for header element " + localName); } int nextOrdinal = currentHeaderElement == null ? 0 : currentHeaderElement.ordinal() + 1; // order is correct and no duplicate so currentHeaderElement can not be last header element assert nextOrdinal < HEADER_ELEMENTS.length; HeaderElement nextHeaderElement = HEADER_ELEMENTS[nextOrdinal]; if (nextHeaderElement.isRequired() && nextHeaderElement != element) { throw new Exception("Required header item missing: " + nextHeaderElement); } currentHeaderElement = element; } public TimeZone getTimeZone() { //time zone for both fastDateFormat and fastDateFormatWithMillies should be the same. return fastDateFormat.getTimeZone(); } @SuppressWarnings("UnusedDeclaration") public PiTimeSeriesSerializer.EventDestination getEventDestination() { return eventDestination; } @SuppressWarnings("UnusedDeclaration") public float getMissingValue() { return missingValue; } } |