Skip to content

Commit

Permalink
Merge branch 'master' into feature-sigmet-model
Browse files Browse the repository at this point in the history
  • Loading branch information
petringo committed Nov 4, 2019
2 parents 7c50c6a + 3a4858c commit 641fb63
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 38 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<description>Aviation weather message conversions</description>
<groupId>fi.fmi.avi.converter</groupId>
<artifactId>fmi-avi-messageconverter</artifactId>
<version>3.9.4-SNAPSHOT</version>
<version>3.9.5-SNAPSHOT</version>

<scm>
<connection>scm:git:https://github.com/fmidev/fmi-avi-messageconverter.git</connection>
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/fi/fmi/avi/util/BulletinHeadingDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
import fi.fmi.avi.model.bulletin.DataTypeDesignatorT2;
import fi.fmi.avi.model.bulletin.immutable.BulletinHeadingImpl;

public class BulletinHeadingDecoder {
public final class BulletinHeadingDecoder {
// Abbreviated heading with lenient augmentation indicator
private static final Pattern ABBREVIATED_HEADING = Pattern.compile(
"^(?<TT>[A-Z]{2})(?<AA>[A-Z]{2})(?<ii>[0-9]{2})\\s*(?<CCCC>[A-Z]{4})\\s*(?<YY>[0-9]{2})(?<GG>[0-9]{2})(?<gg>[0-9]{2})\\s*(?<BBB>[A-Z0-9]+)?$");
// Strict augmentation indicator
private static final Pattern AUGMENTATION_INDICATOR = Pattern.compile("(?<BBB>(CC|RR|AA)[A-Z])?");

private BulletinHeadingDecoder() {
throw new AssertionError();
}

public static BulletinHeading decode(final String input, final ConversionHints hints) throws IllegalArgumentException {
final Matcher m = ABBREVIATED_HEADING.matcher(input);
final String illegalInputMessage = "String '" + input + "' does not match the Abbreviated heading formats 'T1T2A1A2iiCCCCYYGGgg[BBB]' "
Expand All @@ -45,7 +49,7 @@ public static BulletinHeading decode(final String input, final ConversionHints h
}

type = BulletinHeading.Type.fromCode(bbb.substring(0, 2));
bulletinAugmentationNumber = bbb.charAt(2) - 'A' + 1;
bulletinAugmentationNumber = decodeAugmentationNumber(bbb.charAt(2));
}

final DataTypeDesignatorT1 t1 = DataTypeDesignatorT1.fromCode(m.group("TT").charAt(0));
Expand All @@ -65,4 +69,14 @@ public static BulletinHeading decode(final String input, final ConversionHints h
.setIssueTime(PartialOrCompleteTimeInstant.of(PartialDateTime.parse(issueTime)))//
.build();
}

public static int decodeAugmentationNumber(final char tacChar) {
if (tacChar < BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN_CHAR || tacChar > BulletinHeadingEncoder.AUGMENTATION_NUMBER_MAX_CHAR) {
throw new IllegalArgumentException(
"Illegal augmentation number TAC char '" + tacChar + "'; the value must be between '" + BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN_CHAR
+ "' and " + "'" + BulletinHeadingEncoder.AUGMENTATION_NUMBER_MAX_CHAR + "'");
}
return tacChar - BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN_CHAR + BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN;
}

}
118 changes: 87 additions & 31 deletions src/main/java/fi/fmi/avi/util/BulletinHeadingEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@

import static java.util.Objects.requireNonNull;

import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import fi.fmi.avi.converter.ConversionHints;
import fi.fmi.avi.model.MessageFormat;
import fi.fmi.avi.model.PartialOrCompleteTimeInstant;
import fi.fmi.avi.model.bulletin.BulletinHeading;

public class BulletinHeadingEncoder {
public final class BulletinHeadingEncoder {
static final char AUGMENTATION_NUMBER_MIN_CHAR = 'A';
static final char AUGMENTATION_NUMBER_MAX_CHAR = 'Z';
static final int AUGMENTATION_NUMBER_MIN = 1;
static final int AUGMENTATION_NUMBER_MAX = AUGMENTATION_NUMBER_MAX_CHAR - AUGMENTATION_NUMBER_MIN_CHAR + AUGMENTATION_NUMBER_MIN;

private BulletinHeadingEncoder() {
throw new AssertionError();
}

@Deprecated
public static String encode(final BulletinHeading input, final ConversionHints hints) {
Expand All @@ -20,57 +29,104 @@ public static String encode(final BulletinHeading input, final ConversionHints h
public static String encode(final BulletinHeading input, final MessageFormat messageFormat, final ConversionHints hints) {
requireNonNull(input, "input");
requireNonNull(messageFormat, "messageFormat");
final Function<BulletinHeading, String> dataDesignatorsReader = getDataDesignatorsReader(messageFormat);
boolean useSpaces = true;
if (hints != null && hints.containsKey(ConversionHints.KEY_BULLETIN_HEADING_SPACING)) {
useSpaces = hints.get(ConversionHints.KEY_BULLETIN_HEADING_SPACING).equals(ConversionHints.VALUE_BULLETIN_HEADING_SPACING_SPACE);
}
final StringBuilder sb = new StringBuilder();
sb.append(dataDesignatorsReader.apply(input));
sb.append(getDataDesignators(input, messageFormat));
if (useSpaces) {
sb.append(' ');
}
sb.append(input.getLocationIndicator());
if (useSpaces) {
sb.append(' ');
}
final OptionalInt day = input.getIssueTime().getDay();
final OptionalInt hour = input.getIssueTime().getHour();
final OptionalInt minute = input.getIssueTime().getMinute();
if (day.isPresent() && hour.isPresent() && minute.isPresent()) {
sb.append(String.format("%02d", day.getAsInt()));
sb.append(String.format("%02d", hour.getAsInt()));
sb.append(String.format("%02d", minute.getAsInt()));
} else {
throw new IllegalArgumentException("Day, hour or minute missing from bulletin issue time");
}
final Optional<Integer> augNumber = input.getBulletinAugmentationNumber();
if (augNumber.isPresent()) {
if (input.getType() == BulletinHeading.Type.NORMAL) {
throw new IllegalArgumentException("Bulletin contains augmentation number, but the type is " + BulletinHeading.Type.NORMAL);
}
final int seqNumber = Character.codePointAt("A", 0) + augNumber.get() - 1;
//Using Character.codePointAt here is a bit overdo here since we know that we are always operating with single char ASCII codes
if (seqNumber < Character.codePointAt("A", 0) || seqNumber > Character.codePointAt("Z", 0)) {
throw new IllegalArgumentException(
"Illegal bulletin augmentation number '" + augNumber.get() + "', the value must be between 1 and " + ('Z' - 'A' + 1));
}
appendIssueTime(sb, input.getIssueTime());
checkBBBIndicatorDataConsistency(input);
if (encodesBBBIndicator(input.getType())) {
if (useSpaces) {
sb.append(' ');
}
sb.append(input.getType().getPrefix());
sb.append(String.valueOf(Character.toChars(seqNumber)));
appendBBBIndicator(sb, input.getType(), input.getBulletinAugmentationNumber().orElse(1));
}
return sb.toString();
}

private static Function<BulletinHeading, String> getDataDesignatorsReader(final MessageFormat messageFormat) {
public static String getDataDesignators(final BulletinHeading input, final MessageFormat messageFormat) {
requireNonNull(input, "input");
requireNonNull(messageFormat, "messageFormat");
if (messageFormat.equals(MessageFormat.TEXT)) {
return BulletinHeading::getDataDesignatorsForTAC;
return input.getDataDesignatorsForTAC();
} else if (messageFormat.equals(MessageFormat.XML)) {
return BulletinHeading::getDataDesignatorsForXML;
return input.getDataDesignatorsForXML();
} else {
throw new IllegalArgumentException("Unsupported messageFormat: " + messageFormat);
}
}

public static String encodeIssueTime(final PartialOrCompleteTimeInstant issueTime) {
requireNonNull(issueTime, "issueTime");
return appendIssueTime(new StringBuilder(), issueTime).toString();
}

private static StringBuilder appendIssueTime(final StringBuilder sb, final PartialOrCompleteTimeInstant issueTime) {
final OptionalInt day = issueTime.getDay();
final OptionalInt hour = issueTime.getHour();
final OptionalInt minute = issueTime.getMinute();
if (!day.isPresent() || !hour.isPresent() || !minute.isPresent()) {
final String emptyFields = Stream.of(//
day.isPresent() ? "" : "day", //
hour.isPresent() ? "" : "hour", //
minute.isPresent() ? "" : "minute")//
.filter(field -> !field.isEmpty())//
.collect(Collectors.joining(", "));
throw new IllegalArgumentException("Missing " + emptyFields + " from bulletin issue time " + issueTime);
}
return sb.append(String.format("%02d", day.getAsInt()))//
.append(String.format("%02d", hour.getAsInt()))//
.append(String.format("%02d", minute.getAsInt()));
}

private static void checkBBBIndicatorDataConsistency(final BulletinHeading input) {
final boolean encodesBBBIndicator = encodesBBBIndicator(input.getType());
final boolean hasAugmentationNumber = input.getBulletinAugmentationNumber().isPresent();
if (encodesBBBIndicator && !hasAugmentationNumber) {
throw new IllegalArgumentException("Missing bulletinAugmentationNumber; is required with type " + input.getType());
} else if (!encodesBBBIndicator && hasAugmentationNumber) {
throw new IllegalArgumentException("Bulletin contains augmentation number, but it is unsupported with type " + input.getType());
}
}

private static boolean encodesBBBIndicator(final BulletinHeading.Type bulletinHeadingType) {
return !bulletinHeadingType.getPrefix().isEmpty();
}

public static String encodeBBBIndicator(final BulletinHeading input) {
requireNonNull(input, "input");
checkBBBIndicatorDataConsistency(input);
return encodeBBBIndicator(input.getType(), input.getBulletinAugmentationNumber().orElse(1));
}

public static String encodeBBBIndicator(final BulletinHeading.Type bulletinHeadingType, final int augmentationNumber) {
requireNonNull(bulletinHeadingType, "bulletinHeadingType");
if (!encodesBBBIndicator(bulletinHeadingType)) {
return "";
}
return appendBBBIndicator(new StringBuilder(), bulletinHeadingType, augmentationNumber).toString();
}

private static StringBuilder appendBBBIndicator(final StringBuilder sb, final BulletinHeading.Type bulletinHeadingType, final int augmentationNumber) {
return sb.append(bulletinHeadingType.getPrefix())//
.append(encodeAugmentationNumber(augmentationNumber));
}

public static char encodeAugmentationNumber(final int augmentationNumber) {
if (augmentationNumber < AUGMENTATION_NUMBER_MIN || augmentationNumber > AUGMENTATION_NUMBER_MAX) {
throw new IllegalArgumentException(
"Illegal augmentation number <" + augmentationNumber + ">; the value must be between " + AUGMENTATION_NUMBER_MIN + " and "
+ AUGMENTATION_NUMBER_MAX);
}
return (char) (augmentationNumber - AUGMENTATION_NUMBER_MIN + AUGMENTATION_NUMBER_MIN_CHAR);
}
}
39 changes: 35 additions & 4 deletions src/test/java/fi/fmi/avi/util/BulletinHeadingDecoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ public class BulletinHeadingDecoderTest {
private static final Map<String, String> AUGMENTATION_INDICATOR_REPLACEMENTS = ImmutableMap.of("COR", "CCA", "RTD", "RRA", "AMD", "AAA");
private static final ConversionHints EXTENDED_AUGMENTATION_IDENTIFIERS = new ConversionHints();

@Rule
public ExpectedException thrown = ExpectedException.none();

static {
EXTENDED_AUGMENTATION_IDENTIFIERS.put(ConversionHints.KEY_BULLETIN_HEADING_AUGMENTATION_INDICATOR_EXTENSION,
(BulletinHeadingIndicatorInterpreter) key -> AUGMENTATION_INDICATOR_REPLACEMENTS.getOrDefault(key, key));
}

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
@Parameters
public void decode_bulletin_headings(final String input, final BulletinHeading expected, final ConversionHints conversionHints,
Expand All @@ -82,7 +82,9 @@ public Object parametersForDecode_bulletin_headings() {
ConversionHints.EMPTY, null },//
new Object[] { "FTFI32 AAAA 250200 RRA", TAF_BULLETIN_HEADING.toBuilder()
.setType(BulletinHeading.Type.DELAYED)
.setBulletinAugmentationNumber(1).setBulletinNumber(32).setLocationIndicator("AAAA").build(), ConversionHints.EMPTY, null },//
.setBulletinAugmentationNumber(1)
.setBulletinNumber(32)
.setLocationIndicator("AAAA").build(), ConversionHints.EMPTY, null },//
// Extended augmentation indicators
new Object[] { "FTFI31 EFLK 250200 COR",
TAF_BULLETIN_HEADING.toBuilder().setType(BulletinHeading.Type.CORRECTED).setBulletinAugmentationNumber(1).build(),
Expand All @@ -105,4 +107,33 @@ public Object parametersForDecode_bulletin_headings() {
ConversionHints.EMPTY, null } };
}

@Test
public void decodeAugmentationNumber_GivenMinimumChar_ShouldReturnMinimumNumber() {
assertEquals(BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN,
BulletinHeadingDecoder.decodeAugmentationNumber(BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN_CHAR));
}

@Test
public void decodeAugmentationNumber_GivenMaximumChar_ShouldReturnMaximumNumber() {
assertEquals(BulletinHeadingEncoder.AUGMENTATION_NUMBER_MAX,
BulletinHeadingDecoder.decodeAugmentationNumber(BulletinHeadingEncoder.AUGMENTATION_NUMBER_MAX_CHAR));
}

@Test
public void decodeAugmentationNumber_GivenCharSmallerThanMinimum_ShouldThrowException() {
final char tacChar = BulletinHeadingEncoder.AUGMENTATION_NUMBER_MIN_CHAR - 1;
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("'" + tacChar + "'");
//noinspection ResultOfMethodCallIgnored
BulletinHeadingDecoder.decodeAugmentationNumber(tacChar);
}

@Test
public void decodeAugmentationNumber_GivenCharGreaterThanMaximum_ShouldThrowException() {
final char tacChar = BulletinHeadingEncoder.AUGMENTATION_NUMBER_MAX_CHAR + 1;
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("'" + tacChar + "'");
//noinspection ResultOfMethodCallIgnored
BulletinHeadingDecoder.decodeAugmentationNumber(tacChar);
}
}
Loading

0 comments on commit 641fb63

Please sign in to comment.