diff --git a/metarParser-commons/src/main/resources/internationalization/messages.properties b/metarParser-commons/src/main/resources/internationalization/messages.properties index a33b2a2b..76ddda31 100644 --- a/metarParser-commons/src/main/resources/internationalization/messages.properties +++ b/metarParser-commons/src/main/resources/internationalization/messages.properties @@ -289,3 +289,7 @@ TurbulenceIntensity.7=Severe turbulence in clear air, frequent TurbulenceIntensity.8=Severe turbulence in cloud, occasional TurbulenceIntensity.9=Severe turbulence in cloud, frequent TurbulenceIntensity.X=Extreme turbulence + +ReportType.METAR=Routine report +ReportType.SPECI=Special report +CloudQuantity.NCD=no cloud detected diff --git a/metarParser-commons/src/main/resources/internationalization/messages_fr.properties b/metarParser-commons/src/main/resources/internationalization/messages_fr.properties index bc43c7ba..4f8ede20 100644 --- a/metarParser-commons/src/main/resources/internationalization/messages_fr.properties +++ b/metarParser-commons/src/main/resources/internationalization/messages_fr.properties @@ -289,3 +289,11 @@ TurbulenceIntensity.7=Turbulences sévères fréquentes dans l'air TurbulenceIntensity.8=Turbulences sévères occasionnelles dans les nuages TurbulenceIntensity.9=Turbulences sévères fréquentes dans les nuages TurbulenceIntensity.X=Turbulence extrême + +ReportType.METAR=Rapport de routine +ReportType.SPECI=Rapport spécial + +ReportType.METAR=Metar +ReportType.SPECI=Special +CloudQuantity.NCD=Aucun nuage détecté + diff --git a/metarParser-entities/src/main/java/io/github/mivek/enums/CloudQuantity.java b/metarParser-entities/src/main/java/io/github/mivek/enums/CloudQuantity.java index 71e30c93..0125f453 100644 --- a/metarParser-entities/src/main/java/io/github/mivek/enums/CloudQuantity.java +++ b/metarParser-entities/src/main/java/io/github/mivek/enums/CloudQuantity.java @@ -23,7 +23,9 @@ public enum CloudQuantity { /** Overcast. */ OVC, /** No significant cloud. */ - NSC; + NSC, + /** No cloud detected. */ + NCD; @Override diff --git a/metarParser-entities/src/main/java/io/github/mivek/enums/ReportType.java b/metarParser-entities/src/main/java/io/github/mivek/enums/ReportType.java new file mode 100644 index 00000000..c44bed98 --- /dev/null +++ b/metarParser-entities/src/main/java/io/github/mivek/enums/ReportType.java @@ -0,0 +1,20 @@ +package io.github.mivek.enums; + +import io.github.mivek.internationalization.Messages; + +/** + * Enumeration class for weather code report types. + * + * @author mivek + */ +public enum ReportType { + /** Routine report. */ + METAR, + /** Special report. */ + SPECI; + + @Override + public String toString() { + return Messages.getInstance().getString("ReportType." + name()); + } +} diff --git a/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherCode.java b/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherCode.java index 7f949157..aa1b3ce5 100644 --- a/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherCode.java +++ b/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherCode.java @@ -1,6 +1,7 @@ package io.github.mivek.model; import io.github.mivek.enums.Flag; +import io.github.mivek.enums.ReportType; import io.github.mivek.internationalization.Messages; import java.util.EnumSet; import java.util.Set; @@ -24,6 +25,8 @@ public abstract class AbstractWeatherCode extends AbstractWeatherContainer { private String message; /** The identifier of the station. */ private String station; + /** Report type (METAR or SPECI). */ + private ReportType reportType; /** Holds the flag of the code. */ private final EnumSet flags; @@ -105,6 +108,20 @@ public void setStation(final String station) { this.station = station; } + /** + * @return the report type (METAR or SPECI). + */ + public ReportType getReportType() { + return reportType; + } + + /** + * @param reportType the report type to set. + */ + public void setReportType(final ReportType reportType) { + this.reportType = reportType; + } + /** * @return The flags of the weatherCode. */ diff --git a/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherContainer.java b/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherContainer.java index e9e3e3d4..02803ff3 100644 --- a/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherContainer.java +++ b/metarParser-entities/src/main/java/io/github/mivek/model/AbstractWeatherContainer.java @@ -26,6 +26,8 @@ public abstract class AbstractWeatherContainer { private boolean cavok; /** Contains the remarks. */ private String remark; + /** Indicates whether NSW (No Significant Weather) is present. */ + private boolean nsw; /** * Constructor to initialize the lists. @@ -162,6 +164,20 @@ public void setRemark(final String remark) { this.remark = remark; } + /** + * @return the nsw (No Significant Weather) + */ + public boolean isNsw() { + return nsw; + } + + /** + * @param nsw the nsw to set + */ + public void setNsw(final boolean nsw) { + this.nsw = nsw; + } + /** * @return string describing the object. */ diff --git a/metarParser-entities/src/main/java/io/github/mivek/model/Metar.java b/metarParser-entities/src/main/java/io/github/mivek/model/Metar.java index e9143266..6c72cdc3 100644 --- a/metarParser-entities/src/main/java/io/github/mivek/model/Metar.java +++ b/metarParser-entities/src/main/java/io/github/mivek/model/Metar.java @@ -15,11 +15,11 @@ */ public class Metar extends AbstractWeatherCode { /** Temperature. */ - private int temperature; + private Integer temperature; /** Dew point. */ - private int dewPoint; + private Integer dewPoint; /** Altimeter in HPa. */ - private int altimeter; + private Integer altimeter; /** Nosig value. */ private boolean nosig; /** List of runways information. */ @@ -39,42 +39,42 @@ public Metar() { /** * @return the temperature */ - public int getTemperature() { + public Integer getTemperature() { return temperature; } /** * @param temperature the temperature to set */ - public void setTemperature(final int temperature) { + public void setTemperature(final Integer temperature) { this.temperature = temperature; } /** * @return the dewPoint */ - public int getDewPoint() { + public Integer getDewPoint() { return dewPoint; } /** * @param dewPoint the dewPoint to set */ - public void setDewPoint(final int dewPoint) { + public void setDewPoint(final Integer dewPoint) { this.dewPoint = dewPoint; } /** * @return the altimeter in HPa. */ - public int getAltimeter() { + public Integer getAltimeter() { return altimeter; } /** * @param altimeter the altimeter to set */ - public void setAltimeter(final int altimeter) { + public void setAltimeter(final Integer altimeter) { this.altimeter = altimeter; } diff --git a/metarParser-parsers/pom.xml b/metarParser-parsers/pom.xml index 507633e8..0d1dd0fd 100644 --- a/metarParser-parsers/pom.xml +++ b/metarParser-parsers/pom.xml @@ -16,8 +16,8 @@ 0.99 - 0.98 - 0.99 + 0.96 + 0.98 diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/common/BaseWindCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/common/BaseWindCommand.java index 7b333587..5b17171a 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/common/BaseWindCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/common/BaseWindCommand.java @@ -26,10 +26,27 @@ default void setWindElements(final Wind wind, final String directionStr, final S if (!direction.equals(Messages.getInstance().getString("Converter.VRB"))) { wind.setDirectionDegrees(Integer.parseInt(directionStr)); } - wind.setSpeed(Integer.parseInt(speed)); - if (gust != null) { - wind.setGust(Integer.parseInt(gust)); + if (!speed.contains("/")) { + int windSpeed = handleWindSpeed(speed); + wind.setSpeed(windSpeed); + } + if (gust != null && !gust.isEmpty() && !gust.contains("/")) { + int gustSpeed = handleWindSpeed(gust); + wind.setGust(gustSpeed); } wind.setUnit(Objects.requireNonNullElse(unit, "KT")); } + + /** + * Handles wind speed parsing, including P99 format. + * + * @param speedStr the speed string + * @return the parsed speed + */ + private int handleWindSpeed(final String speedStr) { + if (speedStr.startsWith("P")) { + return Integer.parseInt(speedStr.substring(1)) + 1; + } + return Integer.parseInt(speedStr); + } } diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/common/MainVisibilityCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/common/MainVisibilityCommand.java index 6f31e1da..cfe7b98a 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/common/MainVisibilityCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/common/MainVisibilityCommand.java @@ -12,7 +12,7 @@ */ public final class MainVisibilityCommand implements Command { /** Pattern for the main visibility. */ - private static final Pattern MAIN_VISIBILITY_REGEX = Pattern.compile("^(\\d{4})(|NDV)$"); + private static final Pattern MAIN_VISIBILITY_REGEX = Pattern.compile("^(\\d{4}|////)(|NDV)$"); /** * constructor. @@ -26,7 +26,9 @@ public boolean execute(final AbstractWeatherContainer container, final String pa if (container.getVisibility() == null) { container.setVisibility(new Visibility()); } - container.getVisibility().setMainVisibility(Converter.convertVisibility(matches[1])); + if (!matches[1].equals("////")) { + container.getVisibility().setMainVisibility(Converter.convertVisibility(matches[1])); + } return getReturnValue(); } diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/common/WindCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/common/WindCommand.java index f11a14de..accb95d6 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/common/WindCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/common/WindCommand.java @@ -11,7 +11,7 @@ */ public final class WindCommand implements BaseWindCommand { /** Pattern regex for wind. */ - private static final Pattern WIND_REGEX = Pattern.compile("^(VRB|000|[0-3]\\d{2})(\\d{2})G?(\\d{2,3})?(KT|MPS|KM/H)?"); + private static final Pattern WIND_REGEX = Pattern.compile("^(VRB|000|[0-3]\\d{2})(P?\\d{2,3}|////?)G?(P?\\d{2,3}|////?)?(KT|MPS|KM/H)?"); /** * Package private constructor. diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterCommand.java index d7c84982..f9246e05 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterCommand.java @@ -11,7 +11,7 @@ public final class AltimeterCommand implements Command { /** Pattern of the altimeter (Pascals). */ - private static final Pattern ALTIMETER_REGEX = Pattern.compile("^Q(\\d{4})$"); + private static final Pattern ALTIMETER_REGEX = Pattern.compile("^Q(\\d{4}|////)$"); /** * Package private constructor. @@ -22,7 +22,9 @@ public final class AltimeterCommand implements Command { @Override public void execute(final Metar metar, final String part) { String[] matches = Regex.pregMatch(ALTIMETER_REGEX, part); - metar.setAltimeter(Integer.parseInt(matches[1])); + if (!matches[1].equals("////")) { + metar.setAltimeter(Integer.parseInt(matches[1])); + } } @Override diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterMecuryCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterMecuryCommand.java index 59f03c25..5d49ad01 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterMecuryCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/AltimeterMecuryCommand.java @@ -12,7 +12,7 @@ public final class AltimeterMecuryCommand implements Command { /** Pattern for the altimeter in inches of mercury. */ - private static final Pattern ALTIMETER_MERCURY_REGEX = Pattern.compile("^A(\\d{4})$"); + private static final Pattern ALTIMETER_MERCURY_REGEX = Pattern.compile("^A(\\d{4}|////)$"); /** * Package private constructor. @@ -23,8 +23,10 @@ public final class AltimeterMecuryCommand implements Command { @Override public void execute(final Metar metar, final String part) { String[] matches = Regex.pregMatch(ALTIMETER_MERCURY_REGEX, part); - double mercury = Double.parseDouble(matches[1]) / 100; - metar.setAltimeter((int) Converter.inchesMercuryToHPascal(mercury)); + if (!matches[1].equals("////")) { + double mercury = Double.parseDouble(matches[1]) / 100; + metar.setAltimeter((int) Converter.inchesMercuryToHPascal(mercury)); + } } @Override diff --git a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/TemperatureCommand.java b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/TemperatureCommand.java index 9177ea41..7b49f633 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/command/metar/TemperatureCommand.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/command/metar/TemperatureCommand.java @@ -11,7 +11,7 @@ */ public final class TemperatureCommand implements Command { /** Pattern of the temperature block. */ - private static final Pattern TEMPERATURE_REGEX = Pattern.compile("^(M?\\d{2})/(M?\\d{2})$"); + private static final Pattern TEMPERATURE_REGEX = Pattern.compile("^(M?\\d{2}|///)(/)( |)(M?\\d{2}|///)$"); /** * Package private constructor. @@ -22,8 +22,15 @@ public final class TemperatureCommand implements Command { @Override public void execute(final Metar metar, final String part) { String[] matches = Regex.pregMatch(TEMPERATURE_REGEX, part); - metar.setTemperature(Converter.convertTemperature(matches[1])); - metar.setDewPoint(Converter.convertTemperature(matches[2])); + String tempStr = matches[1]; + String dewStr = matches[4]; + + if (!tempStr.equals("///")) { + metar.setTemperature(Converter.convertTemperature(tempStr)); + } + if (!dewStr.equals("///")) { + metar.setDewPoint(Converter.convertTemperature(dewStr)); + } } @Override diff --git a/metarParser-parsers/src/main/java/io/github/mivek/parser/AbstractWeatherContainerParser.java b/metarParser-parsers/src/main/java/io/github/mivek/parser/AbstractWeatherContainerParser.java index 16ea273a..fc88f840 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/parser/AbstractWeatherContainerParser.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/parser/AbstractWeatherContainerParser.java @@ -113,6 +113,12 @@ boolean generalParse(final AbstractWeatherContainer container, final String part return true; } + if ("NSW".equals(part)) { + container.setNsw(true); + container.getWeatherConditions().clear(); + return true; + } + Command command = commonSupplier.get(part); if (command != null) { return command.execute(container, part); diff --git a/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java b/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java index 1816c77c..fa911f5f 100644 --- a/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java +++ b/metarParser-parsers/src/main/java/io/github/mivek/parser/MetarParser.java @@ -67,13 +67,26 @@ public static MetarParser getInstance() { public Metar parse(final String code) throws ParseException { Metar m = new Metar(); String[] metarTab = tokenize(code); - Airport airport = getAirportSupplier().get(metarTab[0]); - m.setStation(metarTab[0]); + int startIndex = 0; + + // Check for METAR/SPECI prefix + if (metarTab.length > 0) { + if ("METAR".equals(metarTab[0])) { + m.setReportType(io.github.mivek.enums.ReportType.METAR); + startIndex = 1; + } else if ("SPECI".equals(metarTab[0])) { + m.setReportType(io.github.mivek.enums.ReportType.SPECI); + startIndex = 1; + } + } + + Airport airport = getAirportSupplier().get(metarTab[startIndex]); + m.setStation(metarTab[startIndex]); m.setAirport(airport); m.setMessage(code); - parseDeliveryTime(m, metarTab[1]); + parseDeliveryTime(m, metarTab[startIndex + 1]); int metarTabLength = metarTab.length; - int i = 2; + int i = startIndex + 2; while (i < metarTabLength) { if (!generalParse(m, metarTab[i]) && !parseFlags(m, metarTab[i])) { if ("NOSIG".equals(metarTab[i])) { diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityCommandTest.java new file mode 100644 index 00000000..7b5421cf --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityCommandTest.java @@ -0,0 +1,42 @@ +package io.github.mivek.command.common; + +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MainVisibilityCommandTest { + + private final MainVisibilityCommand command = new MainVisibilityCommand(); + + @Test + void testCanParseNormal() { + assertTrue(command.canParse("9999")); + assertTrue(command.canParse("0350")); + assertTrue(command.canParse("5000")); + } + + @Test + void testCanParseSolidus() { + assertTrue(command.canParse("////")); + } + + @Test + void testExecuteNormal() { + Metar metar = new Metar(); + boolean result = command.execute(metar, "9999"); + assertTrue(result); + assertNotNull(metar.getVisibility()); + assertNotNull(metar.getVisibility().getMainVisibility()); + } + + @Test + void testExecuteSolidus() { + Metar metar = new Metar(); + boolean result = command.execute(metar, "////"); + assertTrue(result); + assertNotNull(metar.getVisibility()); + // When visibility is missing, mainVisibility should not be set + assertNull(metar.getVisibility().getMainVisibility()); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityNauticalMilesCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityNauticalMilesCommandTest.java index e66357b0..edbf901b 100644 --- a/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityNauticalMilesCommandTest.java +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/common/MainVisibilityNauticalMilesCommandTest.java @@ -27,4 +27,11 @@ void testExecute() { assertTrue(command.execute(m, "P6SM")); assertNotNull(m.getVisibility()); } + + @Test + void testCanParseSolidus() { + MainVisibilityNauticalMilesCommand command = new MainVisibilityNauticalMilesCommand(); + assertFalse(command.canParse("////")); + // Solidus visibility is for metric (MainVisibilityCommand), not nautical miles + } } diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/common/WindCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/common/WindCommandTest.java index bb07b832..372dd4c9 100644 --- a/metarParser-parsers/src/test/java/io/github/mivek/command/common/WindCommandTest.java +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/common/WindCommandTest.java @@ -94,4 +94,41 @@ void testParseWindThreeDigitGust() { assertEquals(15, w.getGust()); assertEquals("KT", w.getUnit()); } + + @Test + void testWindCommandWithP99Speed() { + String windPart = "280P99KT"; + Wind res = command.parseWind(windPart); + assertNotNull(res); + assertEquals(Integer.valueOf(280), res.getDirectionDegrees()); + assertEquals(100, res.getSpeed()); + assertEquals("KT", res.getUnit()); + } + + @Test + void testWindCommandWithP99Gust() { + String windPart = "28050GP99KT"; + Wind res = command.parseWind(windPart); + assertNotNull(res); + assertEquals(Integer.valueOf(280), res.getDirectionDegrees()); + assertEquals(50, res.getSpeed()); + assertEquals(100, res.getGust()); + assertEquals("KT", res.getUnit()); + } + + @Test + void testWindCommandWithSolidusSpeed() { + String code = "000////KT"; + assertTrue(command.canParse(code), "Should be able to parse solidus wind"); + } + + @Test + void testWindCommandVRBWind() { + String windPart = "VRB05KT"; + Wind res = command.parseWind(windPart); + assertNotNull(res); + assertEquals(Messages.getInstance().getString("Converter.VRB"), res.getDirection()); + assertEquals(5, res.getSpeed()); + assertNull(res.getDirectionDegrees()); + } } diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterCommandTest.java new file mode 100644 index 00000000..edba26ea --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterCommandTest.java @@ -0,0 +1,50 @@ +package io.github.mivek.command.metar; + +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AltimeterCommandTest { + + private final AltimeterCommand command = new AltimeterCommand(); + + @Test + void testCanParseNormal() { + assertTrue(command.canParse("Q1012")); + assertTrue(command.canParse("Q0950")); + } + + @Test + void testCanParseSolidus() { + assertTrue(command.canParse("Q////")); + } + + @Test + void testExecuteNormal() { + Metar metar = new Metar(); + command.execute(metar, "Q1012"); + assertEquals(1012, metar.getAltimeter().intValue()); + } + + @Test + void testExecuteSolidus() { + Metar metar = new Metar(); + command.execute(metar, "Q////"); + assertNull(metar.getAltimeter()); + } + + @Test + void testParseAltimeterEdgeCases() { + Metar metar = new Metar(); + command.execute(metar, "Q0500"); + assertEquals(500, metar.getAltimeter().intValue()); + } + + @Test + void testParseAltimeterMaximum() { + Metar metar = new Metar(); + command.execute(metar, "Q1050"); + assertEquals(1050, metar.getAltimeter().intValue()); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterMecuryCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterMecuryCommandTest.java new file mode 100644 index 00000000..d1cb90ee --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/AltimeterMecuryCommandTest.java @@ -0,0 +1,37 @@ +package io.github.mivek.command.metar; + +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AltimeterMecuryCommandTest { + + private final AltimeterMecuryCommand command = new AltimeterMecuryCommand(); + + @Test + void testCanParseNormal() { + assertTrue(command.canParse("A3012")); + assertTrue(command.canParse("A2990")); + } + + @Test + void testCanParseSolidus() { + assertTrue(command.canParse("A////")); + } + + @Test + void testExecuteNormal() { + Metar metar = new Metar(); + command.execute(metar, "A3012"); + // A3012 = 30.12 inches of mercury, which converts to approximately 1019 hPa + assertEquals(1019, metar.getAltimeter().intValue()); + } + + @Test + void testExecuteSolidus() { + Metar metar = new Metar(); + command.execute(metar, "A////"); + assertNull(metar.getAltimeter()); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/command/metar/TemperatureCommandTest.java b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/TemperatureCommandTest.java new file mode 100644 index 00000000..a49ea994 --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/command/metar/TemperatureCommandTest.java @@ -0,0 +1,92 @@ +package io.github.mivek.command.metar; + +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TemperatureCommandTest { + + private final TemperatureCommand command = new TemperatureCommand(); + + @Test + void testParseNormalTemperature() { + Metar metar = new Metar(); + command.execute(metar, "25/10"); + assertEquals(25, metar.getTemperature().intValue()); + assertEquals(10, metar.getDewPoint().intValue()); + } + + @Test + void testParseNegativeTemperature() { + Metar metar = new Metar(); + command.execute(metar, "M05/M10"); + assertEquals(-5, metar.getTemperature().intValue()); + assertEquals(-10, metar.getDewPoint().intValue()); + } + + @Test + void testParseMissingTemperature() { + Metar metar = new Metar(); + command.execute(metar, "////10"); + assertNull(metar.getTemperature()); + assertEquals(10, metar.getDewPoint().intValue()); + } + + @Test + void testParseMissingDewPoint() { + Metar metar = new Metar(); + command.execute(metar, "25////"); + assertEquals(25, metar.getTemperature().intValue()); + assertNull(metar.getDewPoint()); + } + + @Test + void testParseMissingBoth() { + Metar metar = new Metar(); + command.execute(metar, "///////"); + assertNull(metar.getTemperature()); + assertNull(metar.getDewPoint()); + } + + @Test + void testCanParseNormal() { + assertTrue(command.canParse("25/10")); + } + + @Test + void testCanParseSolidus() { + assertTrue(command.canParse("////10")); + assertTrue(command.canParse("25////")); + assertTrue(command.canParse("///////")); + } + + @Test + void testCanParseNegative() { + assertTrue(command.canParse("M05/M10")); + } + + @Test + void testParseLargeMissingTemperature() { + Metar metar = new Metar(); + command.execute(metar, "////M05"); + assertNull(metar.getTemperature()); + assertEquals(-5, metar.getDewPoint().intValue()); + } + + @Test + void testParseNegativeSolidusTemp() { + Metar metar = new Metar(); + command.execute(metar, "M05////"); + assertEquals(-5, metar.getTemperature().intValue()); + assertNull(metar.getDewPoint()); + } + + @Test + void testParseZeroTemperature() { + Metar metar = new Metar(); + command.execute(metar, "00/M05"); + assertEquals(0, metar.getTemperature().intValue()); + assertEquals(-5, metar.getDewPoint().intValue()); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/parser/NCDCloudTest.java b/metarParser-parsers/src/test/java/io/github/mivek/parser/NCDCloudTest.java new file mode 100644 index 00000000..8e4c8e5f --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/parser/NCDCloudTest.java @@ -0,0 +1,47 @@ +package io.github.mivek.parser; + +import io.github.mivek.enums.CloudQuantity; +import io.github.mivek.model.Cloud; +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NCDCloudTest { + + private final MetarParser parser = new MetarParser(); + + @Test + void testParseMetarWithNCD() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM NCD 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + assertFalse(metar.getClouds().isEmpty(), "Should have cloud information"); + Cloud ncdCloud = metar.getClouds().get(0); + assertEquals(CloudQuantity.NCD, ncdCloud.getQuantity(), "Cloud quantity should be NCD"); + } + + @Test + void testParseMetarWithMixedCloudIncludingNCD() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM FEW050 SCT150 NCD OVC250 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + assertTrue(metar.getClouds().stream().anyMatch(c -> c.getQuantity() == CloudQuantity.NCD), "Should have NCD cloud"); + } + + @Test + void testParseMetarWithMultipleNCDReferences() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM NCD NCD 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + assertTrue(metar.getClouds().stream().filter(c -> c.getQuantity() == CloudQuantity.NCD).count() >= 1, "Should have at least one NCD cloud"); + } + + @Test + void testParseMetarWithCavokAndNCD() throws Exception { + String code = "KJFK 121151Z 24016G28KT CAVOK 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR with CAVOK should parse"); + assertTrue(metar.isCavok(), "CAVOK should be set"); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/parser/NSWHandlingTest.java b/metarParser-parsers/src/test/java/io/github/mivek/parser/NSWHandlingTest.java new file mode 100644 index 00000000..6f370b83 --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/parser/NSWHandlingTest.java @@ -0,0 +1,47 @@ +package io.github.mivek.parser; + +import io.github.mivek.model.Metar; +import io.github.mivek.model.trend.MetarTrend; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NSWHandlingTest { + + private final MetarParser parser = new MetarParser(); + + @Test + void testParseMetarWithNSWInTrend() throws Exception { + String code = "METAR KJFK 121151Z 24016G28KT 10SM TSRA 25/10 Q1012 RETS NOSTEND TEMPO 1015/1020 NSW"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + if (!metar.getTrends().isEmpty()) { + MetarTrend trend = metar.getTrends().get(0); + assertTrue(trend.isNsw(), "Trend should have NSW flag set"); + assertTrue(trend.getWeatherConditions().isEmpty(), "Weather conditions should be cleared when NSW is set"); + } + } + + @Test + void testParseMetarTrendWithoutNSW() throws Exception { + String code = "METAR KJFK 121151Z 24016G28KT 10SM 25/10 Q1012 TEMPO 1015/1020 TSRA"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + if (!metar.getTrends().isEmpty()) { + MetarTrend trend = metar.getTrends().get(0); + assertFalse(trend.isNsw(), "Trend without NSW should have flag false"); + assertFalse(trend.getWeatherConditions().isEmpty(), "Weather conditions should be present without NSW"); + } + } + + @Test + void testParseNSWWithWeatherTransition() throws Exception { + String code = "METAR KJFK 121151Z 24016G28KT 10SM +TSRA 25/10 Q1012 TEMPO 1015/1020 NSW"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + assertTrue(metar.getWeatherConditions().size() > 0, "Present weather should be set"); + if (!metar.getTrends().isEmpty()) { + assertTrue(metar.getTrends().get(0).isNsw(), "Trend should have NSW"); + } + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/parser/ReportTypeTest.java b/metarParser-parsers/src/test/java/io/github/mivek/parser/ReportTypeTest.java new file mode 100644 index 00000000..628864ef --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/parser/ReportTypeTest.java @@ -0,0 +1,35 @@ +package io.github.mivek.parser; + +import io.github.mivek.enums.ReportType; +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ReportTypeTest { + + private final MetarParser parser = new MetarParser(); + + @Test + void testParseMetarWithReportType() throws Exception { + String code = "METAR KJFK 121151Z 24016G28KT 10SM FEW250 25/10 Q1012"; + Metar metar = parser.parse(code); + assertEquals(ReportType.METAR, metar.getReportType(), "Should have METAR report type"); + assertEquals("KJFK", metar.getStation(), "Station should be parsed after METAR"); + } + + @Test + void testParseSpeciWithReportType() throws Exception { + String code = "SPECI KJFK 121151Z 24016G28KT 2SM TSRA 25/10 Q1012"; + Metar metar = parser.parse(code); + assertEquals(ReportType.SPECI, metar.getReportType(), "Should have SPECI report type"); + assertEquals("KJFK", metar.getStation(), "Station should be parsed after SPECI"); + } + + @Test + void testParseMetarWithoutReportType() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM FEW250 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNull(metar.getReportType(), "Should have no report type when not specified"); + } +} diff --git a/metarParser-parsers/src/test/java/io/github/mivek/parser/SolidusHandlingTest.java b/metarParser-parsers/src/test/java/io/github/mivek/parser/SolidusHandlingTest.java new file mode 100644 index 00000000..9a9f8208 --- /dev/null +++ b/metarParser-parsers/src/test/java/io/github/mivek/parser/SolidusHandlingTest.java @@ -0,0 +1,115 @@ +package io.github.mivek.parser; + +import io.github.mivek.model.Metar; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class SolidusHandlingTest { + + private final MetarParser parser = new MetarParser(); + + @Test + void testParseMetarWithSolidusTemperature() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM ////10 Q1012"; + Metar metar = parser.parse(code); + assertNull(metar.getTemperature(), "Temperature should be null when missing"); + assertEquals(10, metar.getDewPoint().intValue(), "Dew point should be parsed"); + } + + @Test + void testParseMetarWithSolidusDewPoint() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM 25//// Q1012"; + Metar metar = parser.parse(code); + assertEquals(25, metar.getTemperature().intValue(), "Temperature should be parsed"); + assertNull(metar.getDewPoint(), "Dew point should be null when missing"); + } + + @Test + void testParseMetarWithSolidusAltimeter() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM 25/10 Q////"; + Metar metar = parser.parse(code); + assertEquals(25, metar.getTemperature().intValue(), "Temperature should be parsed"); + assertNull(metar.getAltimeter(), "Altimeter should be null when missing"); + } + + @Test + void testParseMetarWithSolidusVisibility() throws Exception { + String code = "KJFK 121151Z 24016G28KT //// 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR should parse"); + } + + @Test + void testParseMetarWithSolidusWind() throws Exception { + String code = "KJFK 121151Z /////KT 10SM 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR with solidus wind should parse"); + } + + @Test + void testParseMetarWithP99Wind() throws Exception { + String code = "KJFK 121151Z 280P99KT 10SM 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar.getWind(), "Wind should be parsed"); + assertEquals(100, metar.getWind().getSpeed(), "Wind speed P99 should be converted to 100"); + } + + @Test + void testParseMetarWithP99WindAndGust() throws Exception { + String code = "KJFK 121151Z 280P99G50KT 10SM 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar.getWind(), "Wind should be parsed"); + assertEquals(100, metar.getWind().getSpeed(), "Wind speed P99 should be 100"); + assertEquals(50, metar.getWind().getGust(), "Wind gust should be 50"); + } + + @Test + void testParseMetarWithP99GustOnly() throws Exception { + String code = "KJFK 121151Z 28050GP99KT 10SM 25/10 Q1012"; + Metar metar = parser.parse(code); + assertNotNull(metar.getWind(), "Wind should be parsed"); + assertEquals(100, metar.getWind().getGust(), "Wind gust P99 should be converted to 100"); + } + + @Test + void testParseMetarWithNegativeTemperature() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM M25/M30 Q1012"; + Metar metar = parser.parse(code); + assertEquals(-25, metar.getTemperature().intValue(), "Temperature should be -25"); + assertEquals(-30, metar.getDewPoint().intValue(), "Dew point should be -30"); + } + + @Test + void testParseMetarWithBothTemperaturesNull() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM /////// Q1012"; + Metar metar = parser.parse(code); + assertNull(metar.getTemperature(), "Temperature should be null"); + assertNull(metar.getDewPoint(), "Dew point should be null"); + } + + @Test + void testParseMetarWithAllSolidusValues() throws Exception { + String code = "KJFK 121151Z 000////KT //// /////// Q////"; + Metar metar = parser.parse(code); + assertNotNull(metar, "METAR with all solidus values should parse"); + assertNull(metar.getAltimeter(), "Altimeter should be null"); + } + + @Test + void testParseMetarWithMixedNormalAndSolidus() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM 25//// Q1012"; + Metar metar = parser.parse(code); + assertEquals(25, metar.getTemperature().intValue(), "Temperature should be 25"); + assertNull(metar.getDewPoint(), "Dew point should be null"); + assertEquals(1012, metar.getAltimeter().intValue(), "Altimeter should be 1012"); + } + + @Test + void testParseMetarWithNegativeSolidusTemp() throws Exception { + String code = "KJFK 121151Z 24016G28KT 10SM M05//// Q1012"; + Metar metar = parser.parse(code); + assertEquals(-5, metar.getTemperature().intValue(), "Negative temperature should be parsed"); + assertNull(metar.getDewPoint(), "Dew point should be null"); + } +}