Skip to content

Commit

Permalink
Parsing layer-species data file
Browse files Browse the repository at this point in the history
  • Loading branch information
smithkm committed Jul 7, 2023
1 parent 98cb956 commit 6abc2d2
Show file tree
Hide file tree
Showing 34 changed files with 905 additions and 201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public T next() throws IOException, ResourceParseException {
*
* @return
*/
protected abstract T convert(Map<String, Object> entry);
protected abstract T convert(Map<String, Object> entry) throws ResourceParseException;

@Override
public boolean hasNext() throws IOException, ResourceParseException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public BaseCoefficientParser<T, M> groupIndexKey(int maxGroups) {

public BaseCoefficientParser<T, M> speciesKey(String name) {
return key(
2, name, ControlledValueParser.SPECIES, SP0DefinitionParser::getSpeciesAliases,
2, name, ControlledValueParser.GENUS, GenusDefinitionParser::getSpeciesAliases,
"%s is not a valid species"
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package ca.bc.gov.nrs.vdyp.io.parse;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;

@FunctionalInterface
public interface ControlledValueParser<T> {
Expand Down Expand Up @@ -30,6 +32,7 @@ public interface ControlledValueParser<T> {
public static <U> ControlledValueParser<U> validate(
ControlledValueParser<U> delegate, BiFunction<U, Map<String, Object>, Optional<String>> validator
) {
Objects.requireNonNull(delegate, "delegate must not be null");
return (s, c) -> {
var value = delegate.parse(s, c);
var error = validator.apply(value, c);
Expand All @@ -47,23 +50,64 @@ public static <U> ControlledValueParser<U> validate(
* @param delegate Parser to use if the string is not blank
*/
public static <U> ControlledValueParser<Optional<U>> optional(ControlledValueParser<U> delegate) {
Objects.requireNonNull(delegate, "delegate must not be null");
return pretestOptional(delegate, s -> !s.isBlank());
}

/**
* Makes a parser that parses if the string passes the test, and returns an
* empty Optional otherwise.
*
* @param delegate Parser to use if the string is not blank
* @param test Test to apply to the string
*/
public static <U> ControlledValueParser<Optional<U>>
pretestOptional(ControlledValueParser<U> delegate, Predicate<String> test) {
Objects.requireNonNull(delegate, "delegate must not be null");
return (s, c) -> {
if (!s.isBlank()) {
if (test.test(s)) {
return Optional.of(delegate.parse(s, c));
}
return Optional.empty();
};
}

/**
* Parser that strips whitespace and validates that the string is a Species ID
* Makes a parser that parses the string, then tests it, and returns empty if it
* fails.
*
* @param delegate Parser to use
* @param test Test to apply to the parsed result
*/
public static <U> ControlledValueParser<Optional<U>>
posttestOptional(ControlledValueParser<U> delegate, Predicate<U> test) {
Objects.requireNonNull(delegate, "delegate must not be null");
return (s, c) -> {
var result = delegate.parse(s, c);
if (test.test(result)) {
return Optional.of(result);
}
return Optional.empty();
};
}

/**
* Parser that strips whitespace and validates that the string is a Genus (SP0)
* ID
*/
static final ControlledValueParser<String> SPECIES = (string, control) -> {
static final ControlledValueParser<String> GENUS = (string, control) -> {
var result = string.strip();
SP0DefinitionParser.checkSpecies(control, result);
GenusDefinitionParser.checkSpecies(control, result);
return result;
};

/**
* Parser that strips whitespace of a Species (SP64) id
*/
// Currently just parses as a string but marking it explicitly makes it clearer
// and could allow for validation later.
public static final ControlledValueParser<String> SPECIES = (s, c) -> s.strip();

/**
* Parser that strips whitespace and validates that the string is a BEC ID
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public EquationGroupParser(int identifierLength) {
public Map<String, Map<String, Integer>> parse(InputStream is, Map<String, Object> control)
throws IOException, ResourceParseException {

final var sp0List = SP0DefinitionParser.getSpecies(control);
final var sp0List = GenusDefinitionParser.getSpecies(control);

var becKeys = BecDefinitionParser.getBecAliases(control);

Expand Down Expand Up @@ -70,7 +70,7 @@ public Map<String, Map<String, Integer>> parse(InputStream is, Map<String, Objec

List<String> errors = new ArrayList<>();

var sp0Keys = SP0DefinitionParser.getSpeciesAliases(control);
var sp0Keys = GenusDefinitionParser.getSpeciesAliases(control);

var restrictedBecKeys = becKeys.stream().filter(k -> !hiddenBecs.contains(k)).toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import java.util.Optional;
import java.util.stream.Collectors;

import ca.bc.gov.nrs.vdyp.model.SP0Definition;
import ca.bc.gov.nrs.vdyp.model.GenusDefinition;

/**
* Parser for a SP0 Definition data file
* Parser for a Genus (SP0) Definition data file
*
* @author Kevin Smith, Vivid Solutions
*
*/
public class SP0DefinitionParser implements ControlMapSubResourceParser<List<SP0Definition>> {
public class GenusDefinitionParser implements ControlMapSubResourceParser<List<GenusDefinition>> {

public static final String CONTROL_KEY = "SP0_DEF";

Expand All @@ -30,28 +30,28 @@ public class SP0DefinitionParser implements ControlMapSubResourceParser<List<SP0
.flatMap(v -> v == 0 ? Optional.empty() : Optional.of(v))
);

public SP0DefinitionParser() {
public GenusDefinitionParser() {
super();
this.num_sp0 = 16;
}

public SP0DefinitionParser(int num_sp0) {
public GenusDefinitionParser(int num_sp0) {
super();
this.num_sp0 = num_sp0;
}

@SuppressWarnings("unchecked")
@Override
public List<SP0Definition> parse(InputStream is, Map<String, Object> control)
public List<GenusDefinition> parse(InputStream is, Map<String, Object> control)
throws IOException, ResourceParseException {
SP0Definition[] result = new SP0Definition[num_sp0];
GenusDefinition[] result = new GenusDefinition[num_sp0];
result = lineParser.parse(is, result, (v, r) -> {
String alias = (String) v.get("alias");
Optional<Integer> preference = (Optional<Integer>) v.get("preference");
String name = (String) v.get("name");
Integer lineNumber = (Integer) v.get(LineParser.LINE_NUMBER_KEY);

var defn = new SP0Definition(alias, preference, name);
var defn = new GenusDefinition(alias, preference, name);
int p = preference.orElse(lineNumber);

if (p > num_sp0) {
Expand Down Expand Up @@ -80,7 +80,7 @@ public List<SP0Definition> parse(InputStream is, Map<String, Object> control)

public static void checkSpecies(final List<String> speciesIndicies, final String sp0) throws ValueParseException {
if (!speciesIndicies.contains(sp0)) {
throw new ValueParseException(sp0, sp0 + " is not a valid species");
throw new ValueParseException(sp0, sp0 + " is not a valid genus (SP0)");
}
}

Expand All @@ -89,13 +89,13 @@ public static void checkSpecies(final Map<String, Object> controlMap, final Stri
checkSpecies(speciesIndicies, sp0);
}

public static List<SP0Definition> getSpecies(final Map<String, Object> controlMap) {
public static List<GenusDefinition> getSpecies(final Map<String, Object> controlMap) {
return ResourceParser
.<List<SP0Definition>>expectParsedControl(controlMap, SP0DefinitionParser.CONTROL_KEY, List.class);
.<List<GenusDefinition>>expectParsedControl(controlMap, GenusDefinitionParser.CONTROL_KEY, List.class);
}

public static List<String> getSpeciesAliases(final Map<String, Object> controlMap) {
return getSpecies(controlMap).stream().map(SP0Definition::getAlias).collect(Collectors.toList());
return getSpecies(controlMap).stream().map(GenusDefinition::getAlias).collect(Collectors.toList());
}

/**
Expand All @@ -105,7 +105,7 @@ public static List<String> getSpeciesAliases(final Map<String, Object> controlMa
* @param controlMap
* @return
*/
public static SP0Definition getSpeciesByIndex(final int index, final Map<String, Object> controlMap) {
public static GenusDefinition getSpeciesByIndex(final int index, final Map<String, Object> controlMap) {

return getSpecies(controlMap).get(index - 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public boolean isStopLine(String line) {
public MatrixMap2<String, Region, Coefficients> parse(InputStream is, Map<String, Object> control)
throws IOException, ResourceParseException {
final var regionIndicies = Arrays.asList(Region.values());
final var speciesIndicies = SP0DefinitionParser.getSpeciesAliases(control);
final var speciesIndicies = GenusDefinitionParser.getSpeciesAliases(control);

MatrixMap2<String, Region, Coefficients> result = new MatrixMap2Impl<String, Region, Coefficients>(
speciesIndicies, regionIndicies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public boolean isStopLine(String line) {
public MatrixMap3<String, String, Region, NonprimaryHLCoefficients>
parse(InputStream is, Map<String, Object> control) throws IOException, ResourceParseException {
final var regionIndicies = Arrays.asList(Region.values());
final var speciesIndicies = SP0DefinitionParser.getSpeciesAliases(control);
final var speciesIndicies = GenusDefinitionParser.getSpeciesAliases(control);

MatrixMap3<String, String, Region, NonprimaryHLCoefficients> result = new MatrixMap3Impl<String, String, Region, NonprimaryHLCoefficients>(
speciesIndicies, speciesIndicies, regionIndicies
Expand All @@ -62,8 +62,8 @@ public boolean isStopLine(String line) {
var region = (Region) v.get(REGION_KEY);
@SuppressWarnings("unchecked")
var coefficients = (List<Float>) v.get(COEFFICIENT_KEY);
SP0DefinitionParser.checkSpecies(speciesIndicies, sp0_1);
SP0DefinitionParser.checkSpecies(speciesIndicies, sp0_2);
GenusDefinitionParser.checkSpecies(speciesIndicies, sp0_1);
GenusDefinitionParser.checkSpecies(speciesIndicies, sp0_2);

if (coefficients.size() < NUM_COEFFICIENTS) {
throw new ValueParseException(null, "Expected 2 coefficients"); // TODO handle this better
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Map<String, SiteCurve> parse(InputStream is, Map<String, Object> control)

return r;
}, control);
final var sp0List = SP0DefinitionParser.getSpeciesAliases(control);
final var sp0List = GenusDefinitionParser.getSpeciesAliases(control);

var missing = ExpectationDifference.difference(result.keySet(), sp0List).getMissing();
if (!missing.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public MatrixMap3<Region, String, Integer, Float> parse(InputStream is, Map<Stri
throws IOException, ResourceParseException {
var regionIndicies = Arrays.asList(Region.values());
List<Integer> coeIndicies = Stream.iterate(1, x -> x + 1).limit(NUM_COEFFICIENTS).collect(Collectors.toList());
final var speciesIndicies = SP0DefinitionParser.getSpeciesAliases(control);
final var speciesIndicies = GenusDefinitionParser.getSpeciesAliases(control);

MatrixMap3<Region, String, Integer, Float> result = new MatrixMap3Impl<Region, String, Integer, Float>(
regionIndicies, speciesIndicies, coeIndicies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public boolean isStopLine(String line) {
public MatrixMap3<Integer, String, String, Coefficients> parse(InputStream is, Map<String, Object> control)
throws IOException, ResourceParseException {
final var becIndices = BecDefinitionParser.getBecAliases(control);
final var speciesIndicies = SP0DefinitionParser.getSpeciesAliases(control);
final var speciesIndicies = GenusDefinitionParser.getSpeciesAliases(control);
final var ucIndices = Arrays.asList(1, 2, 3, 4);

MatrixMap3<Integer, String, String, Coefficients> result = new MatrixMap3Impl<Integer, String, String, Coefficients>(
Expand All @@ -61,7 +61,7 @@ public MatrixMap3<Integer, String, String, Coefficients> parse(InputStream is, M

@SuppressWarnings("unchecked")
var coefficients = (List<Float>) v.get(COEFFICIENT_KEY);
SP0DefinitionParser.checkSpecies(speciesIndicies, sp0);
GenusDefinitionParser.checkSpecies(speciesIndicies, sp0);

if (coefficients.size() < numCoefficients) {
throw new ValueParseException(null, "Expected " + numCoefficients + " coefficients");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,44 @@ public static ValueParser<Integer> indexParser(String sequenceName, int indexFro
*/
public static final ValueParser<Float> FLOAT = numberParser(Float::parseFloat, Float.class);

/**
* Parser for percentages
*/
public static final ValueParser<Float> PERCENTAGE = ValueParser
.range(FLOAT, 0.0f, true, 100.0f, true, "Percentage");

/**
* Validate that a parsed value is within a range
*
* @param parser underlying parser
* @param min the lower bound
* @param includeMin is the lower bound inclusive
* @param max the upper bound
* @param includeMax is the upper bound inclusive
* @param name Name for the value to use in the parse error if it is out
* of the range.
*/
public static <T extends Comparable<T>> ValueParser<T>
range(ValueParser<T> parser, T min, boolean includeMin, T max, boolean includeMax, String name) {
return validate(parser, x -> {
if (x.compareTo(min) < (includeMin ? 0 : 1)) {
return Optional.of(
String.format(
"%s must be %s %s.", name, includeMin ? "greater than or equal to" : "greater than", min
)
);
}
if (x.compareTo(max) > (includeMax ? 0 : -1)) {
return Optional.of(
String.format(
"%s must be %s %s.", name, includeMax ? "less than or equal to" : "less than", max
)
);
}
return Optional.empty();
});
}

/**
* Parser for integers as booleans
*/
Expand Down Expand Up @@ -186,6 +224,28 @@ public static <U> ValueParser<Optional<U>> optional(ValueParser<U> delegate) {
return uncontrolled(ControlledValueParser.optional(delegate));
}

/**
* Makes a parser that parses if the string passes the test, and returns an
* empty Optional otherwise.
*
* @param delegate Parser to use if the string is not blank
* @param test Test to apply to the string
*/
public static <U> ValueParser<Optional<U>> pretestOptional(ValueParser<U> delegate, Predicate<String> test) {
return uncontrolled(ControlledValueParser.pretestOptional(delegate, test));
}

/**
* Makes a parser that parses the string, then tests it, and returns empty if it
* fails.
*
* @param delegate Parser to use
* @param test Test to apply to the parsed result
*/
public static <U> ValueParser<Optional<U>> posttestOptional(ValueParser<U> delegate, Predicate<U> test) {
return uncontrolled(ControlledValueParser.posttestOptional(delegate, test));
}

/**
* Parse a string as a set of fixed length chucks.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public boolean isStopLine(String line) {
@Override
public MatrixMap2<String, Region, Coefficients> parse(InputStream is, Map<String, Object> control)
throws IOException, ResourceParseException {
final var speciesIndicies = SP0DefinitionParser.getSpeciesAliases(control);
final var speciesIndicies = GenusDefinitionParser.getSpeciesAliases(control);
final var regionIndicies = Arrays.asList(Region.values());

MatrixMap2<String, Region, Coefficients> result = new MatrixMap2Impl<String, Region, Coefficients>(
Expand All @@ -54,7 +54,7 @@ public MatrixMap2<String, Region, Coefficients> parse(InputStream is, Map<String

@SuppressWarnings("unchecked")
var coefficients = (List<Float>) v.get(COEFFICIENT_KEY);
SP0DefinitionParser.checkSpecies(speciesIndicies, sp0);
GenusDefinitionParser.checkSpecies(speciesIndicies, sp0);

if (coefficients.size() < numCoefficients) {
throw new ValueParseException(null, "Expected " + numCoefficients + " coefficients");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import java.util.Optional;

public class SP0Definition extends AbstractSpeciesDefinition {
public class GenusDefinition extends AbstractSpeciesDefinition {

final Optional<Integer> preference;

public SP0Definition(String alias, Optional<Integer> preference, String name) {
public GenusDefinition(String alias, Optional<Integer> preference, String name) {
super(alias, name);
this.preference = preference;
}
Expand Down
Loading

0 comments on commit 6abc2d2

Please sign in to comment.