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.eq.model.SystemParser.SECTIONS_FILENAME; import static org.opensha2.internal.Parsing.readDouble; import static org.opensha2.internal.Parsing.readString; import static org.opensha2.internal.SourceAttribute.ASEIS; import static org.opensha2.internal.SourceAttribute.DEPTH; import static org.opensha2.internal.SourceAttribute.DIP; import static org.opensha2.internal.SourceAttribute.DIP_DIR; import static org.opensha2.internal.SourceAttribute.INDEX; import static org.opensha2.internal.SourceAttribute.LOWER_DEPTH; import static org.opensha2.internal.SourceAttribute.NAME; import org.opensha2.eq.fault.surface.DefaultGriddedSurface; import org.opensha2.eq.fault.surface.GriddedSurface; import org.opensha2.geo.LocationList; import org.opensha2.internal.SourceElement; 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.Lists; 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 section parser. SAX parser 'Attributes' are * stateful and cannot be stored. This class is not thread safe. The List * returned by parse() is mutable as it will be made immutable when ultimately * passed to SystemSourceSet.Builder * * @author Peter Powers */ @SuppressWarnings("incomplete-switch") class SystemSectionParser extends DefaultHandler { private final Logger log = Logger.getLogger(SystemParser.class.getName()); private final SAXParser sax; private boolean used = false; private Locator locator; private List<GriddedSurface> sections; private List<String> sectionNames; private DefaultGriddedSurface.Builder surfaceBuilder; // Traces are the only text content in source files private boolean readingTrace = false; private StringBuilder traceBuilder = null; private SystemSectionParser(SAXParser sax) { this.sax = sax; } static SystemSectionParser create(SAXParser sax) { return new SystemSectionParser(checkNotNull(sax)); } // TODO can we just return RuptureSurface? are grid details necessary // downstream? void parse(InputStream in) throws SAXException, IOException { checkState(!used, "This parser has expired"); sax.parse(in, this); checkState(sections.size() > 0, "Section surface list is empty"); used = true; } /* Can't call before parse(). */ List<GriddedSurface> sections() { checkState(used == true); return sections; } /* Can't call before parse(). */ List<String> sectionNames() { checkState(used == true); return 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_FAULT_SECTIONS: sections = Lists.newArrayList(); sectionNames = Lists.newArrayList(); String setName = readString(NAME, atts); log.info("Fault model: " + setName + "/" + SECTIONS_FILENAME); break; /* * NOTE: currently, section indices are ordered ascending from zero; if * this were to change we'd need agregate a list of indices as well. */ case SECTION: surfaceBuilder = DefaultGriddedSurface.builder(); String sectionName = readString(NAME, atts); sectionNames.add(cleanName(sectionName)); String sectionIndex = readString(INDEX, atts); log.finer(" Section: [" + sectionIndex + "] " + sectionName); break; case GEOMETRY: double aseis = readDouble(ASEIS, atts); double depth = readDouble(DEPTH, atts); double lowerDepth = readDouble(LOWER_DEPTH, atts); surfaceBuilder.depth(depth) .lowerDepth(lowerDepth) .aseis(aseis) .dip(readDouble(DIP, atts)) .dipDir(readDouble(DIP_DIR, atts)); break; case 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 TRACE: readingTrace = false; surfaceBuilder.trace(LocationList.fromString(traceBuilder.toString())); break; case SECTION: sections.add(surfaceBuilder.build()); 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; } /* * TODO for consistency during development we have kept the original (long) * UCERF3 section names in fault_sections.xml but it might make sense down * the road to update the source file rather thna clean the name here. */ private static String cleanName(String name) { return name.replace(" 2011 CFM", "").replace(", Subsection ", " [") + "]"; } }