package nl.wldelft.fews.pi;
import nl.wldelft.util.*;
import nl.wldelft.util.coverage.PointGeometry;
import nl.wldelft.util.geodatum.GeoDatum;
import nl.wldelft.util.io.VirtualInputDir;
import nl.wldelft.util.io.VirtualInputDirConsumer;
import nl.wldelft.util.io.XmlParser;
import nl.wldelft.util.timeseries.*;
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.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();
@Override
public void setVirtualInputDir(VirtualInputDir virtualInputDir) {
this.virtualInputDir = virtualInputDir;
}
private enum HeaderElement {
type(F.R), 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),
missVal, longName, stationName, lat, lon, x, y, z, units,
sourceOrganisation, sourceSystem, fileDescription,
creationDate, creationTime, region, thresholds;
interface F {
int A = 1 << 0; // attributes
int R = 1 << 1; // required;
int M = 1 << 2; // multiple;
}
private final int flags;
HeaderElement(/* ================================================================
* 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.CharArrayUtils;
import nl.wldelft.util.Clasz;
import nl.wldelft.util.ExceptionUtils;
import nl.wldelft.util.FastDateFormat;
import nl.wldelft.util.FileUtils;
import nl.wldelft.util.FloatArrayUtils;
import nl.wldelft.util.NumberType;
import nl.wldelft.util.Period;
import nl.wldelft.util.Properties;
import nl.wldelft.util.StringArrayUtils;
import nl.wldelft.util.TextUtils;
import nl.wldelft.util.TimeUnit;
import nl.wldelft.util.TimeZoneUtils;
import nl.wldelft.util.geodatum.GeoDatum;
import nl.wldelft.util.io.VirtualInputDir;
import nl.wldelft.util.io.VirtualInputDirConsumer;
import nl.wldelft.util.io.XmlParser;
import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader;
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;
String user = null;
String limitText = null;
for (int i = 0, n = reader.getAttributeCount(); i < n; i++) {
String localName = reader.getAttributeLocalName(i);
String attributeValue = reader.getAttributeValue(i);
if (dateText == null && TextUtils.equals(localName, "date")) {
dateText = attributeValue;
} else if (timeText == null && TextUtils.equals(localName, "time")) {
timeText = attributeValue;
} else if (valueText == null && TextUtils.equals(localName, "value")) {
this.flags valueText = 0attributeValue;
}
else if (valueSource == null HeaderElement(int flags&& TextUtils.equals(localName, "valueSource")) {
this.flagsvalueSource = flagsattributeValue;
}
} else if (flagText == publicnull boolean isRequired(&& TextUtils.equals(localName, "flag")) {
return (flags & F.R)flagText != 0attributeValue;
}
} else if (flagSource == publicnull boolean hasAttributes()&& TextUtils.equals(localName, "flagSource")) {
return (flags & F.A)flagSource != 0attributeValue;
}
} else if public boolean isMultipleAllowed((comment == null && TextUtils.equals(localName, "comment")) {
return (flags & F.M)comment != 0attributeValue;
}
}
// fastDateFormat is used to keep track of last time zone and lenient
else if (user == null && TextUtils.equals(localName, "user")) {
private FastDateFormat fastDateFormatuser = FastDateFormat.getInstance("yyyy-MM-dd", "HH:mm:ss", TimeZoneUtils.GMT, Locale.US, null);
attributeValue;
private HeaderElement currentHeaderElement = null;
private} else staticif final HeaderElement[] HEADER_ELEMENTS = HeaderElement.class.getEnumConstants();
(startDateText == null && TextUtils.equals(localName, "startDate")) {
private PiTimeSeriesHeader header = new PiTimeSeriesHeader();
private List<String> qualifiersstartDateText = new ArrayList<>()attributeValue;
private long timeStepMillis = 0;
private TimeStep timeStep} =else null;
if (startTimeText == null private long startTime = Long.MIN_VALUE;
&& TextUtils.equals(localName, "startTime")) {
private long endTime = Long.MIN_VALUE;
private float missingValuestartTimeText = Float.NaNattributeValue;
private String creationDateText = null;
} privateelse Stringif creationTimeText(endDateText == null;
&& TextUtils.equals(localName, "endDate")) {
private TimeSeriesContentHandler timeSeriesContentHandler = null;
private PiTimeSeriesSerializer.EventDestination eventDestinationendDateText = nullattributeValue;
/**
* For performance reasons} theelse piif time series format allows that the values are stored in
(endTimeText == null && TextUtils.equals(localName, "endTime")) {
* a separate bin file instead of embedded in theendTimeText xml= file.attributeValue;
* The bin file should have same name} aselse theif xml(minValueText file== exceptnull the extension equals bin
&& TextUtils.equals(localName, "minValue")) {
* In this case all time series should be equidistant.
minValueText = */attributeValue;
private VirtualInputDir virtualInputDir = VirtualInputDir.NONE;
} privateelse InputStreamif binaryInputStream(maxValueText == null;
&& private byte[] byteBuffer = null;
TextUtils.equals(localName, "maxValue")) {
private float[] floatBuffer = null;
private int bufferPosmaxValueText = 0attributeValue;
private int bufferCount = 0;
} privateelse XMLStreamReaderif reader(maxValueText == null;
&& TextUtils.equals(localName, "detection")) {
private String virtualFileName = null;
private static boolean lenientlimitText = falseattributeValue;
private double lat = Double.NaN;
private double} lonelse =if Double.NaN;
private double z = Double.NaN;
/**(reader.getAttributePrefix(i) != null && TextUtils.equals(reader.getAttributePrefix(i), "fs")) {
* For backwards compatibility. Earlier versions of the PiTimeSeriesParser were tolerant aboutint theindex date/time format= timeSeriesContentHandler.addFlagSourceColumn(localName);
* and the case insensitive for header element names.
timeSeriesContentHandler.setColumnFlagSource(index, attributeValue);
* This parser should not accept files that are not valid according} to pi_timeseries.xsd
else {
* When old adapters are not working you can UseLenientPiTimeSeriesParser temporary till theif adapter is fixed(lenient) continue;
*
* @param lenient
throw */
public static void setLenient(boolean lenient) {
PiTimeSeriesParser.lenient = lenientnew Exception("Unknown or duplicate attribute " + localName + " in event");
}
public static boolean isLenient() {}
}
return lenient;
}
public PiTimeSeriesParser() {parseTime(dateText, timeText, startDateText, startTimeText, endDateText, endTimeText);
fastDateFormat.setLenient(lenientparseFlagsUserComment(flagText, flagSource, comment, user);
}
@Override
public void parse(XMLStreamReader reader, String virtualFileName, TimeSeriesContentHandler timeSeriesContentHandler) throws Exception {parseValue(valueText, valueSource, minValueText, maxValueText, limitText);
timeSeriesContentHandler.applyCurrentFields();
this.reader = readerreader.require(XMLStreamConstants.END_ELEMENT, null, "event");
this.virtualFileName = virtualFileNamereader.nextTag();
}
this.timeSeriesContentHandlerprivate = timeSeriesContentHandler;
String virtualBinFileName = FileUtils.getPathWithOtherExtension(virtualFileName, "bin");
void parseValue(String valueText, String valueSource, String minValueText, String maxValueText, String limit) throws Exception {
if // time zone can be overruled by one or more time zone elements in the pi file(domainCount == 0) {
try {
this.fastDateFormat.setTimeZone(timeSeriesContentHandler.getDefaultTimeZone());
if (!virtualInputDir.exists(virtualBinFileName)) {
float value = valueText == null ? Float.NaN : TextUtils.parseFloat(valueText);
eventDestination = PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED;
float minValue = minValueText == null ? value : parseTextUtils.parseFloat(minValueText);
return;
float maxValue = maxValueText }
== null ? value eventDestination = PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE;
: TextUtils.parseFloat(maxValueText);
binaryInputStream = virtualInputDir.getInputStream(virtualBinFileName);
// we can trynot {
use the automatic missing value detection of the content handler because the ifmissing (byteBuffervalue ==is null)different {
for each time series
byteBuffer = new byte[BUFFER_SIZE * NumberType.FLOAT_SIZE];
if (value == missingValue) {
floatBuffer = new float[BUFFER_SIZE];
value = Float.NaN;
}
} else {
parse();
boolean eof = bufferPos == bufferCount && binaryInputStream.read() == -1 timeSeriesContentHandler.setValueResolution(TextUtils.getValueResolution(valueText, '.'));
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 {
timeSeriesContentHandler.setValueAndRange(value, minValue, maxValue);
timeSeriesContentHandler.setValueSource(TextUtils.equals(valueSource, "MAN") ? ValueSource.MANUAL : ValueSource.AUTOMATIC);
bufferPos = 0 timeSeriesContentHandler.setOutOfDetectionRangeFlag(OutOfDetectionRangeFlag.get(getOutOfDetectionFlag(limit)));
} bufferCount = 0;catch (NumberFormatException e) {
binaryInputStream.close();
throw new Exception("Value should be a float " binaryInputStream = null+ valueText);
}
}
private void parse() throws Exception {
reader.require(XMLStreamConstants.START_DOCUMENT, null, nullnextTag();
reader.nextTag();
reader.require(XMLStreamConstants.START_ELEMENT, null, "TimeSeries")return;
reader.nextTag();}
whileif (reader.getEventType()valueText != XMLStreamConstants.END_ELEMENTnull)
{
throw new parseTimeZone();
readTimeSeries(Exception("Attribute value not allowed when having domain parameters, use event element text instead");
}
int count = parseValueList();
if reader.require(XMLStreamConstants.END_ELEMENT, null, "TimeSeries");(count == 0) {
reader.next( timeSeriesContentHandler.setValues(null);
reader.require(XMLStreamConstants.END_DOCUMENT, null, null);
}return;
private void readTimeSeries() throws Exception}
{
reader.require(XMLStreamConstants.START_ELEMENT, null, "series");
int expectedCount = 1;
for reader.nextTag();
parseHeader();
(int i = 0, n = domainCount; i < n; i++) {
timeSeriesContentHandler.setProperties(Properties.NONE);
float[] axis = axisValues[i];
if (eventDestinationaxis == PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED) {
null)
whilethrow new (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
Exception("Domain axis values for domain parameter " + domainParameterIds[i] + " are missing");
String localNameexpectedCount *= readeraxis.getLocalName()length;
}
if (TextUtils.equals(localName, "event")) {expectedCount != count)
throw new Exception("Length of value list for event parseEvent();
does not matches the axes lengths");
} else if (TextUtils.equals(localName, "properties")axesDirty) {
timeSeriesContentHandler.setDomainAxesValueResolutions(axisValueResolutions);
parseProperties( timeSeriesContentHandler.setDomainAxesValues(axisValues);
axesDirty = false;
} else {}
if (count != floatBuffer.length) floatBuffer = FloatArrayUtils.resize(floatBuffer, expectedCount);
break;
timeSeriesContentHandler.setValueResolution(minValueResolution);
timeSeriesContentHandler.setValues(floatBuffer);
}
private byte getOutOfDetectionFlag(String }
text) {
if (text == null if|| (readertext.getEventTypeisEmpty()) ==return XMLStreamConstantsTimeSeriesArray.START_ELEMENT) {INSIDE_DETECTION_RANGE;
char ch = text.charAt(0);
// skip comment
if (ch == '<') return TimeSeriesArray.BELOW_DETECTION_RANGE;
if reader.require(XMLStreamConstants.START_ELEMENT, null, "comment");
(ch == '>') return TimeSeriesArray.ABOVE_DETECTION_RANGE;
if (ch reader.getElementText();
== '~') return TimeSeriesArray.VARYING;
return reader.nextTag()TimeSeriesArray.INSIDE_DETECTION_RANGE;
}
private void parseFlagsUserComment(String flagText, }
String flagSource, String comment, String user) throws }Exception else {
assert eventDestination == PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE;if (flagText == null) {
readValuesFromBinFiletimeSeriesContentHandler.setFlag(0);
}
else {
reader.require(XMLStreamConstants.END_ELEMENT, null, "series");
reader.nextTag();try {
}
private void parseHeader() throws Exception {
reader.require(XMLStreamConstants.START_ELEMENT, null, "header");
timeSeriesContentHandler.setFlag(TextUtils.parseInt(flagText));
} ifcatch (reader.getAttributeCount() > 0NumberFormatException e) {
throw new Exception("AttributesFlag areshould notbe allowedan forinteger header" element+ "flagText);
}
}
readertimeSeriesContentHandler.nextTagsetComment(comment);
initHeadertimeSeriesContentHandler.setUser(user);
timeSeriesContentHandler.setFlagSource(flagSource);
do {}
private void parseTime(String dateText, String timeText, String detectHeaderElement();
startDateText, String startTimeText, String endDateText, String endTimeText) throws Exception {
if (timeText == null)
parseHeaderElement();
throw } while (reader.getEventType() != XMLStreamConstants.END_ELEMENTnew Exception("Attribute time is missing");
if (header.getForecastTime()dateText == Long.MIN_VALUE) header.setForecastTime(startTime);
null)
if (!Double.isNaN(lat)) header.setGeometry(new PointGeometry(GeoDatum.WGS_1984.createLatLongZ(lat, lon, z)));
throw new Exception("Attribute date is missing");
initiateTimeStep();try {
header.setTimeStep(timeStep);
long time = if (!qualifiers.isEmpty()) header.setQualifierIds(qualifiers.toArray(new String[qualifiers.size()]));
parseTime(dateText, timeText, null, null, Long.MIN_VALUE, lastTime, lastTimeAmbiguous);
if (creationDateTextlastTime != null) {
time;
lastTimeAmbiguous try= {timeAmbiguous;
long startTime = parseTime(startDateText, startTimeText, longdateText, creationTimetimeText, = fastDateFormat.parseToMillis(creationDateTexttime, lastStartTime, creationTimeTextlastStartTimeAmbiguous);
lastStartTime header.setCreationTime(creationTime)= startTime;
}lastStartTimeAmbiguous catch (ParseException e) {= timeAmbiguous;
long endTime throw new Exception("Can not parse creation date/time " + creationDateText + ' ' + creationTimeText= parseTime(endDateText, endTimeText, dateText, dateText, time, lastEndTime, lastEndTimeAmbiguous);
}
lastEndTime }
= endTime;
if (startTime != Long.MIN_VALUE && endTimelastEndTimeAmbiguous != Long.MIN_VALUE) {timeAmbiguous;
timeSeriesContentHandler.setEstimatedPeriodsetTimeAndRange(newtime, Period(startTime, endTime));
}
catch (ParseException timeSeriesContentHandler.setNewTimeSeriesHeader(header);
e) {
reader.require(XMLStreamConstants.END_ELEMENT, null, "header");
throw new reader.nextTag();
}
@SuppressWarnings("OverlyLongMethod")
private void parseEvent() throws Exception {Exception("Can not parse " + dateText + ' ' + timeText);
assert}
binaryInputStream == null;}
private long parseTime(String dateText, reader.require(XMLStreamConstants.START_ELEMENT, null, "event");
String dateText = null;String timeText, String defaultDateText, String defaultTimeText, long defaultTime, long lastTime, boolean lastTimeAmbiguous) throws ParseException {
Stringif timeText(dateText == null;
&& timeText == null) String valueText = null;
return defaultTime;
if (dateText == Stringnull) flagTextdateText = nulldefaultDateText;
Stringif flagSource(timeText == null) timeText = defaultTimeText;
Stringboolean commentuseMillis = null;
timeText.contains(".");
if (!fastDateFormat.getTimeZone().useDaylightTime()) return String user = null;
useMillis? fastDateFormatWithMillies.parseToMillis(dateText, timeText): fastDateFormat.parseToMillis(dateText, timeText);
for (int i = 0, n = reader.getAttributeCount(); i < n; i++) long t1;
try {
String localName = reader.getAttributeLocalName(it1 = useMillis?fastDateFormatWithMillies.parseToMillis(dateText, timeText): fastDateFormat.parseToMillis(dateText, timeText);
} catch (ParseException String attributeValue = reader.getAttributeValue(i);e) {
if (dateText == null && TextUtils!timeText.equals(localName, "date""02:00:00")) {throw e;
// see FEWS-17782 also dateText = attributeValue;
accept 02:00:00 on daylight saving time switch
} else if (timeText == null && TextUtils.equals(localName, "time")) try {
timeText = attributeValuereturn fastDateFormat.parseToMillis(dateText, "03:00:00");
} else ifcatch (valueText == null && TextUtils.equals(localName, "value"))ParseException e1) {
valueTextthrow = attributeValuee;
}
else if (flagText == null}
&& TextUtils.equals(localName, "flag")) {
Calendar calendar = useMillis? fastDateFormatWithMillies.getCalendar() : fastDateFormat.getCalendar();
flagText = attributeValue calendar.setTimeInMillis(t1);
int timeOfDay = getTimeOfDay(calendar);
} else if (flagSource ==long nullt2 && TextUtils.equals(localName, "flagSource")) {= getOtherTime(calendar, t1, timeOfDay);
timeAmbiguous = t1 != t2;
flagSource = attributeValue;
long minTime = Math.min(t1, t2);
} else iflong (commentmaxTime == null && TextUtils.equals(localName, "comment")) { Math.max(t1, t2);
long res = minTime > lastTime ? commentminTime =: attributeValuemaxTime;
if (!lastTimeAmbiguous) return res;
} else if (user == null && TextUtils.equals(localName, "user")) {
user = attributeValue;
// see FEWS-17782 also accept two times 02:00:00 instead of two times 01:00:00 on daylight saving time switch
if (timeStepMillis != 0L && lastTime }+ elsetimeStepMillis {
!= res) return lastTime + timeStepMillis;
if (timeStep != thrownull new Exception("Unknown attribute " + localName + " in event"&& timeStep.isRegular() && timeStep.nextTime(lastTime) != res) return timeStep.nextTime(lastTime);
}
return res;
}
private static int if getTimeOfDay(timeTextCalendar == null)calendar) {
return (int) (calendar.get(Calendar.HOUR_OF_DAY) * throw new Exception("Attribute time is missing");
if (dateText == null)TimeUnit.HOUR_MILLIS + calendar.get(Calendar.MINUTE) * TimeUnit.MINUTE_MILLIS + calendar.get(Calendar.SECOND) * TimeUnit.SECOND_MILLIS + calendar.get(Calendar.MILLISECOND));
}
private static long getOtherTime(Calendar throw new Exception("Attribute date is missing");
calendar, long time, int timeOfDay) {
if (valueText == null)try {
throw new Exception("Attribute value is missing"calendar.setTimeInMillis(time + TimeUnit.HOUR_MILLIS);
putValue(dateText, timeText, valueText, flagText, flagSource, comment, user);
if (getTimeOfDay(calendar) == timeOfDay) return time + TimeUnit.HOUR_MILLIS;
reader.nextTag();
readercalendar.require(XMLStreamConstants.END_ELEMENT, null, "event"setTimeInMillis(time - TimeUnit.HOUR_MILLIS);
reader.nextTag();
}
private void putValue(String dateText, String timeText, String valueText, String flagText, String flagSource, String comment, String user) throws Exception {
if (getTimeOfDay(calendar) == timeOfDay) return time - TimeUnit.HOUR_MILLIS;
return time;
} tryfinally {
timeSeriesContentHandlercalendar.setTime(fastDateFormat.parseToMillis(dateText, timeText)setTimeInMillis(time);
}
catch (ParseException e) {}
private long parseTime() throws Exception {
throw new Exception("Can not parse " +String dateText + ' ' + timeText= reader.getAttributeValue(null, "date");
}
if (dateText == null) {
if (flagText == null)throw {
new Exception("Attribute " + currentHeaderElement +
timeSeriesContentHandler.setFlag(0);
} else {
"-date is missing");
try {}
String timeText timeSeriesContentHandler.setFlag(TextUtils.parseInt(flagText))= reader.getAttributeValue(null, "time");
if (timeText } catch (NumberFormatException e== null) {
throw new Exception("FlagAttribute should" be+ an integer " + flagText);
currentHeaderElement +
}
"-time is }missing");
timeSeriesContentHandler.setComment(comment);}
timeSeriesContentHandler.setUser(userboolean useMillis = timeText.contains(".");
timeSeriesContentHandler.setFlagSource(flagSource);
long time;
try {
float value = TextUtils.parseFloat(valueText);
time = useMillis? fastDateFormatWithMillies.parseToMillis(dateText, timeText) : fastDateFormat.parseToMillis(dateText, timeText);
} catch (ParseException e) {
// we can not use the automatic missing value detection of the content handler because the missing value is different for each time series
throw new Exception("Not a valid data time for "
+ currentHeaderElement + ' ' + ifdateText (value+ ==' missingValue)' {
+ timeText, e);
}
value = Floatreader.NaNnextTag();
return time;
}
else {
private void parseTimeStep() throws Exception {
String times = timeSeriesContentHandlerreader.setValueResolution(TextUtils.getValueResolution(valueText, '.'));getAttributeValue(null, "times");
if (times != null) }{
timeSeriesContentHandler.setValue(valuetimeStep = PiCastorUtils.createTimesOfDayTimeStep(times, getTimeZone());
timeSeriesContentHandlerreader.applyCurrentFieldsnextTag();
} catch (NumberFormatException e) {return;
}
throw new Exception("Value shouldString beunit a float " + valueText= reader.getAttributeValue(null, "unit");
TimeUnit tu }
}
= unit == null ? null : TimeUnit.get(unit);
private longif parseTime(tu != null) throws{
Exception {
String dateTextmultiplierText = reader.getAttributeValue(null, "datemultiplier");
int multiplier;
if (dateTextmultiplierText == null) {
throw new Exception("Attribute " +multiplier currentHeaderElement= +1;
} else {
"-date is missing"); try {
}
String timeTextmultiplier = readerInteger.getAttributeValue(null, "time"parseInt(multiplierText);
if } catch (timeText == nullNumberFormatException e) {
throw new Exception("Attribute " + currentHeaderElement +ExceptionUtils.getMessage(e), e);
}
"-time is missing");
}
if (multiplier == long0) time;{
try {
timethrow =new fastDateFormat.parseToMillis(dateText, timeTextException("Multiplier is 0");
} catch (ParseException e) {
}
throw new Exception("Not a valid data time for " }
String dividerText = reader.getAttributeValue(null, "divider");
+ currentHeaderElement + ' ' + dateText + 'int 'divider;
+ timeText, e);
}
if (dividerText == null) {
reader.nextTag();
return time;
}
divider = 1;
private long parseTimeStep() throws Exception {
} else {
String unit = reader.getAttributeValue(null, "unit");
if (unit == null)try {
throw new Exception("Attribute unit is missing in "divider + currentHeaderElement= Integer.parseInt(dividerText);
}
TimeUnit} tucatch = TimeUnit.get(unit);(NumberFormatException e) {
if (tu != null) {
throw String multiplierText = reader.getAttributeValue(null, "multiplier");
new Exception(ExceptionUtils.getMessage(e), e);
int multiplier;
}
if (multiplierTextdivider == null0) {
multiplier = 1 throw new Exception("divider is 0");
} else {}
}
try {
reader.nextTag();
timeStepMillis multiplier= = Integertu.parseIntgetMillis(multiplierText);
* multiplier / divider;
}timeStep catch (NumberFormatException e) {= null;
} else {
throw new Exception(ExceptionUtilsreader.getMessagenextTag(e), e);
timeStepMillis = 0;
}
timeStep = IrregularTimeStep.INSTANCE;
if (multiplier == 0) {}
}
private void initHeader() {
throw new Exception("Multiplier is 0"header.clear();
header.setFileDescription(virtualFileName);
}
currentHeaderElement = null;
timeStep = null;
}
timeStepMillis = 0;
String dividerTextstartTime = reader.getAttributeValue(null, "divider")Long.MIN_VALUE;
endTime = Long.MIN_VALUE;
int divider;
missingValue = Float.NaN;
if (dividerTextcreationDateText == null) {;
creationTimeText = "00:00:00";
divider = 1qualifiers.clear();
lat = Double.NaN;
} else {
lon = Double.NaN;
z try {
= Double.NaN;
}
private void readValuesFromBinFile() throws Exception {
TimeStep dividertimeStep = Integerheader.parseIntgetTimeStep(dividerText);
if (!timeStep.isRegular()) {
} catch (NumberFormatException e) {
throw new Exception("Only equidistant time step supported when pi events are stored in bin file instead of xml");
throw new Exception(ExceptionUtils.getMessage(e), e);
}
boolean equidistantMillis }
= timeStep.isEquidistantMillis();
long stepMillis = equidistantMillis ? timeStep.getStepMillis() if (divider == 0) {
: Long.MIN_VALUE;
for (long time = startTime; time <= endTime;) {
throw new Exception("divider is 0" timeSeriesContentHandler.setTime(time);
if (bufferPos == }bufferCount) fillBuffer();
}
float value = floatBuffer[bufferPos++];
reader.nextTag();
return tu.getMillis() * multiplier / divider;
} else {
// we can not use the automatic missing value detection of the content handler because the missing value is different for each time series
if (value == missingValue) value = readerFloat.nextTag()NaN;
return 0timeSeriesContentHandler.setValue(value);
}
}
timeSeriesContentHandler.applyCurrentFields();
private void initHeader() {
if header.clear(equidistantMillis); {
header.setFileDescription(virtualFileName);
currentHeaderElementtime += nullstepMillis;
timeStep = nullcontinue;
timeStepMillis = 0;
}
startTimetime = Long.MIN_VALUEtimeStep.nextTime(time);
}
endTime = Long.MIN_VALUE; }
private void fillBuffer() throws missingValueIOException = Float.NaN;
{
int creationDateTextbyteBufferCount = null0;
creationTimeTextwhile = "00:00:00";
qualifiers.clear();
(byteBufferCount % NumberType.FLOAT_SIZE != 0 || byteBufferCount == 0) {
lat = Double.NaN;
int count = binaryInputStream.read(byteBuffer, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE lon = Double.NaN- byteBufferCount);
z = Double.NaN;
assert count }
private void readValuesFromBinFile() throws Exception {
!= 0; // see read javadoc
TimeStepif timeStep(count == header.getTimeStep();
if (!timeStep.isRegular()) {
-1) throw new EOFException("Bin file is too short");
byteBufferCount throw new Exception("Only equidistant time step supported when pi events are stored in bin file instead of xml");
}
boolean equidistantMillis = timeStep.isEquidistantMillis(+= count;
}
bufferCount = byteBufferCount / NumberType.FLOAT_SIZE;
BinaryUtils.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIAN);
long stepMillisbufferPos = equidistantMillis0;
? timeStep.getStepMillis() : Long.MIN_VALUE; }
private void for (long time = startTime; time <= endTime;initiateTimeStep() {
if (timeStep != timeSeriesContentHandler.setTime(time);null) {
ifassert (bufferPostimeStepMillis == bufferCount) fillBuffer()0;
floatreturn;
value = floatBuffer[bufferPos++];
}
//if we(timeStepMillis can not use the automatic missing value detection of the content handler because the missing value is different for each time series== 0){
//no timestep in header. Fix for backward compatibility
if (valuetimeStep == missingValue) value = Float.NaN IrregularTimeStep.INSTANCE;
timeSeriesContentHandler.setValue(value)return;
}
timeSeriesContentHandler.applyCurrentFields(); if (timeStepMillis >= TimeUnit.HOUR_MILLIS && getTimeZone().useDaylightTime()) {
iftimeStep (equidistantMillis) {
= IrregularTimeStep.INSTANCE;
return;
time += stepMillis;}
long startTime = this.startTime == Long.MIN_VALUE ? 0L : continue;
this.startTime;
if (timeStepMillis % TimeUnit.SECOND_MILLIS != 0) }{
timetimeStep = timeStepRelativeEquidistantTimeStep.nextTime(timegetInstance(timeStepMillis, startTime);
}
}
return;
private void fillBuffer() throws IOException {}
intlong byteBufferCounttimeZoneOffsetMillis = -startTime % 0timeStepMillis;
whileif (byteBufferCounttimeZoneOffsetMillis % NumberTypeTimeUnit.FLOATMINUTE_SIZEMILLIS != 0 || byteBufferCount == 0) {
inttimeStep count = binaryInputStreamRelativeEquidistantTimeStep.readgetInstance(byteBuffertimeStepMillis, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE - byteBufferCount);
assert count != 0; // see read javadocstartTime);
ifreturn;
(count == -1) throw new EOFException("Bin file is too short"); }
timeStep = byteBufferCount += countSimpleEquidistantTimeStep.getInstance(timeStepMillis, timeZoneOffsetMillis);
}
@SuppressWarnings({"OverlyLongMethod"})
private void parseHeaderElement() throws Exception {
bufferCount = byteBufferCount / NumberType.FLOAT_SIZE;
switch (currentHeaderElement) {
BinaryUtils.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIAN);
case type:
bufferPos = 0;
}
private void initiateTimeStep() { header.setParameterType(parseType(reader.getElementText()));
if (timeStepMillis == 0) {
break;
timeStep = IrregularTimeStep.INSTANCE;
case moduleInstanceId:
return;
header.setModuleInstanceId(reader.getElementText());
}
long startTime = this.startTime == Long.MIN_VALUE ? 0L : this.startTime;
break;
if (timeStepMillis % TimeUnit.SECOND_MILLIS != 0) {
case locationId:
// timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime);
return;see FEWS-9858, when there is no location id the time series are assigned to all locations
}
long timeZoneOffsetMillis = -startTime % timeStepMillis;
if (timeZoneOffsetMillis % TimeUnit.MINUTE_MILLIS != 0) {// this is a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty strings
timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime header.setLocationId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"));
return break;
}
case parameterId:
timeStep = SimpleEquidistantTimeStep.getInstance(timeStepMillis, timeZoneOffsetMillis);
}
private void parseTimeZone() throws Exception {
if (reader.getEventType() != XMLStreamConstants.START_ELEMENT) return;
// see FEWS-9858, when there is no parameter id the time series are assigned to all locations
if (!TextUtils.equals(reader.getLocalName(), "timeZone")) return;
// trythis {
is a flaw in the pi_timeSeries.xsd, the location element is required but Stringis timeZoneTextallowed = reader.getElementText();empty strings
// element name="timeZone" type="fews:TimeZoneSimpleType" default="0.0" minOccurs="0"/>
header.setParameterId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"));
// when default isbreak;
used in schema for element the consequence is that empty strings arecase allowedqualifierId:
double offset = timeZoneTextqualifiers.isEmpty() ? 0d : Double.parseDouble(timeZoneTextadd(reader.getElementText());
TimeZone timeZoneFromDouble = TimeZoneUtils.createTimeZoneFromDouble(offset) break;
this.fastDateFormat.setTimeZone(timeZoneFromDouble);
case ensembleId:
} catch (NumberFormatException e) {
header.setEnsembleId(reader.getElementText());
throw new Exception("Not valid timeZone format", e)break;
}
reader.require(XMLStreamConstants.END_ELEMENT, null, "timeZone");case ensembleMemberIndex:
reader.nextTag();
}
@SuppressWarnings({"OverlyLongMethod"})header.setEnsembleMemberIndex(parseEnsembleMemberIndex(reader.getElementText()));
private void parseHeaderElement() throws Exception {
switch (currentHeaderElement) {break;
case typeensembleMemberId:
header.setParameterType(parseTypesetEnsembleMemberId(reader.getElementText()));
break;
case locationIdtimeStep:
// see FEWS-9858, when there is no location id the time series are assigned to all locationsparseTimeStep();
break;
case startDate:
// this is a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty strings
startTime = parseTime();
break;
header.setLocationId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"));
case endDate:
endTime break= parseTime();
case parameterId:
break;
case //forecastDate:
see FEWS-9858, when there is no parameter id the time series are assigned to all locations header.setForecastTime(parseTime());
//break;
this is a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty stringscase approvedDate:
header.setParameterId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"))setApprovedTime(parseTime());
break;
case qualifierIdmissVal:
missingValue = qualifiers.addparseString(reader.getElementText());
break;
case ensembleIdlongName:
header.setEnsembleIdsetLongName(reader.getElementText());
break;
case ensembleMemberIndexstationName:
header.setEnsembleMemberIndex(parseEnsembleMemberIndexsetLocationName(reader.getElementText()));
break;
case ensembleMemberIdunits:
header.setEnsembleMemberIdsetUnit(reader.getElementText());
break;
case timeStepdomainAxis:
timeStepMillis = parseTimeStepparseDomainAxis();
break;
case startDatesourceOrganisation:
startTime = parseTimeheader.setSourceOrganisation(reader.getElementText());
break;
case endDatesourceSystem:
endTime = parseTimeheader.setSourceSystem(reader.getElementText());
break;
case forecastDatefileDescription:
header.setForecastTimesetFileDescription(parseTimereader.getElementText());
break;
case missValcreationDate:
missingValuecreationDateText = parseValue(reader.getElementText());
break;
case longNamecreationTime:
header.setLongName(creationTimeText = reader.getElementText());
break;
case stationNameregion:
header.setLocationNamesetRegion(reader.getElementText());
break;
case unitsthresholds:
header.setUnit(reader.getElementText()parseThresholds();
break;
case sourceOrganisationlat:
lat = header.setSourceOrganisationparseString(reader.getElementText());
break;
case sourceSystemlon:
header.setSourceSystemlon = parseString(reader.getElementText());
break;
case fileDescriptionx:
header.setFileDescription(reader.getElementText());
break;
case creationDatey:
creationDateText = reader.getElementText();
break;
case creationTimez:
creationTimeTextz = parseString(reader.getElementText());
break;
case regionfirstValueTime:
header.setRegion(reader.getElementText());
break;
case thresholdslastValueTime:
parseThresholdsreader.getElementText();
break;
case latmaxValue:
lat = parseValue(reader.getElementText());
break;
case lonminValue:
lon = parseValue(reader.getElementText());
break;
case xvalueCount:
reader.getElementText();
break;
case ymaxWarningLevelName:
reader.getElementText();
break;
}
case z: reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name());
reader.nextTag();
}
zprivate =void parseValueparseDomainAxis(reader.getElementText());
throws Exception {
String parameterId = breakreader.getAttributeValue(null, "parameterId");
}if (parameterId == null)
reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name());
throw new Exception("Attribute parameterId for domainUnits is missing");
reader.nextTag(domainParameterIds = Clasz.strings.ensureCapacity(domainParameterIds, domainCount + 1);
}
privatedomainUnits void parseThresholds() throws XMLStreamException {
= Clasz.strings.ensureCapacity(domainUnits, domainCount + 1);
domainParameterIds[domainCount] = reader.nextTag()parameterId;
ArrayList<String> idsdomainUnits[domainCount] = new ArrayList<>(reader.getAttributeValue(null, "units");
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) {
String id = ids.add(reader.getAttributeValue(null, "id"));
names.add(String name = reader.getAttributeValue(null, "name"));
float stringValues.addvalue = TextUtils.parseFloat(reader.getAttributeValue(null, "value"));
groupIds.add(String groupId = reader.getAttributeValue(null, "groupId"));
groupNames.add(String groupName = reader.getAttributeValue(null, "groupName"));
}
String[] groupIds = groupId == null ? reader.nextTagClasz.strings.emptyArray();
: new String[] {groupId};
} while (!reader.getLocalName().equals(currentHeaderElement.name()));
float[] values = new float[stringValues.size()];
for (int i = 0; i < values.length; i++) {
String[] groupNames = groupName == null ? Clasz.strings.emptyArray() : new String[] {groupName};
values[i] = Floatthresholds.parseFloatadd(stringValuesnew DefaultTimeSeriesHeader.get(iDefaultThreshold(id, name, value, groupIds, groupNames));
}
header.setHighLevelThresholds(ids.toArray(new String[ids.size()]), names.toArray(new String[names.size()]), values, reader.nextTag();
} while (!reader.getLocalName().equals(currentHeaderElement.name()));
groupIds.toArray(new String[groupIds.size()]), groupNames.toArray(new String[groupNames.size()])) 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;
}
}
|