diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java index 347373fd..a6053e72 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/application/Processor.java @@ -69,7 +69,7 @@ public void run( Path controlFilePath = inputFileResolver.toPath(controlFileName).getParent(); FileSystemFileResolver relativeResolver = new FileSystemFileResolver(controlFilePath); - parser.parse(is, relativeResolver, controlMap); + controlMap = parser.parse(is, relativeResolver, controlMap); } } diff --git a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java index 31398a1a..4a39b724 100644 --- a/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java +++ b/lib/vdyp-common/src/main/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParser.java @@ -51,6 +51,8 @@ public class VdypSpeciesParser implements ControlMapValueReplacer, EndOfRecord> LAYER_TYPE_EOR_BUILDER = new ValueOrMarker.Builder<>(); + @Override public ControlKey getControlKey() { return ControlKey.FORWARD_INPUT_VDYP_LAYER_BY_SPECIES; @@ -107,11 +109,8 @@ protected ValueOrMarker, EndOfRecord> convert(Map, EndOfRecord>) entry.get(LAYER_TYPE); - if (layerType == null) { - var builder = new ValueOrMarker.Builder, EndOfRecord>(); - layerType = builder.marker(EndOfRecord.END_OF_RECORD); - } + var layerType = (ValueOrMarker, EndOfRecord>) entry + .getOrDefault(LAYER_TYPE, LAYER_TYPE_EOR_BUILDER.marker(EndOfRecord.END_OF_RECORD)); var genusIndex = (Integer) entry.get(GENUS_INDEX); var optionalGenus = (Optional) entry.get(GENUS); var genusNameText0 = (Optional) entry.get(SPECIES_0); @@ -152,20 +151,10 @@ protected ValueOrMarker, EndOfRecord> convert(Map 0.0 && yearsToBreastHeight > 0.0) - iTotalAge = yearsAtBreastHeight + yearsToBreastHeight; - } else if (Float.isNaN(yearsToBreastHeight)) { - if (yearsAtBreastHeight > 0.0 && totalAge > yearsAtBreastHeight) - iYearsToBreastHeight = totalAge - yearsAtBreastHeight; - } + var inferredAges = inferAges(new Ages(totalAge, yearsAtBreastHeight, yearsToBreastHeight)); - var inferredTotalAge = iTotalAge; - var inferredYearsToBreastHeight = iYearsToBreastHeight; + var inferredTotalAge = inferredAges.totalAge; + var inferredYearsToBreastHeight = inferredAges.yearsAtBreastHeight; return VdypSpecies.build(speciesBuilder -> { speciesBuilder.sp64DistributionSet(speciesDistributionSet); @@ -224,6 +213,31 @@ protected boolean stop(ValueOrMarker, EndOfRecord> nextChi }; } + record Ages(float totalAge, float yearsAtBreastHeight, float yearsToBreastHeight) { + } + + /** + * Fills in NaN value for one of totalAge or yearsToBreastHeight if the other values are valid numbers + * + * @param givenAges + * @return An ages object with the NaN value filled in. + */ + static Ages inferAges(final Ages givenAges) { + var iTotalAge = givenAges.totalAge; + var iYearsToBreastHeight = givenAges.yearsToBreastHeight; + + // From VDYPGETS.FOR, lines 235 to 255. + if (Float.isNaN(givenAges.totalAge)) { + if (givenAges.yearsAtBreastHeight > 0.0 && givenAges.yearsToBreastHeight > 0.0) + iTotalAge = givenAges.yearsAtBreastHeight + givenAges.yearsToBreastHeight; + } else if (Float.isNaN(givenAges.yearsToBreastHeight)) { + if (givenAges.yearsAtBreastHeight > 0.0 && givenAges.totalAge > givenAges.yearsAtBreastHeight) + iYearsToBreastHeight = givenAges.totalAge - givenAges.yearsAtBreastHeight; + } + + return new Ages(iTotalAge, givenAges.yearsAtBreastHeight, iYearsToBreastHeight); + } + @Override public ValueParser getValueParser() { return FILENAME; diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java new file mode 100644 index 00000000..a77521a3 --- /dev/null +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/application/ProcessorTest.java @@ -0,0 +1,59 @@ +package ca.bc.gov.nrs.vdyp.application; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import org.easymock.EasyMock; +import org.junit.jupiter.api.Test; + +import ca.bc.gov.nrs.vdyp.io.FileResolver; +import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.control.BaseControlParser; +import ca.bc.gov.nrs.vdyp.test.MockFileResolver; +import ca.bc.gov.nrs.vdyp.test.TestUtils; + +class ProcessorTest { + + @Test + void test() throws IOException, ResourceParseException, ProcessingException { + + Processor unit = EasyMock.partialMockBuilder(Processor.class).addMockedMethod("getControlFileParser") + .addMockedMethod("process", Set.class, Map.class, Optional.class, Predicate.class).createStrictMock(); + + BaseControlParser controlParser = EasyMock.createMock(BaseControlParser.class); + + var inputResolver = new MockFileResolver("input"); + var outputResolver = new MockFileResolver("output"); + + var is = TestUtils.makeInputStream("TEST"); + + inputResolver.addStream("test.ctr", is); + + var mockMap = new HashMap(); + + EasyMock.expect( + controlParser + .parse(EasyMock.same(is), EasyMock.anyObject(FileResolver.class), EasyMock.anyObject(Map.class)) + ).andStubReturn(mockMap); + + EasyMock.expect(unit.getControlFileParser()).andStubReturn(controlParser); + + unit.process( + EasyMock.eq(EnumSet.allOf(Pass.class)), EasyMock.same(mockMap), EasyMock.anyObject(Optional.class), + EasyMock.anyObject(Predicate.class) + ); + EasyMock.expectLastCall(); + + EasyMock.replay(unit, controlParser); + + unit.run(inputResolver, outputResolver, List.of("test.ctr"), EnumSet.allOf(Pass.class)); + + EasyMock.verify(unit, controlParser); + } +} diff --git a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java index 92ef93b0..d2a039e3 100644 --- a/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java +++ b/lib/vdyp-common/src/test/java/ca/bc/gov/nrs/vdyp/io/parse/model/VdypSpeciesParserTest.java @@ -8,9 +8,13 @@ import java.util.ArrayList; import java.util.NoSuchElementException; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import ca.bc.gov.nrs.vdyp.io.parse.common.ResourceParseException; +import ca.bc.gov.nrs.vdyp.io.parse.model.VdypSpeciesParser.Ages; import ca.bc.gov.nrs.vdyp.model.LayerType; import ca.bc.gov.nrs.vdyp.model.UtilizationClass; import ca.bc.gov.nrs.vdyp.test.MockFileResolver; @@ -179,4 +183,71 @@ void testMultiplePoly() throws IOException, ResourceParseException { } } + @Nested + class InferAges { + + @Test + void testNoNan() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, 10)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Leave as is + } + + @Test + void testTotalNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 50, 10)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Fill in total + } + + @Test + void testYtbNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, Float.NaN)); + assertThat(result, equalTo(new Ages(60, 50, 10))); // Fill Years to Breast Height + } + + // TODO maybe implement this the same as the other two + @Test + void testYabNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(60, Float.NaN, 10)); + assertThat(result, equalTo(new Ages(60, Float.NaN, 10))); // Leave as is + } + + // TODO maybe we should log a warning for these cases? + + @Test + void testDontAddUp() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 50, 5)); + assertThat(result, equalTo(new Ages(60, 50, 5))); // Leave as is + } + + @Test + void testTotalAbndYabBothNaN() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, Float.NaN, 10)); + assertThat(result, equalTo(new Ages(Float.NaN, Float.NaN, 10))); // Leave as is + } + + @Test + void testTotalNaNYtbZero() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 50, 0)); + assertThat(result, equalTo(new Ages(Float.NaN, 50, 0))); // Leave as is + } + + @Test + void testTotalNaNYabZero() { + var result = VdypSpeciesParser.inferAges(new Ages(Float.NaN, 0, 10)); + assertThat(result, equalTo(new Ages(Float.NaN, 0, 10))); // Leave as is + } + + @Test + void testYtbNaNTotalZero() { + var result = VdypSpeciesParser.inferAges(new Ages(0, 50, Float.NaN)); + assertThat(result, equalTo(new Ages(0, 50, Float.NaN))); // Leave as is + } + + @Test + void testYtbNaNYabZero() { + var result = VdypSpeciesParser.inferAges(new Ages(60, 0, Float.NaN)); + assertThat(result, equalTo(new Ages(60, 0, Float.NaN))); // Leave as is + } + + } }