package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.opensha2.internal.Parsing.rangeStringToIntList;
import static org.opensha2.internal.Parsing.readDouble;
import static org.opensha2.internal.Parsing.readEnum;
import static org.opensha2.internal.Parsing.readInt;
import static org.opensha2.internal.Parsing.readString;
import static org.opensha2.internal.SourceAttribute.DEPTH;
import static org.opensha2.internal.SourceAttribute.DIP;
import static org.opensha2.internal.SourceAttribute.ID;
import static org.opensha2.internal.SourceAttribute.INDICES;
import static org.opensha2.internal.SourceAttribute.NAME;
import static org.opensha2.internal.SourceAttribute.RAKE;
import static org.opensha2.internal.SourceAttribute.TYPE;
import static org.opensha2.internal.SourceAttribute.WEIGHT;
import static org.opensha2.internal.SourceAttribute.WIDTH;
import static org.opensha2.mfd.MfdType.GR;
import static org.opensha2.mfd.MfdType.GR_TAPER;
import static org.opensha2.mfd.MfdType.INCR;
import static org.opensha2.mfd.MfdType.SINGLE;
import org.opensha2.eq.fault.surface.GriddedSurface;
import org.opensha2.eq.model.MfdHelper.SingleData;
import org.opensha2.internal.SourceElement;
import org.opensha2.mfd.IncrementalMfd;
import org.opensha2.mfd.MfdType;
import org.opensha2.mfd.Mfds;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.parsers.SAXParser;
/*
* Non-validating indexed fault source parser. SAX parser 'Attributes' are
* stateful and cannot be stored. This class is not thread safe.
*
* @author Peter Powers
*/
@SuppressWarnings("incomplete-switch")
class SystemParser extends DefaultHandler {
static final String GRIDSOURCE_FILENAME = "grid_sources.xml";
static final String RUPTURES_FILENAME = "fault_ruptures.xml";
static final String SECTIONS_FILENAME = "fault_sections.xml";
private final Logger log = Logger.getLogger(SystemParser.class.getName());
private final SAXParser sax;
private boolean used = false;
private Locator locator;
private GmmSet gmmSet;
// TODO can these be RuptureSurfaces??
private List<GriddedSurface> sections;
private List<String> sectionNames;
private SystemSourceSet sourceSet;
private SystemSourceSet.Builder sourceSetBuilder;
/*
* Currently, SystemSourceSets simply maintain lists of magnitudes and rates
* for each rupture, but they may be refactored in the future to support one
* or more MFDs (e.g. aleatory uncertainty on magnitude may be desired). This
* parser has been constructed assuming this will be the case and although it
* adds some unnecessary overhead at this time (mag and rate are simply pulled
* from the MFD for the SystemSourceSet.Builder; the MFD reference is not
* retained), it ensures consistency with other SourceSet implementations and
* that MFDs are fully specified between sources and defaults.
*/
private IncrementalMfd mfd;
// Default MFD data
private boolean parsingDefaultMFDs = false;
private MfdHelper.Builder mfdHelperBuilder;
private MfdHelper mfdHelper;
// Traces are the only text content in source files
private boolean readingTrace = false;
private StringBuilder traceBuilder = null;
private SystemParser(SAXParser sax) {
this.sax = sax;
}
static SystemParser create(SAXParser sax) {
return new SystemParser(checkNotNull(sax));
}
SystemSourceSet parse(InputStream sectionsIn, InputStream rupturesIn, GmmSet gmmSet)
throws SAXException, IOException {
checkState(!used, "This parser has expired");
this.gmmSet = gmmSet;
parseSections(sectionsIn);
sax.parse(rupturesIn, this);
checkState(sourceSet.size() > 0, "SystemSourceSet is empty");
used = true;
return sourceSet;
}
private void parseSections(InputStream in) throws SAXException, IOException {
SystemSectionParser parser = SystemSectionParser.create(sax);
parser.parse(in);
sections = parser.sections();
sectionNames = parser.sectionNames();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
try {
switch (e) {
case SYSTEM_SOURCE_SET:
String name = readString(NAME, atts);
int id = readInt(ID, atts);
double weight = readDouble(WEIGHT, atts);
sourceSetBuilder = new SystemSourceSet.Builder();
sourceSetBuilder
.name(name)
.id(id)
.weight(weight)
.gmms(gmmSet);
sourceSetBuilder.sections(sections);
sourceSetBuilder.sectionNames(sectionNames);
log.info(" Weight: " + weight);
log.info(" Sections: " + sections.size());
log.info(" Ruptures: " + name + "/" + RUPTURES_FILENAME);
mfdHelperBuilder = MfdHelper.builder();
mfdHelper = mfdHelperBuilder.build(); // dummy; usually
// overwritten
break;
case DEFAULT_MFDS:
parsingDefaultMFDs = true;
break;
case INCREMENTAL_MFD:
if (parsingDefaultMFDs) {
mfdHelperBuilder.addDefault(atts);
break;
}
mfd = buildMfd(atts);
break;
case GEOMETRY:
sourceSetBuilder
.mag(mfd.getX(0))
.rate(mfd.getY(0))
.indices(rangeStringToIntList(readString(INDICES, atts)))
.depth(readDouble(DEPTH, atts))
.dip(readDouble(DIP, atts))
.rake(readDouble(RAKE, atts))
.width(readDouble(WIDTH, atts));
break;
}
} catch (Exception ex) {
throw new SAXParseException("Error parsing <" + qName + ">", locator, ex);
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
SourceElement e = null;
try {
e = SourceElement.fromString(qName);
} catch (IllegalArgumentException iae) {
throw new SAXParseException("Invalid element <" + qName + ">", locator, iae);
}
try {
switch (e) {
case DEFAULT_MFDS:
parsingDefaultMFDs = false;
mfdHelper = mfdHelperBuilder.build();
checkDefaultMfds();
break;
case SYSTEM_SOURCE_SET:
sourceSet = sourceSetBuilder.build();
log.info(" Size: " + sourceSet.size());
break;
}
} catch (Exception ex) {
throw new SAXParseException("Error parsing <" + qName + ">", locator, ex);
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
if (readingTrace) {
traceBuilder.append(ch, start, length);
}
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
private void checkDefaultMfds() {
checkState(
mfdHelper.typeCount(SINGLE) <= 1 &&
mfdHelper.typeCount(INCR) == 0 &&
mfdHelper.typeCount(GR) == 0 &&
mfdHelper.typeCount(GR_TAPER) == 0,
"Only one SINGLE default MFD may be defined");
}
private IncrementalMfd buildMfd(Attributes atts) {
MfdType type = readEnum(TYPE, atts, MfdType.class);
checkState(type == MfdType.SINGLE, "Only SINGLE mfds are supported");
// ensures only one SINGLE mfd exists
SingleData singleData = Iterables.getOnlyElement(mfdHelper.singleData(atts));
return Mfds.newSingleMFD(singleData.m, singleData.rate * singleData.weight,
singleData.floats);
}
}