package nl.wldelft.fews.pi;
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import nl.wldelft.util.FastDateFormat;
import nl.wldelft.util.FileUtils;
import nl.wldelft.util.Period;
import nl.wldelft.util.TimeUnit;
import nl.wldelft.util.io.LineWriter;
import nl.wldelft.util.io.LittleEndianDataOutputStream;
import nl.wldelft.util.io.TextSerializer;
import nl.wldelft.util.io.VirtualOutputDir;
import nl.wldelft.util.io.VirtualOutputDirConsumer;
import nl.wldelft.util.timeseries.TimeSeriesContent;
import nl.wldelft.util.timeseries.TimeSeriesHeader;
import nl.wldelft.util.timeseries.TimeStep;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
public class PiTimeSeriesSerializer implements TextSerializer<TimeSeriesContent>, VirtualOutputDirConsumer {
public enum EventDestination {XML_EMBEDDED, SEPARATE_BINARY_FILE, ONLY_HEADERS}
private EventDestination eventDestination = EventDestination.XML_EMBEDDED;
private PiVersion version = PiVersion.VERSION_1_2;
private final FastDateFormat dateFormat = new FastDateFormat("yyyy-MM-dd");
private final FastDateFormat timeFormat = new FastDateFormat("HH:mm:ss");
private TimeSeriesContent timeSeriesContent = null;
private LineWriter writer = null;
private ContentHandler xmlContentHandler = null;
private VirtualOutputDir virtualOutputDir = null;
private LittleEndianDataOutputStream binaryOutputSteam = null;
private final AttributesImpl attributesBuffer = new AttributesImpl();
public PiTimeSeriesSerializer() {
}
public EventDestination getEventDestination() {
return eventDestination;
}
public void setEventDestination(EventDestination eventDestination) {
this.eventDestination = eventDestination;
}
public PiVersion getVersion() {
return version;
}
public void setVersion(PiVersion version) {
if (version == null)
throw new IllegalArgumentException("version == null");
this.version = version;
}
@Override
public void setVirtualOutputDir(VirtualOutputDir virtualOutputDir) {
this.virtualOutputDir = virtualOutputDir;
}
@Override
public void serialize(TimeSeriesContent timeSeriesContent, LineWriter writer, String virtualFileName) throws Exception {
this.timeSeriesContent = timeSeriesContent;
this.writer = writer;
dateFormat.setTimeZone(this.timeSeriesContent.getDefaultTimeZone());
timeFormat.setTimeZone(this.timeSeriesContent.getDefaultTimeZone());
if (eventDestination == EventDestination.SEPARATE_BINARY_FILE) {
if (virtualOutputDir == null)
throw new IllegalStateException("virtualOutputDir == null");
binaryOutputSteam = new LittleEndianDataOutputStream(virtualOutputDir.getOutputStream(FileUtils.getPathWithOtherExtension(virtualFileName, "bin")));
try {
serialize();
} finally {
binaryOutputSteam.close();
}
return;
}
binaryOutputSteam = null;
serialize();
}
private void serialize() throws Exception {
XMLSerializer serializer = new XMLSerializer();
OutputFormat of = new OutputFormat("XML", "UTF-8", true);
serializer.setOutputFormat(of);
serializer.setOutputCharStream(writer);
xmlContentHandler = serializer.asContentHandler();
xmlContentHandler.startDocument();
attributesBuffer.clear();
addAttribute("xmlns", "http://www.wldelft.nl/fews/PI");
addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
addAttribute("xsi:schemaLocation", PiSchemaLocations.get("pi_timeseries.xsd"));
addAttribute("version", version.toString());
xmlContentHandler.startElement("", "TimeSeries", "TimeSeries", attributesBuffer);
writeElement("timeZone", String.valueOf((double) timeSeriesContent.getDefaultTimeZone().getRawOffset() / (double) TimeUnit.HOUR_MILLIS));
for (int i = 0, n = timeSeriesContent.getTimeSeriesCount(); i < n; i++) {
timeSeriesContent.setTimeSeriesIndex(i);
xmlContentHandler.startElement(null, null, "series", null);
writeHeader();
writeEvents();
xmlContentHandler.endElement(null, null, "series");
}
xmlContentHandler.endElement(null, null, "TimeSeries");
xmlContentHandler.endDocument();
}
private void writeEvents() throws Exception {
for (int i = 0, n = timeSeriesContent.getContentTimeCount(); i < n; i++) {
timeSeriesContent.setContentTimeIndex(i);
if (!timeSeriesContent.isTimeAvailable()) continue;
writeEvent(timeSeriesContent.getTime());
}
}
private void writeHeader() throws Exception {
TimeSeriesHeader header = timeSeriesContent.getTimeSeriesHeader();
PiTimeSeriesHeader piHeader = header instanceof PiTimeSeriesHeader ? (PiTimeSeriesHeader) header : new PiTimeSeriesHeader();
xmlContentHandler.startElement(null, null, "header", null);
writeElement("type", header.getParameterType() == null ? "instantaneous" : header.getParameterType().getName());
writeElement("locationId", header.getLocationId() == null ? "unknown" : header.getLocationId());
writeElement("parameterId", header.getParameterId() == null ? "unknown" : header.getParameterId());
if (version.ordinal() >= PiVersion.VERSION_1_4.ordinal()) {
for (int i = 0, n = header.getQualifierCount(); i < n; i++) {
writeElement("qualifierId", header.getQualifierId(i));
}
}
if (version.ordinal() >= PiVersion.VERSION_1_4.ordinal() && header.getEnsembleId() != null && !header.getEnsembleId().equals("main")) {
writeOptionalElement("ensembleId", header.getEnsembleId());
writeOptionalElement("ensembleMemberIndex", header.getEnsembleMemberIndex());
}
writeTimeStep(header);
writePeriod();
if (version.ordinal() >= PiVersion.VERSION_1_5.ordinal()) writeTime("forecastDate", header.getForecastTime());
writeElement("missVal", Float.toString(timeSeriesContent.getDefaultMissingValue()));
writeOptionalElement("longName", piHeader.getLongName());
writeOptionalElement("stationName", header.getLocationName());
writeOptionalElement("units", header.getUnit());
writeOptionalElement("sourceOrganisation", piHeader.getSourceOrganisation());
writeOptionalElement("sourceSystem", piHeader.getSourceSystem());
writeOptionalElement("fileDescription", piHeader.getFileDescription());
if (header.getCreationTime() != Long.MIN_VALUE) {
writeElement("creationDate", dateFormat.format(header.getCreationTime()));
writeElement("creationTime", timeFormat.format(header.getCreationTime()));
}
writeOptionalElement("region", piHeader.getRegion());
xmlContentHandler.endElement(null, null, "header");
}
private void writePeriod() throws SAXException {
TimeStep timeStep = timeSeriesContent.getTimeSeriesHeader().getTimeStep();
Period period = timeSeriesContent.getTimeSeriesPeriod();
Period headerPeriod;
if (period == Period.NEVER) {
// create a dummy period
long now = timeStep.nearestTime(System.currentTimeMillis());
headerPeriod = new Period(now, now);
} else {
headerPeriod = period;
}
writeTime("startDate", headerPeriod.getStartTime());
writeTime("endDate", headerPeriod.getEndTime());
}
private void writeTime(String name, long time) throws SAXException {
if (time == Long.MIN_VALUE) return;
attributesBuffer.clear();
addAttribute("date", dateFormat.format(time));
addAttribute("time", timeFormat.format(time));
writeAttributes(name);
}
private void writeTimeStep(TimeSeriesHeader header) throws SAXException {
TimeStep timeStep = header.getTimeStep();
attributesBuffer.clear();
// todo add support for month time step
if (timeStep.isEquidistantMillis()) {
long seconds = timeStep.getStepMillis() / 1000;
addAttribute("unit","second");
addAttribute("multiplier", String.valueOf(seconds));
} else {
addAttribute("unit", "nonequidistant");
}
writeAttributes("timeStep");
}
private void writeEvent(long time) throws Exception {
if (eventDestination == EventDestination.ONLY_HEADERS) return;
if (eventDestination == EventDestination.SEPARATE_BINARY_FILE) {
binaryOutputSteam.writeFloat(timeSeriesContent.getValue());
return;
}
attributesBuffer.clear();
addAttribute("date", dateFormat.format(time));
addAttribute("time", timeFormat.format(time));
addAttribute("value", timeSeriesContent.getValue('.'));
addAttribute("flag", timeSeriesContent.getStringFlag());
if (version.ordinal() >= PiVersion.VERSION_1_3.ordinal()) {
String comment = timeSeriesContent.getComment();
if (comment != null) addAttribute("comment", timeSeriesContent.getComment());
}
writeAttributes("event");
}
private void writeOptionalElement(String elementName, int index) throws SAXException {
if (index == -1) return;
writeElement(elementName, Integer.toString(index));
}
private void writeOptionalElement(String elementName, String s) throws SAXException {
if (s == null) return;
if (s.trim().length() == 0) return;
writeElement(elementName, s);
}
private void writeElement(String name, String value) throws SAXException {
xmlContentHandler.startElement(null, null, name, null);
xmlContentHandler.characters(value.toCharArray(), 0, value.length());
xmlContentHandler.endElement(null, null, name);
}
private void writeAttributes(String name) throws SAXException {
xmlContentHandler.startElement("", name, name, attributesBuffer);
xmlContentHandler.endElement(null, null, name);
}
private void addAttribute(String name, String value) {
attributesBuffer.addAttribute("", name, name, "CDATA", value);
}
}
|