package org.opensha2.eq.model;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
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.NAME;
import static org.opensha2.internal.SourceAttribute.RAKE;
import static org.opensha2.internal.SourceAttribute.RUPTURE_SCALING;
import static org.opensha2.internal.SourceAttribute.WEIGHT;
import static org.opensha2.internal.SourceAttribute.WIDTH;
import org.opensha2.eq.fault.surface.RuptureScaling;
import org.opensha2.eq.model.MfdHelper.GR_Data;
import org.opensha2.eq.model.MfdHelper.SingleData;
import org.opensha2.geo.LocationList;
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 java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.parsers.SAXParser;
/*
* Non-validating subduction source parser. SAX parser 'Attributes' are stateful
* and cannot be stored. This class is not thread safe.
*
* @author Peter Powers
*/
@SuppressWarnings("incomplete-switch")
class InterfaceParser extends DefaultHandler {
private final Logger log = Logger.getLogger(InterfaceParser.class.getName());
private final SAXParser sax;
private boolean used = false;
private Locator locator;
private GmmSet gmmSet;
private ModelConfig config;
private InterfaceSourceSet sourceSet;
private InterfaceSourceSet.Builder sourceSetBuilder;
private InterfaceSource.Builder sourceBuilder;
private RuptureScaling rupScaling;
// 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 InterfaceParser(SAXParser sax) {
this.sax = sax;
}
static InterfaceParser create(SAXParser sax) {
return new InterfaceParser(checkNotNull(sax));
}
InterfaceSourceSet parse(InputStream in, GmmSet gmmSet, ModelConfig config) throws SAXException,
IOException {
checkState(!used, "This parser has expired");
this.gmmSet = gmmSet;
this.config = config;
sax.parse(in, this);
checkState(sourceSet.size() > 0, "InterfaceSourceSet is empty");
used = true;
return sourceSet;
}
@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 SUBDUCTION_SOURCE_SET:
String name = readString(NAME, atts);
int id = readInt(ID, atts);
double weight = readDouble(WEIGHT, atts);
sourceSetBuilder = new InterfaceSourceSet.Builder();
sourceSetBuilder
.name(name)
.id(id)
.weight(weight)
.gmms(gmmSet);
if (log.isLoggable(FINE)) {
log.fine("");
log.fine(" Name: " + name);
log.fine(" Weight: " + weight);
}
mfdHelperBuilder = MfdHelper.builder();
mfdHelper = mfdHelperBuilder.build(); // dummy; usually overwritten
break;
case DEFAULT_MFDS:
parsingDefaultMFDs = true;
break;
case SOURCE_PROPERTIES:
rupScaling = readEnum(RUPTURE_SCALING, atts, RuptureScaling.class);
log.fine("Rup scaling: " + rupScaling);
break;
case SOURCE:
String srcName = readString(NAME, atts);
int srcId = readInt(ID, atts);
sourceBuilder = new InterfaceSource.Builder();
sourceBuilder.name(srcName);
sourceBuilder.id(srcId);
sourceBuilder.ruptureScaling(rupScaling);
sourceBuilder.ruptureFloating(config.ruptureFloating);
sourceBuilder.ruptureVariability(config.ruptureVariability);
sourceBuilder.surfaceSpacing(config.surfaceSpacing);
log.fine(" Source: " + srcName);
break;
case INCREMENTAL_MFD:
if (parsingDefaultMFDs) {
mfdHelperBuilder.addDefault(atts);
break;
}
sourceBuilder.mfds(buildMfds(atts));
break;
case GEOMETRY:
sourceBuilder.rake(readDouble(RAKE, atts));
/*
* At present, an InterfaceSource geometry may be defined with an
* upper trace, dip, depth and width, or and upper and lower trace.
* Rake is always required (above), but the three scalar parameters
* (dip, depth, and width) must be conditionally read. The
* InterfaceSource.Builder will check if either construction technique
* has been satisfied.
*/
try {
sourceBuilder.depth(readDouble(DEPTH, atts))
.dip(readDouble(DIP, atts))
.width(readDouble(WIDTH, atts));
} catch (NullPointerException npe) {
// keep moving, these atts are not necessarily required
}
break;
case TRACE:
readingTrace = true;
traceBuilder = new StringBuilder();
break;
case LOWER_TRACE:
readingTrace = true;
traceBuilder = new StringBuilder();
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();
break;
case TRACE:
readingTrace = false;
sourceBuilder.trace(LocationList.fromString(traceBuilder.toString()));
break;
case LOWER_TRACE:
readingTrace = false;
sourceBuilder.lowerTrace(LocationList.fromString(traceBuilder.toString()));
break;
case SOURCE:
sourceSetBuilder.source(sourceBuilder.buildSubductionSource());
log.finer(""); // insert blank line for detailed source
// output
break;
case SUBDUCTION_SOURCE_SET:
sourceSet = sourceSetBuilder.buildSubductionSet();
}
} 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 List<IncrementalMfd> buildMfds(Attributes atts) {
MfdType type = MfdType.valueOf(atts.getValue("type"));
switch (type) {
case GR:
return buildGR(atts);
case SINGLE:
return buildSingle(atts);
default:
throw new IllegalStateException(type + " not supported");
}
}
/*
* InterfaceSource.Builder creates an ImmutableList; so no need for one in
* methods below.
*/
/*
* Build GR MFDs. Method throws IllegalStateException if attribute values
* yield an MFD with no magnitude bins.
*/
private List<IncrementalMfd> buildGR(Attributes atts) {
List<IncrementalMfd> mfdList = new ArrayList<>();
for (GR_Data grData : mfdHelper.grData(atts)) {
mfdList.add(buildGR(grData));
}
return mfdList;
}
private IncrementalMfd buildGR(GR_Data data) {
int nMag = Mfds.magCount(data.mMin, data.mMax, data.dMag);
checkState(nMag > 0, "GR MFD with no mags");
double tmr = Mfds.totalMoRate(data.mMin, nMag, data.dMag, data.a, data.b);
IncrementalMfd mfd = Mfds.newGutenbergRichterMoBalancedMFD(data.mMin, data.dMag, nMag,
data.b, tmr * data.weight);
log.finer(" MFD type: GR");
if (log.isLoggable(FINEST)) {
log.finest(mfd.getMetadataString());
}
return mfd;
}
/* Build SINGLE MFDs. */
private List<IncrementalMfd> buildSingle(Attributes atts) {
List<IncrementalMfd> mfdList = new ArrayList<>();
for (SingleData singleData : mfdHelper.singleData(atts)) {
mfdList.add(buildSingle(singleData));
}
return mfdList;
}
private IncrementalMfd buildSingle(SingleData data) {
IncrementalMfd mfd = Mfds.newSingleMFD(data.m, data.weight * data.rate, data.floats);
log.finer(" MFD type: SINGLE");
if (log.isLoggable(FINEST)) {
log.finest(mfd.getMetadataString());
}
return mfd;
}
}