/* ================================================================
* 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.DateUtilsBinaryUtils;
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.TextUtilsProperties;
import nl.wldelft.util.StringArrayUtils;
import nl.wldelft.util.TimeUnitTextUtils;
import nl.wldelft.util.TimeZoneUtilsTimeUnit;
import nl.wldelft.util.NumberTypeTimeZoneUtils;
import nl.wldelft.util.BinaryUtilsgeodatum.GeoDatum;
import nl.wldelft.util.io.VirtualInputDir;
import nl.wldelft.util.io.VirtualInputDirConsumer;
import nl.wldelft.util.io.XmlParser;
import nl.wldelft.util.timeseries.IrregularTimeStepDefaultTimeSeriesHeader;
import nl.wldelft.util.timeseries.ParameterTypeIrregularTimeStep;
import nl.wldelft.util.timeseries.SimpleEquidistantTimeStepOutOfDetectionRangeFlag;
import nl.wldelft.util.timeseries.TimeSeriesContentHandlerParameterType;
import nl.wldelft.util.timeseries.TimeStepRelativeEquidistantTimeStep;
import orgnl.wldelft.apacheutil.log4jtimeseries.LoggerSimpleEquidistantTimeStep;
import javaxnl.wldelft.xmlutil.streamtimeseries.XMLStreamConstantsTimeSeriesArray;
import javaxnl.wldelft.xmlutil.streamtimeseries.XMLStreamExceptionTimeSeriesContentHandler;
import javaxnl.wldelft.xmlutil.streamtimeseries.XMLStreamReaderTimeSeriesHeader;
import java.io.IOExceptionnl.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.ionio.EOFExceptionByteOrder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.ListArrays;
import java.util.Calendar;
import java.util.LocaleList;
import java.util.TimeZoneLocale;
import java.nioutil.ByteOrderTimeZone;
public class PiTimeSeriesParser implements XmlParser<TimeSeriesContentHandler>, VirtualInputDirConsumer {
private static final Loggerint logBUFFER_SIZE = Logger.getLogger(PiTimeSeriesParser.class);
2048;
private static final int BUFFER_SIZEProperties.Builder propertyBuilder = 2048;
new Properties.Builder();
@Override
private char[] charBuffer = publicnull;
void setVirtualInputDir(VirtualInputDir virtualInputDir) {
private float minValueResolution = Float.NaN;
private this.virtualInputDirfloat[][] axisValues = virtualInputDirnull;
}
private float[] axisValueResolutions = Clasz.floats.emptyArray();
private boolean enumaxesDirty HeaderElement= {false;
private String[] domainParameterIds type(F.R), locationId(F.R),= Clasz.strings.emptyArray();
private String[] domainUnits parameterId(F.R), qualifierId(F.M), ensembleId, ensembleMemberIndex,
timeStep(F.R | F.A), startDate(F.R | F.A), endDate(F.R | F.A), forecastDate(F.A),= Clasz.strings.emptyArray();
private int domainCount = 0;
@Override
public void setVirtualInputDir(VirtualInputDir virtualInputDir) {
missVal, longName, stationName, units,
this.virtualInputDir = virtualInputDir;
}
private sourceOrganisation,enum sourceSystem,HeaderElement fileDescription,{
creationDatetype(F.R), creationTimemoduleInstanceId, region, thresholds;
locationId(F.R),
interface F {
parameterId(F.R), qualifierId(F.M), ensembleId, ensembleMemberIndex, ensembleMemberId,
timeStep(F.R | int F.A = 1 << 0; // attributes
), startDate(F.R | F.A), endDate(F.R | F.A), forecastDate(F.A), approvedDate(F.A),
missVal, intlongName, RstationName, =lat, 1lon, << 1; // required;
int M = 1 << 2; // multple;
x, y, z, units, domainAxis(F.M | F.A),
sourceOrganisation, }
sourceSystem, fileDescription,
privatecreationDate, finalcreationTime, int flags;
region, thresholds,
HeaderElementfirstValueTime(F.A), lastValueTime(F.A), maxValue, minValue, valueCount, maxWarningLevelName;
interface F {
this.flagsint A = 1 << 0; // attributes
}
int R = 1 << HeaderElement(int flags) {1; // required;
this.flagsint M = flags 1 << 2; // multiple;
}
private final public boolean isRequiredint flags;
HeaderElement() {
return (flags & F.R) !this.flags = 0;
}
public boolean 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", DateUtilsTimeZoneUtils.GMT, Locale.US, null);
private booleanFastDateFormat invalidHeaderTimeDetectedfastDateFormatWithMillies = false;
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> qualfiersqualifiers = new ArrayList<String>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 TimeSeriesContentHandlerlong timeSeriesContentHandlerlastTime = nullLong.MIN_VALUE;
/**
private long lastStartTime = Long.MIN_VALUE;
* For performance reasionsprivate thelong pilastEndTime time series format alllows that the values are stored in= Long.MIN_VALUE;
private boolean timeAmbiguous = false;
private *boolean alastTimeAmbiguous separate= binfalse;
file instead of embeddedprivate inboolean thelastStartTimeAmbiguous xml= file.false;
private *boolean ThelastEndTimeAmbiguous bin file should have = 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 tolleranttolerant 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 temporaraytemporary 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 readTimeSeries();timeZone != null) {
}
readerthis.fastDateFormat.require(XMLStreamConstants.END_ELEMENT, null, "TimeSeries"setTimeZone(timeZone);
readerthis.fastDateFormatWithMillies.nextsetTimeZone(timeZone);
reader.require(XMLStreamConstants.END_DOCUMENT, null, null);
}
if (noTimeSeries()) continue;
}
private void readTimeSeries() throws Exception {;
}
reader.require(XMLStreamConstants.STARTEND_ELEMENT, null, "seriesTimeSeries");
reader.nextTagnext();
parseHeader(reader.require(XMLStreamConstants.END_DOCUMENT, null, null);
}
ifprivate boolean noTimeSeries(binaryInputStream == null) {
whilereturn (reader.getEventType() == XMLStreamConstants.STARTEND_ELEMENT && TextUtils.equals(reader.getLocalName(), "event")) {.equals("TimeSeries");
}
private void readTimeSeries() throws Exception {
parseEvent( reader.require(XMLStreamConstants.START_ELEMENT, null, "series");
reader.nextTag();
}
parseHeader();
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {lastTime = Long.MIN_VALUE;
timeAmbiguous = false;
// skip comment
lastTimeAmbiguous = false;
lastStartTimeAmbiguous = false;
reader.require(XMLStreamConstants.START_ELEMENT, null, "comment");
lastEndTimeAmbiguous = false;
reader.getElementText(timeSeriesContentHandler.setProperties(Properties.NONE);
if (eventDestination reader.nextTag();== PiTimeSeriesSerializer.EventDestination.XML_EMBEDDED) {
}
while (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
} else {
String localName = readValuesFromBinFilereader.getLocalName();
}
if reader(TextUtils.require(XMLStreamConstants.END_ELEMENT, nullequals(localName, "seriesevent");) {
reader.nextTag();
}
private void parseHeaderparseEvent() throws Exception {;
reader.require(XMLStreamConstants.START_ELEMENT, null, "header");
} else if (readerTextUtils.getAttributeCount() > 0equals(localName, "domainAxisValues")) {
throw new Exception("Attributes are not allowed for header element "parseDomainAxisValues();
}
reader.nextTag();
} else if (TextUtils.equals(localName, "properties")) {
initHeaderparseProperties();
do } else {
detectHeaderElement();
break;
parseHeaderElement();
} while (reader.getEventType() != XMLStreamConstants.END_ELEMENT);
}
if (header.getForecastTime() == Long.MIN_VALUE) header.setForecastTime(startTime); }
initiateTimeStep();
} else {
header.setTimeStep(timeStep);
assert eventDestination if (!qualfiers.isEmpty()) header.setQualifierIds(qualfiers.toArray(new String[qualfiers.size()]))== PiTimeSeriesSerializer.EventDestination.SEPARATE_BINARY_FILE;
readValuesFromBinFile();
}
if (reader.getEventType(creationDateText) !== nullXMLStreamConstants.START_ELEMENT) {
// tryskip {comment
long creationTime = fastDateFormat.parseToMillis(creationDateText, creationTimeTextreader.require(XMLStreamConstants.START_ELEMENT, null, "comment");
header.setCreationTime(creationTimereader.getElementText();
} catch (ParseException e) {reader.nextTag();
}
throw new Exception("Can not parse creation date/time " + creationDateText + ' ' + creationTimeText);
reader.require(XMLStreamConstants.END_ELEMENT, null, "series");
reader.nextTag();
}
private void parseDomainAxisValues() throws Exception }{
}
String domainParameterId timeSeriesContentHandler.setNewTimeSeriesHeader(header= reader.getAttributeValue(null, "parameterId");
if (startTimedomainParameterId != Long.MIN_VALUE && endTime != Long.MIN_VALUEnull)
{
throw timeSeriesContentHandler.setEstimatedPeriod(new PeriodException(startTime, endTime));
"Attribute parameterId for domainAxisValues is missing");
}
int index = readerStringArrayUtils.require(XMLStreamConstants.END_ELEMENTindexOf(domainParameterIds, 0, nulldomainCount, "header"domainParameterId);
if reader.nextTag();
(index == -1)
}
privatethrow voidnew parseEvent() throws Exception {Exception("parameterId " + domainParameterId + " for domainAxisValues not defined in header");
assertif binaryInputStream(axisValues == null) axisValues = new float[domainCount][];
if reader.require(XMLStreamConstants.START_ELEMENT, null, "event")axisValueResolutions.length < domainCount) axisValueResolutions = new float[domainCount];
Stringint timeTextcount = reader.getAttributeValue(null, "time"parseValueList();
String dateText = reader.getAttributeValue(reader.require(XMLStreamConstants.END_ELEMENT, null, "datedomainAxisValues");
String valueText = reader.getAttributeValue(null, "value"nextTag();
String flagText axisValues[index] = readerClasz.floats.getAttributeValue(nullcopyOfArrayRange(floatBuffer, 0, "flag"count);
String commentTextaxisValueResolutions[index] = reader.getAttributeValue(null, "comment");
minValueResolution;
ifaxesDirty (timeText == null)true;
}
private int throw newparseValueList() throws Exception("Attribute time is missing");
{
if (dateTextcharBuffer == null)
charBuffer = new char[100];
char[] throwcharBuffer new Exception("Attribute date is missing");
= this.charBuffer;
if (valueTextfloatBuffer == null)
floatBuffer = new float[100];
throwminValueResolution new Exception("Attribute value is missing");
= Float.POSITIVE_INFINITY;
tryint {
i = 0;
int timeSeriesContentHandler.setTime(fastDateFormat.parseToMillis(dateText, timeText))length = 0;
}int catchj (ParseException e) {= 0;
while (reader.next() == throw new Exception("Can not parse " + dateText + ' ' + timeText)XMLStreamConstants.CHARACTERS) {
length -= i;
}
assert length >= 0;
if (flagText == null) {
// shift the remaining part of timeSeriesContentHandler.setFlag(0);
the buffer to the start
} else {
if (length > 0) CharArrayUtils.arraycopy(charBuffer, i, charBuffer, try {0, length);
int textLength = timeSeriesContentHandlerreader.setFlag(TextUtils.parseInt(flagText)getTextLength();
} catch (NumberFormatException eif (charBuffer.length < length + textLength) {
throwcharBuffer new Exception("Flag should be an integer "= Clasz.chars.copyOfArrayRange(charBuffer, 0, length, length + flagTexttextLength);
}
this.charBuffer = charBuffer;
}
timeSeriesContentHandler.setComment(commentText);
}
try {
int n = reader.getTextCharacters(0, charBuffer, length, textLength);
float value = TextUtils.parseFloat(valueText);
assert n == textLength;
// we can not use the automatic missinglength value detection of the content handler because the missing value is different for each time series+= textLength;
i = 0;
iffor (value; ==; missingValue) {
int valuestart = Float.NaNCharArrayUtils.indexOfNonWhitespace(charBuffer, i, length - i);
} else {
if (start == -1) {
timeSeriesContentHandler.setValueResolution(TextUtils.getValueResolution(valueText, '.'));
i = }length;
timeSeriesContentHandler.setValue(value);
break;
timeSeriesContentHandler.applyCurrentFields();
} catch (NumberFormatException e) {}
throw new Exception("Value should be a float " + valueText);
int end = CharArrayUtils.indexOfWhitespace(charBuffer, start, length - start);
}
if reader.nextTag();
(end == -1) {
reader.require(XMLStreamConstants.END_ELEMENT, null, "event");
i = reader.nextTag()start;
}
private long parseTime() throws Exception {
break;
String dateText = reader.getAttributeValue(null, "date");
if (dateText == null) {}
throw new Exception("Attribute " +i currentHeaderElement= +end;
parseValue(charBuffer, j++, start, end "-date start is+ missing"1);
}
}
String timeText = reader.getAttributeValue(null, "time"); }
if (timeTexti =!= nulllength) {
parseValue(charBuffer, j++, i, length - i);
throw new Exception("Attribute " + currentHeaderElement +return j;
}
private void parseValue(char[] charBuffer, int pos, int start, int length) {
"-time is missing");
float value = (float) TextUtils.parseDouble(charBuffer, start, }
length, '.');
long time;
try {if (value == missingValue) value = Float.NaN;
float valueResolution time = fastDateFormatTextUtils.parseToMillis(dateText, timeTextgetValueResolution(charBuffer, start, length, '.');
}if catch(valueResolution (ParseException< eminValueResolution) {
minValueResolution = valueResolution;
if throw new Exception("Not a valid data time for "
(floatBuffer.length == pos) floatBuffer = Clasz.floats.ensureCapacity(floatBuffer, pos + 1);
floatBuffer[pos] = value;
}
private void parseHeader() +throws currentHeaderElementException +{
' ' + dateText + ' ' +domainCount timeText,= e)0;
}
reader.nextTag(Arrays.fill(domainParameterIds, null);
return timeArrays.fill(domainUnits, null);
}
if private(axisValues long!= parseTimeStep(null) throws Exception {
Arrays.fill(axisValues, null);
String unit = reader.getAttributeValue(require(XMLStreamConstants.START_ELEMENT, null, "unitheader");
if (unit == nullreader.getAttributeCount() > 0) {
throw new Exception("AttributeAttributes unitare isnot missingallowed infor "header +element currentHeaderElement");
}
TimeUnit tu = TimeUnit.get(unitreader.nextTag();
if (tu != null) {
initHeader();
do {
String multiplierText = reader.getAttributeValue(null, "multiplier"detectHeaderElement();
int multiplierparseHeaderElement();
} if (multiplierText == null) {while (reader.getEventType() != XMLStreamConstants.END_ELEMENT);
if (header.getForecastTime() == multiplier = 1Long.MIN_VALUE) header.setForecastTime(startTime);
if (!Double.isNaN(lat)) header.setGeometry(GeoDatum.WGS_1984.createLatLongZ(lat, } else {lon, z));
initiateTimeStep();
try {header.setTimeStep(timeStep);
if (!qualifiers.isEmpty()) header.setQualifierIds(Clasz.strings.newArrayFrom(qualifiers));
if (creationDateText multiplier != Integer.parseInt(multiplierText);null) {
} catch (NumberFormatException e) try {
long creationTime throw new Exception(ExceptionUtils.getMessage(e), e= fastDateFormat.parseToMillis(creationDateText, creationTimeText);
}
header.setCreationTime(creationTime);
} catch if (multiplier == 0ParseException e) {
throw new Exception("Multiplier is 0");
Can not parse creation date/time " + creationDateText + ' ' + creationTimeText);
}
}
}
if (startTime != String dividerText = reader.getAttributeValue(null, "divider");
int divider;Long.MIN_VALUE && endTime != Long.MIN_VALUE) {
iftimeSeriesContentHandler.setEstimatedPeriod(new Period(dividerText == null) {startTime, endTime));
}
dividerdomainParameterIds = 1 Clasz.strings.resizeArray(domainParameterIds, domainCount);
domainUnits = Clasz.strings.resizeArray(domainUnits, domainCount);
} else {
header.setDomainParameterIds(domainParameterIds);
header.setDomainUnits(domainUnits);
try {
timeSeriesContentHandler.setNewTimeSeriesHeader(header);
reader.require(XMLStreamConstants.END_ELEMENT, null, "header");
divider = Integerreader.parseIntnextTag(dividerText);
}
@SuppressWarnings("OverlyLongMethod")
private void parseEvent() } catch (NumberFormatException e)throws Exception {
assert binaryInputStream == null;
throw new Exception(ExceptionUtils.getMessage(e), ereader.require(XMLStreamConstants.START_ELEMENT, null, "event");
timeSeriesContentHandler.clearFlagSourceColumns();
}
String dateText = null;
String timeText = null;
if (divider == 0)String {
startDateText = null;
String startTimeText = null;
throw new Exception("dividplier is 0")String endDateText = null;
String endTimeText = null;
}
String valueText = null;
}
String valueSource = null;
reader.nextTag();
String minValueText = null;
return tu.getMillis() * multiplier / divider;
String maxValueText = null;
String }flagText else= {null;
String flagSource reader.nextTag()= null;
String comment = return 0null;
}String user = null;
}
privateString voidlimitText initHeader() {
= null;
for header.clear();
header.setFileDescription(virtualFileName);
currentHeaderElement = null;
(int i = 0, n = reader.getAttributeCount(); i < n; i++) {
String timeSteplocalName = nullreader.getAttributeLocalName(i);
timeStepMillisString attributeValue = 0reader.getAttributeValue(i);
startTime = Long.MIN_VALUE;
if (dateText == null && endTime = Long.MIN_VALUE;TextUtils.equals(localName, "date")) {
missingValue = Float.NaN;
dateText creationDateText = nullattributeValue;
creationTimeText = "00:00:00";
qualfiers.clear(); } else if (timeText == null && TextUtils.equals(localName, "time")) {
}
private void readValuesFromBinFile() throws Exception {
timeText = attributeValue;
TimeStep timeStep = header.getTimeStep();
} else if (!timeStep.isRegular(valueText == null && TextUtils.equals(localName, "value")) {
throw new Exception("Only equidistant timevalueText step= supportedattributeValue;
when pi events are stored in bin file instead of xml");
} else if (valueSource == null }
&& TextUtils.equals(localName, "valueSource")) {
boolean equidistantMillis = timeStep.isEquidistantMillis();
valueSource = attributeValue;
long stepMillis = equidistantMillis ? timeStep.getStepMillis() : Long.MIN_VALUE;
} else if (flagText == trynull && TextUtils.equals(localName, "flag")) {
for (long timeflagText = startTimeattributeValue;
time <= endTime;) {
} else if (flagSource == null && timeSeriesContentHandlerTextUtils.setTime(time);equals(localName, "flagSource")) {
ifflagSource (bufferPos == bufferCount) fillBuffer()= attributeValue;
} else if float value = floatBuffer[bufferPos++];
(comment == null && TextUtils.equals(localName, "comment")) {
// we can not usecomment the= automaticattributeValue;
missing value detection of the content handler because the missing value is} differentelse forif each(user time== series
null && TextUtils.equals(localName, "user")) {
if (value == missingValue) valueuser = Float.NaNattributeValue;
} else if timeSeriesContentHandler.setValue(value);
(startDateText == null && TextUtils.equals(localName, "startDate")) {
timeSeriesContentHandler.applyCurrentFields();
startDateText = attributeValue;
if (equidistantMillis) {
} else if (startTimeText == null && TextUtils.equals(localName, "startTime")) {
time += stepMillis;
startTimeText = attributeValue;
continue;
} else if (endDateText == null && TextUtils.equals(localName, }"endDate")) {
timeendDateText = timeStep.nextTime(time)attributeValue;
}
else if (endTimeText == null } catch (IOException e&& TextUtils.equals(localName, "endTime")) {
throw new Exception(ExceptionUtils.getMessage(e), e)endTimeText = attributeValue;
}
}
else if (minValueText == privatenull void fillBuffer() throws IOException {
&& TextUtils.equals(localName, "minValue")) {
int byteBufferCountminValueText = 0attributeValue;
while (byteBufferCount % NumberType.FLOAT_SIZE != 0 || byteBufferCount } else if (maxValueText == 0null && TextUtils.equals(localName, "maxValue")) {
int count = binaryInputStream.read(byteBuffer, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE - byteBufferCount) maxValueText = attributeValue;
assert count != 0; // see read javadoc
} else if (maxValueText == null && TextUtils.equals(localName, "detection")) {
if (countlimitText == -1) throw new EOFException("Bin file is too short");
attributeValue;
} else if (reader.getAttributePrefix(i) != null byteBufferCount += count;&& TextUtils.equals(reader.getAttributePrefix(i), "fs")) {
}
bufferCountint index = byteBufferCount / NumberType.FLOAT_SIZEtimeSeriesContentHandler.addFlagSourceColumn(localName);
BinaryUtils.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIAN timeSeriesContentHandler.setColumnFlagSource(index, attributeValue);
bufferPos = 0;
} else }{
private void initiateTimeStep() {
timeStepif = IrregularTimeStep.INSTANCE; //default timestep
(lenient) continue;
if (timeStepMillis == 0) {
throw new Exception("Unknown or duplicate attribute " + localName return+ " in event");
}
}
if (timeStepMillis % TimeUnit.MINUTE_MILLIS != 0) {}
parseTime(dateText, timeText, startDateText, startTimeText, if (!this.invalidHeaderTimeDetected) {endDateText, endTimeText);
parseFlagsUserComment(flagText, flagSource, comment, user);
if (log.isDebugEnabled()) log.debug("Header timestep and/or start time has not rounded minutes ! Irregular timestep wil be used."parseValue(valueText, valueSource, minValueText, maxValueText, limitText);
timeSeriesContentHandler.applyCurrentFields();
reader.require(XMLStreamConstants.END_ELEMENT, null, "event");
reader.nextTag();
}
this.invalidHeaderTimeDetectedprivate = true;
}void parseValue(String valueText, String valueSource, String minValueText, String maxValueText, String limit) throws Exception {
if timeStepMillis (domainCount == 0;) {
try return;{
}
long timeZoneOffsetMillisfloat value = valueText = -startTime % timeStepMillis;
= null ? Float.NaN : TextUtils.parseFloat(valueText);
if (timeZoneOffsetMillis % TimeUnit.MINUTE_MILLIS != 0) {
float minValue = minValueText == null ? value if: (!this.invalidHeaderTimeDetected) {TextUtils.parseFloat(minValueText);
if (log.isDebugEnabled()) log.debug("Header timestep and/or start time has not rounded minutes ! Irregular timestep wil be used.");
float maxValue = maxValueText == null ? value : TextUtils.parseFloat(maxValueText);
// we can not use the automatic missing this.invalidHeaderTimeDetectedvalue =detection true;
of the content handler because the missing value is different for each time }series
timeStepMillis = 0;
if (value == missingValue) {
return;
}
value timeStep = SimpleEquidistantTimeStep.getInstance(timeStepMillis, timeZoneOffsetMillis);
Float.NaN;
}
private void parseTimeZone() throws} Exceptionelse {
if (reader.getEventType() != XMLStreamConstants.START_ELEMENT) return;
if timeSeriesContentHandler.setValueResolution(!TextUtils.equals(reader.getLocalName(), "timeZone")) return;
getValueResolution(valueText, '.'));
try {}
double offset = DoubletimeSeriesContentHandler.parseDouble(reader.getElementText()setValueAndRange(value, minValue, maxValue);
TimeZone timeZoneFromDouble = TimeZoneUtils.createTimeZoneFromDouble(offsettimeSeriesContentHandler.setValueSource(TextUtils.equals(valueSource, "MAN") ? ValueSource.MANUAL : ValueSource.AUTOMATIC);
thistimeSeriesContentHandler.fastDateFormat.setTimeZone(timeZoneFromDoublesetOutOfDetectionRangeFlag(OutOfDetectionRangeFlag.get(getOutOfDetectionFlag(limit)));
} catch (NumberFormatException e) {
throw new Exception("Not valid timeZone format", eValue should be a float " + valueText);
}
reader.require(XMLStreamConstants.END_ELEMENT, null, "timeZone"nextTag();
reader.nextTag()return;
}
@SuppressWarnings({"OverlyLongMethod"})}
private void parseHeaderElement() throws Exceptionif {
(valueText switch (currentHeaderElement) {!= null)
casethrow type:
header.setParameterType(parseType(reader.getElementText())new Exception("Attribute value not allowed when having domain parameters, use event element text instead");
int count break= parseValueList();
if (count == case locationId:0) {
header.setLocationId(reader.getElementText()timeSeriesContentHandler.setValues(null);
return;
break;
}
caseint parameterId:
expectedCount = 1;
for (int i = 0, header.setParameterId(reader.getElementText());n = domainCount; i < n; i++) {
float[] axis break= axisValues[i];
case qualifierId:
if (axis == null)
qualfiers.add(reader.getElementText());
throw new Exception("Domain axis values for domain parameter " + domainParameterIds[i] + " are breakmissing");
expectedCount case ensembleId:*= axis.length;
}
if header.setEnsembleId(reader.getElementText());(expectedCount != count)
throw new Exception("Length of break;
value list for event does not matches the axes lengths");
case ensembleMemberIndex:
if (axesDirty) {
headertimeSeriesContentHandler.setEnsembleMemberIndex(parseEnsembleMemberIndex(reader.getElementText()))setDomainAxesValueResolutions(axisValueResolutions);
timeSeriesContentHandler.setDomainAxesValues(axisValues);
break;
axesDirty = false;
case timeStep:
}
if (count timeStepMillis!= floatBuffer.length) floatBuffer = parseTimeStep(FloatArrayUtils.resize(floatBuffer, expectedCount);
timeSeriesContentHandler.setValueResolution(minValueResolution);
breaktimeSeriesContentHandler.setValues(floatBuffer);
}
private byte getOutOfDetectionFlag(String case startDate:text) {
if (text == null || text.isEmpty()) startTime = parseTime()return TimeSeriesArray.INSIDE_DETECTION_RANGE;
char ch = text.charAt(0);
break;
if (ch == '<') return TimeSeriesArray.BELOW_DETECTION_RANGE;
case endDate:
if (ch == '>') return TimeSeriesArray.ABOVE_DETECTION_RANGE;
endTimeif (ch == parseTime()'~') return TimeSeriesArray.VARYING;
return TimeSeriesArray.INSIDE_DETECTION_RANGE;
}
break;
private void parseFlagsUserComment(String flagText, String flagSource, String comment, String user) throws caseException forecastDate:{
if (flagText == null) {
header.setForecastTime(parseTime());
timeSeriesContentHandler.setFlag(0);
break;} else {
casetry missVal:{
missingValue = parseMissingValue(reader.getElementText(timeSeriesContentHandler.setFlag(TextUtils.parseInt(flagText));
} catch (NumberFormatException e) break;{
case longName:
throw new Exception("Flag should be an integer " header.setLongName(reader.getElementText()+ flagText);
}
break;
}
case stationName:timeSeriesContentHandler.setComment(comment);
timeSeriesContentHandler.setUser(user);
headertimeSeriesContentHandler.setLocationName(reader.getElementText()setFlagSource(flagSource);
}
private void parseTime(String dateText, String timeText, String startDateText, break;
String startTimeText, String endDateText, String endTimeText) throws case units:Exception {
if (timeText header.setUnit(reader.getElementText());== null)
throw new Exception("Attribute time is breakmissing");
if (dateText == null)
case sourceOrganisation:
throw new Exception("Attribute date header.setSourceOrganisation(reader.getElementText()is missing");
try {
break;
long time = parseTime(dateText, timeText, null, null, case sourceSystem:Long.MIN_VALUE, lastTime, lastTimeAmbiguous);
lastTime header.setSourceSystem(reader.getElementText())= time;
lastTimeAmbiguous = breaktimeAmbiguous;
caselong fileDescription:
startTime = parseTime(startDateText, startTimeText, dateText, timeText, time, header.setFileDescription(reader.getElementText()lastStartTime, lastStartTimeAmbiguous);
lastStartTime = breakstartTime;
lastStartTimeAmbiguous case= creationDate:timeAmbiguous;
long endTime = creationDateText = reader.getElementText(parseTime(endDateText, endTimeText, dateText, dateText, time, lastEndTime, lastEndTimeAmbiguous);
lastEndTime = breakendTime;
lastEndTimeAmbiguous case= creationTime:timeAmbiguous;
timeSeriesContentHandler.setTimeAndRange(time, creationTimeText = reader.getElementText(startTime, endTime);
} catch (ParseException e) {
break;
throw new Exception("Can not caseparse region:
" + dateText + ' ' + timeText);
header.setRegion(reader.getElementText());}
}
private long parseTime(String dateText, String timeText, String defaultDateText, break;
String defaultTimeText, long defaultTime, long lastTime, boolean lastTimeAmbiguous) throws ParseException {
case thresholds:
if (dateText == null && timeText == null) return parseThresholds()defaultTime;
if (dateText == null) dateText = breakdefaultDateText;
}
if (timeText == null) timeText = defaultTimeText;
reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name());
boolean useMillis = readertimeText.nextTagcontains(".");
}
private void parseThresholds() throws XMLStreamException {
reader.nextTag(if (!fastDateFormat.getTimeZone().useDaylightTime()) return useMillis? fastDateFormatWithMillies.parseToMillis(dateText, timeText): fastDateFormat.parseToMillis(dateText, timeText);
ArrayList<String> ids = new ArrayList<String>()long t1;
ArrayList<String>try names{
= new ArrayList<String>();
ArrayList<String> stringValuest1 = new ArrayList<String>( useMillis?fastDateFormatWithMillies.parseToMillis(dateText, timeText): fastDateFormat.parseToMillis(dateText, timeText);
do} catch (ParseException e) {
if (reader!timeText.getEventType(equals("02:00:00")) == XMLStreamConstants.START_ELEMENT) {throw e;
// see FEWS-17782 also String id = reader.getAttributeValue(null, "id");
accept 02:00:00 on daylight saving time switch
String name = reader.getAttributeValue(null, "name");try {
String stringValue = reader.getAttributeValue(nullreturn fastDateFormat.parseToMillis(dateText, "value03:00:00");
} catch (ParseException ids.add(id);
e1) {
names.add(name);throw e;
stringValues.add(stringValue);}
}
}
Calendar calendar = useMillis? fastDateFormatWithMillies.getCalendar() : readerfastDateFormat.nextTaggetCalendar();
} while (!reader.getLocalName().equals(currentHeaderElement.name()))calendar.setTimeInMillis(t1);
float[]int valuestimeOfDay = new float[stringValues.size()]getTimeOfDay(calendar);
for (int ilong t2 = 0; i < values.length; i++) {getOtherTime(calendar, t1, timeOfDay);
timeAmbiguous = values[i] = Float.valueOf(stringValues.get(i))t1 != t2;
}
long minTime = Math.min(t1, t2);
header.setHighLevelThresholds(ids.toArray(new String[ids.size()]), names.toArray(new String[names.size()]), values);
}
private static float parseMissingValue(String gotString) throws Exception { long maxTime = Math.max(t1, t2);
long res = minTime > lastTime ? minTime : maxTime;
if (!lastTimeAmbiguous) return res;
// 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 + timeStepMillis != res) return lastTime + timeStepMillis;
if (timeStep != null && timeStep.isRegular() && timeStep.nextTime(lastTime) != res) return timeStep.nextTime(lastTime);
return res;
}
private static int getTimeOfDay(Calendar calendar) {
return (int) (calendar.get(Calendar.HOUR_OF_DAY) * 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 calendar, long time, int timeOfDay) {
try {
calendar.setTimeInMillis(time + TimeUnit.HOUR_MILLIS);
if (getTimeOfDay(calendar) == timeOfDay) return time + TimeUnit.HOUR_MILLIS;
calendar.setTimeInMillis(time - TimeUnit.HOUR_MILLIS);
if (getTimeOfDay(calendar) == timeOfDay) return time - TimeUnit.HOUR_MILLIS;
return time;
} finally {
calendar.setTimeInMillis(time);
}
}
private long parseTime() throws Exception {
String dateText = reader.getAttributeValue(null, "date");
if (dateText == null) {
throw new Exception("Attribute " + currentHeaderElement +
"-date is missing");
}
String timeText = reader.getAttributeValue(null, "time");
if (timeText == null) {
throw new Exception("Attribute " + currentHeaderElement +
"-time is missing");
}
boolean useMillis = timeText.contains(".");
long time;
try {
time = useMillis? fastDateFormatWithMillies.parseToMillis(dateText, timeText) : fastDateFormat.parseToMillis(dateText, timeText);
} catch (ParseException e) {
throw new Exception("Not a valid data time for "
+ currentHeaderElement + ' ' + dateText + ' ' + timeText, e);
}
reader.nextTag();
return time;
}
private void parseTimeStep() throws Exception {
String times = reader.getAttributeValue(null, "times");
if (times != null) {
timeStep = PiCastorUtils.createTimesOfDayTimeStep(times, getTimeZone());
reader.nextTag();
return;
}
String unit = reader.getAttributeValue(null, "unit");
TimeUnit tu = unit == null ? null : TimeUnit.get(unit);
if (tu != null) {
String multiplierText = reader.getAttributeValue(null, "multiplier");
int multiplier;
if (multiplierText == null) {
multiplier = 1;
} else {
try {
multiplier = Integer.parseInt(multiplierText);
} catch (NumberFormatException e) {
throw new Exception(ExceptionUtils.getMessage(e), e);
}
if (multiplier == 0) {
throw new Exception("Multiplier is 0");
}
}
String dividerText = reader.getAttributeValue(null, "divider");
int divider;
if (dividerText == null) {
divider = 1;
} else {
try {
divider = Integer.parseInt(dividerText);
} catch (NumberFormatException e) {
throw new Exception(ExceptionUtils.getMessage(e), e);
}
if (divider == 0) {
throw new Exception("divider is 0");
}
}
reader.nextTag();
timeStepMillis = tu.getMillis() * multiplier / divider;
timeStep = null;
} else {
reader.nextTag();
timeStepMillis = 0;
timeStep = IrregularTimeStep.INSTANCE;
}
}
private void initHeader() {
header.clear();
header.setFileDescription(virtualFileName);
currentHeaderElement = null;
timeStep = null;
timeStepMillis = 0;
startTime = Long.MIN_VALUE;
endTime = Long.MIN_VALUE;
missingValue = Float.NaN;
creationDateText = null;
creationTimeText = "00:00:00";
qualifiers.clear();
lat = Double.NaN;
lon = Double.NaN;
z = Double.NaN;
}
private void readValuesFromBinFile() throws Exception {
TimeStep timeStep = header.getTimeStep();
if (!timeStep.isRegular()) {
throw new Exception("Only equidistant time step supported when pi events are stored in bin file instead of xml");
}
boolean equidistantMillis = timeStep.isEquidistantMillis();
long stepMillis = equidistantMillis ? timeStep.getStepMillis() : Long.MIN_VALUE;
for (long time = startTime; time <= endTime;) {
timeSeriesContentHandler.setTime(time);
if (bufferPos == bufferCount) fillBuffer();
float value = floatBuffer[bufferPos++];
// 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 = Float.NaN;
timeSeriesContentHandler.setValue(value);
timeSeriesContentHandler.applyCurrentFields();
if (equidistantMillis) {
time += stepMillis;
continue;
}
time = timeStep.nextTime(time);
}
}
private void fillBuffer() throws IOException {
int byteBufferCount = 0;
while (byteBufferCount % NumberType.FLOAT_SIZE != 0 || byteBufferCount == 0) {
int count = binaryInputStream.read(byteBuffer, byteBufferCount, BUFFER_SIZE * NumberType.FLOAT_SIZE - byteBufferCount);
assert count != 0; // see read javadoc
if (count == -1) throw new EOFException("Bin file is too short");
byteBufferCount += count;
}
bufferCount = byteBufferCount / NumberType.FLOAT_SIZE;
BinaryUtils.copy(byteBuffer, 0, byteBufferCount, floatBuffer, 0, bufferCount, ByteOrder.LITTLE_ENDIAN);
bufferPos = 0;
}
private void initiateTimeStep() {
if (timeStep != null) {
assert timeStepMillis == 0;
return;
}
if (timeStepMillis == 0){
//no timestep in header. Fix for backward compatibility
timeStep = IrregularTimeStep.INSTANCE;
return;
}
if (timeStepMillis >= TimeUnit.HOUR_MILLIS && getTimeZone().useDaylightTime()) {
timeStep = IrregularTimeStep.INSTANCE;
return;
}
long startTime = this.startTime == Long.MIN_VALUE ? 0L : this.startTime;
if (timeStepMillis % TimeUnit.SECOND_MILLIS != 0) {
timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime);
return;
}
long timeZoneOffsetMillis = -startTime % timeStepMillis;
if (timeZoneOffsetMillis % TimeUnit.MINUTE_MILLIS != 0) {
timeStep = RelativeEquidistantTimeStep.getInstance(timeStepMillis, startTime);
return;
}
timeStep = SimpleEquidistantTimeStep.getInstance(timeStepMillis, timeZoneOffsetMillis);
}
@SuppressWarnings({"OverlyLongMethod"})
private void parseHeaderElement() throws Exception {
switch (currentHeaderElement) {
case type:
header.setParameterType(parseType(reader.getElementText()));
break;
case moduleInstanceId:
header.setModuleInstanceId(reader.getElementText());
break;
case locationId:
// see FEWS-9858, when there is no location id the time series are assigned to all locations
// this is a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty strings
header.setLocationId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"));
break;
case parameterId:
// see FEWS-9858, when there is no parameter id the time series are assigned to all locations
// this is a flaw in the pi_timeSeries.xsd, the location element is required but is allowed empty strings
header.setParameterId(TextUtils.defaultIfNull(TextUtils.trimToNull(reader.getElementText()), "none"));
break;
case qualifierId:
qualifiers.add(reader.getElementText());
break;
case ensembleId:
header.setEnsembleId(reader.getElementText());
break;
case ensembleMemberIndex:
header.setEnsembleMemberIndex(parseEnsembleMemberIndex(reader.getElementText()));
break;
case ensembleMemberId:
header.setEnsembleMemberId(reader.getElementText());
break;
case timeStep:
parseTimeStep();
break;
case startDate:
startTime = parseTime();
break;
case endDate:
endTime = parseTime();
break;
case forecastDate:
header.setForecastTime(parseTime());
break;
case approvedDate:
header.setApprovedTime(parseTime());
break;
case missVal:
missingValue = parseString(reader.getElementText());
break;
case longName:
header.setLongName(reader.getElementText());
break;
case stationName:
header.setLocationName(reader.getElementText());
break;
case units:
header.setUnit(reader.getElementText());
break;
case domainAxis:
parseDomainAxis();
break;
case sourceOrganisation:
header.setSourceOrganisation(reader.getElementText());
break;
case sourceSystem:
header.setSourceSystem(reader.getElementText());
break;
case fileDescription:
header.setFileDescription(reader.getElementText());
break;
case creationDate:
creationDateText = reader.getElementText();
break;
case creationTime:
creationTimeText = reader.getElementText();
break;
case region:
header.setRegion(reader.getElementText());
break;
case thresholds:
parseThresholds();
break;
case lat:
lat = parseString(reader.getElementText());
break;
case lon:
lon = parseString(reader.getElementText());
break;
case x:
reader.getElementText();
break;
case y:
reader.getElementText();
break;
case z:
z = parseString(reader.getElementText());
break;
case firstValueTime:
reader.getElementText();
break;
case lastValueTime:
reader.getElementText();
break;
case maxValue:
reader.getElementText();
break;
case minValue:
reader.getElementText();
break;
case valueCount:
reader.getElementText();
break;
case maxWarningLevelName:
reader.getElementText();
break;
}
reader.require(XMLStreamConstants.END_ELEMENT, null, currentHeaderElement.name());
reader.nextTag();
}
private void parseDomainAxis() throws Exception {
String parameterId = reader.getAttributeValue(null, "parameterId");
if (parameterId == null)
throw new Exception("Attribute parameterId for domainUnits is missing");
domainParameterIds = Clasz.strings.ensureCapacity(domainParameterIds, domainCount + 1);
domainUnits = Clasz.strings.ensureCapacity(domainUnits, domainCount + 1);
domainParameterIds[domainCount] = parameterId;
domainUnits[domainCount] = reader.getAttributeValue(null, "units");
domainCount++;
reader.nextTag();
}
private void parseThresholds() throws XMLStreamException {
reader.nextTag();
ArrayList<DefaultTimeSeriesHeader.DefaultThreshold> thresholds = new ArrayList<>();
do {
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) {
String id = reader.getAttributeValue(null, "id");
String name = reader.getAttributeValue(null, "name");
float value = TextUtils.parseFloat(reader.getAttributeValue(null, "value"));
String groupId = reader.getAttributeValue(null, "groupId");
String groupName = reader.getAttributeValue(null, "groupName");
String[] groupIds = groupId == null ? Clasz.strings.emptyArray() : new String[] {groupId};
String[] groupNames = groupName == null ? Clasz.strings.emptyArray() : new String[] {groupName};
thresholds.add(new DefaultTimeSeriesHeader.DefaultThreshold(id, name, value, groupIds, groupNames));
}
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":
try {
if (time.contains(".")) {
propertyBuilder.addDateTime(key, fastDateFormatWithMillies.parseToMillis(date, time));
} else {
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 parseString(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);
assert element != null; // contract of valueOf
} 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 " + 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;
}
} |