package org.opensha2.eq.model; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.padEnd; import static java.nio.charset.StandardCharsets.UTF_8; import static org.opensha2.internal.TextUtils.LOG_INDENT; import static org.opensha2.internal.TextUtils.LOG_VALUE_COLUMN; import org.opensha2.eq.fault.surface.RuptureFloating; import org.opensha2.eq.model.AreaSource.GridScaling; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import java.io.IOException; import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; /** * Model and calculation configuration class. No defaults; 'config.json' must be * supplied with model. * * @author Peter Powers */ final class ModelConfig { static final String FILE_NAME = "config.json"; private static final String ID = ModelConfig.class.getSimpleName(); private static final String STATE_ERROR = "%s %s not set"; private static final String ELEMENT_NAME = "model"; private static final Gson GSON = new GsonBuilder().create(); private final Path resource; final String name; final double surfaceSpacing; final RuptureFloating ruptureFloating; final boolean ruptureVariability; final PointSourceType pointSourceType; final GridScaling areaGridScaling; private ModelConfig( String name, Path resource, double surfaceSpacing, RuptureFloating ruptureFloating, boolean ruptureVariability, PointSourceType pointSourceType, GridScaling areaGridScaling) { this.name = name; this.resource = resource; this.surfaceSpacing = surfaceSpacing; this.ruptureFloating = ruptureFloating; this.ruptureVariability = ruptureVariability; this.pointSourceType = pointSourceType; this.areaGridScaling = areaGridScaling; } private enum Key { NAME, RESOURCE, SURFACE_SPACING, RUPTURE_FLOATING, RUPTURE_VARIABILITY, POINT_SOURCE_TYPE, AREA_GRID_SCALING; private String label; private Key() { this.label = UPPER_UNDERSCORE.to(LOWER_CAMEL, name()); } @Override public String toString() { return label; } } @Override public String toString() { return new StringBuilder("Model Config: ") .append(resource.toAbsolutePath().normalize()) .append(LOG_INDENT).append("Model") .append(formatEntry(Key.NAME, name)) .append(formatEntry(Key.SURFACE_SPACING, surfaceSpacing)) .append(formatEntry(Key.RUPTURE_FLOATING, ruptureFloating)) .append(formatEntry(Key.RUPTURE_VARIABILITY, ruptureVariability)) .append(formatEntry(Key.POINT_SOURCE_TYPE, pointSourceType)) .append(formatEntry(Key.AREA_GRID_SCALING, areaGridScaling)) .toString(); } private static final String KEY_INDENT = LOG_INDENT + " "; private static <E extends Enum<E>> String formatEntry(E key, Object value) { return padEnd(KEY_INDENT + '.' + key + ':', LOG_VALUE_COLUMN, ' ') + value; } final static class Builder { private boolean built = false; private String name; private Path resource; private Double surfaceSpacing; private RuptureFloating ruptureFloating; private Boolean ruptureVariability; private PointSourceType pointSourceType; private GridScaling areaGridScaling; static Builder copyOf(ModelConfig that) { checkNotNull(that); Builder b = new Builder(); b.name = that.name; b.resource = that.resource; b.surfaceSpacing = that.surfaceSpacing; b.ruptureFloating = that.ruptureFloating; b.ruptureVariability = that.ruptureVariability; b.pointSourceType = that.pointSourceType; b.areaGridScaling = that.areaGridScaling; return b; } static Builder fromFile(Path path) throws IOException { // TODO test with zip files checkNotNull(path); Path configPath = Files.isDirectory(path) ? path.resolve(FILE_NAME) : path; Reader reader = Files.newBufferedReader(configPath, UTF_8); JsonElement modelRoot = new JsonParser() .parse(reader) .getAsJsonObject() .get(ELEMENT_NAME); Builder configBuilder = GSON.fromJson( checkNotNull( modelRoot, "'%s' element is missing from root of config: %s", ELEMENT_NAME, configPath), Builder.class); configBuilder.resource = configPath; reader.close(); return configBuilder; } Builder extend(Builder that) { checkNotNull(that); // name can't be overridden if (that.resource != null) { this.resource = that.resource; } if (that.surfaceSpacing != null) { this.surfaceSpacing = that.surfaceSpacing; } if (that.ruptureFloating != null) { this.ruptureFloating = that.ruptureFloating; } if (that.ruptureVariability != null) { this.ruptureVariability = that.ruptureVariability; } if (that.pointSourceType != null) { this.pointSourceType = that.pointSourceType; } if (that.areaGridScaling != null) { this.areaGridScaling = that.areaGridScaling; } return this; } private void validateState() { checkState(!built, "This %s instance has already been used", ID + ".Builder"); checkNotNull(name, STATE_ERROR, ID, Key.NAME); checkNotNull(resource, STATE_ERROR, ID, Key.RESOURCE); checkNotNull(surfaceSpacing, STATE_ERROR, ID, Key.SURFACE_SPACING); checkNotNull(ruptureFloating, STATE_ERROR, ID, Key.RUPTURE_FLOATING); checkNotNull(ruptureVariability, STATE_ERROR, ID, Key.RUPTURE_VARIABILITY); checkNotNull(pointSourceType, STATE_ERROR, ID, Key.POINT_SOURCE_TYPE); checkNotNull(areaGridScaling, STATE_ERROR, ID, Key.AREA_GRID_SCALING); built = true; } ModelConfig build() { validateState(); return new ModelConfig( name, resource, surfaceSpacing, ruptureFloating, ruptureVariability, pointSourceType, areaGridScaling); } } }