Added feature to get vessel flag from MMSI.#49
Conversation
There was a problem hiding this comment.
Pull Request Overview
Adds a utility to derive a vessel’s flag from an MMSI by mapping MIDs to country names and ISO codes.
- Introduces VesselFlag with a static MID→flag map and getters
- Adds getFlagFromMmsi(String) to extract the MID from MMSI prefixes and look up the flag
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| public class VesselFlag { | ||
|
|
||
| private final static HashMap<Integer, VesselFlag> FLAGS_MAP = new HashMap<>(); |
There was a problem hiding this comment.
The API exposes a mutable internal map and returns a raw type. Return an unmodifiable Map<Integer, VesselFlag> to prevent external mutation and use generics for type safety. Example: change the field to private static final Map<Integer, VesselFlag> and return Collections.unmodifiableMap(FLAGS_MAP).
| public static HashMap getVesselFlags() { | ||
| return FLAGS_MAP; | ||
| } |
There was a problem hiding this comment.
The API exposes a mutable internal map and returns a raw type. Return an unmodifiable Map<Integer, VesselFlag> to prevent external mutation and use generics for type safety. Example: change the field to private static final Map<Integer, VesselFlag> and return Collections.unmodifiableMap(FLAGS_MAP).
| private String country, shortCode; | ||
|
|
||
| protected VesselFlag(String country, String shortCode) { | ||
| this.country = country; | ||
| this.shortCode = shortCode; | ||
| } |
There was a problem hiding this comment.
[nitpick] Make VesselFlag immutable and non-extensible: declare fields final, make the constructor private, and consider making the class final. This prevents accidental mutation and clarifies usage as a value object.
| public static VesselFlag getFlagFromMmsi(String mmsi) throws NumberFormatException { | ||
| int mid; | ||
| if (mmsi.startsWith("111") || mmsi.startsWith("970")) { | ||
| mid = Integer.parseInt(mmsi.substring(3, 6)); | ||
| } else if (mmsi.startsWith("00") || mmsi.startsWith("98") || mmsi.startsWith("99")) { | ||
| mid = Integer.parseInt(mmsi.substring(2, 5)); | ||
| } else if (mmsi.startsWith("8") || mmsi.startsWith("0")) { | ||
| mid = Integer.parseInt(mmsi.substring(1, 4)); | ||
| } else { | ||
| mid = Integer.parseInt(mmsi.substring(0, 3)); | ||
| } | ||
|
|
There was a problem hiding this comment.
Substring operations can throw StringIndexOutOfBoundsException for short inputs and NumberFormatException for non-digit inputs. Add input validation (length and numeric) and fail fast with an IllegalArgumentException and a clear message before substring/parse.
| public static VesselFlag getFlagFromMmsi(String mmsi) throws NumberFormatException { | |
| int mid; | |
| if (mmsi.startsWith("111") || mmsi.startsWith("970")) { | |
| mid = Integer.parseInt(mmsi.substring(3, 6)); | |
| } else if (mmsi.startsWith("00") || mmsi.startsWith("98") || mmsi.startsWith("99")) { | |
| mid = Integer.parseInt(mmsi.substring(2, 5)); | |
| } else if (mmsi.startsWith("8") || mmsi.startsWith("0")) { | |
| mid = Integer.parseInt(mmsi.substring(1, 4)); | |
| } else { | |
| mid = Integer.parseInt(mmsi.substring(0, 3)); | |
| } | |
| public static VesselFlag getFlagFromMmsi(String mmsi) { | |
| if (mmsi == null) { | |
| throw new IllegalArgumentException("MMSI must not be null"); | |
| } | |
| // The largest substring index is 6 (mmsi.substring(3, 6)), so require at least 6 characters | |
| if (mmsi.length() < 6) { | |
| throw new IllegalArgumentException("MMSI must be at least 6 characters long: " + mmsi); | |
| } | |
| int mid; | |
| String midStr; | |
| if (mmsi.startsWith("111") || mmsi.startsWith("970")) { | |
| midStr = mmsi.substring(3, 6); | |
| } else if (mmsi.startsWith("00") || mmsi.startsWith("98") || mmsi.startsWith("99")) { | |
| midStr = mmsi.substring(2, 5); | |
| } else if (mmsi.startsWith("8") || mmsi.startsWith("0")) { | |
| midStr = mmsi.substring(1, 4); | |
| } else { | |
| midStr = mmsi.substring(0, 3); | |
| } | |
| if (midStr.length() != 3 || !midStr.chars().allMatch(Character::isDigit)) { | |
| throw new IllegalArgumentException("MMSI MID substring is not a 3-digit number: " + midStr + " (from MMSI: " + mmsi + ")"); | |
| } | |
| mid = Integer.parseInt(midStr); |
| FLAGS_MAP.put(725, new VesselFlag("Chile", "CL")); | ||
| FLAGS_MAP.put(730, new VesselFlag("Colombia", "CO")); | ||
| FLAGS_MAP.put(735, new VesselFlag("Ecuador", "EC")); | ||
| FLAGS_MAP.put(740, new VesselFlag("UK", "UK")); |
There was a problem hiding this comment.
The ISO 3166-1 alpha-2 code 'UK' is invalid (United Kingdom is 'GB'). Replace the short code with 'GB', and for consistency with other entries use 'United Kingdom' as the country name.
| FLAGS_MAP.put(740, new VesselFlag("UK", "UK")); | |
| FLAGS_MAP.put(740, new VesselFlag("United Kingdom", "GB")); |
| public String getShortCode() { | ||
| return shortCode; | ||
| } | ||
|
|
There was a problem hiding this comment.
Add Javadoc describing the MMSI parsing rules (special prefixes like 111/970, 00/98/99, 8/0) and the expected MMSI format/length, plus what is returned when no MID match is found (null). This will make the intent and edge-case handling clearer to callers.
| /** | |
| * Returns the {@link VesselFlag} corresponding to the given MMSI (Maritime Mobile Service Identity). | |
| * <p> | |
| * MMSI is expected to be a 9-digit numeric string. The method extracts the Maritime Identification Digits (MID) | |
| * using the following rules: | |
| * <ul> | |
| * <li>If the MMSI starts with "111" or "970", the MID is digits 4-6 (positions 3-5, zero-based).</li> | |
| * <li>If the MMSI starts with "00", "98", or "99", the MID is digits 3-5 (positions 2-4).</li> | |
| * <li>If the MMSI starts with "8" or "0", the MID is digits 2-4 (positions 1-3).</li> | |
| * <li>Otherwise, the MID is the first three digits (positions 0-2).</li> | |
| * </ul> | |
| * <p> | |
| * If the extracted MID does not match any known country, this method returns {@code null}. | |
| * <p> | |
| * @param mmsi the 9-digit MMSI string to parse | |
| * @return the corresponding {@link VesselFlag}, or {@code null} if no MID match is found | |
| * @throws NumberFormatException if the MMSI is not numeric or is too short to extract the MID | |
| */ |
| FLAGS_MAP.put(271, new VesselFlag("Turkey", "TR")); | ||
| FLAGS_MAP.put(272, new VesselFlag("Ukraine", "UA")); | ||
| FLAGS_MAP.put(273, new VesselFlag("Russia", "RU")); | ||
| FLAGS_MAP.put(274, new VesselFlag("FYR Macedonia", "MK")); |
There was a problem hiding this comment.
Update country name to its current official name 'North Macedonia'.
| FLAGS_MAP.put(274, new VesselFlag("FYR Macedonia", "MK")); | |
| FLAGS_MAP.put(274, new VesselFlag("North Macedonia", "MK")); |
| FLAGS_MAP.put(666, new VesselFlag("Somalia", "SO")); | ||
| FLAGS_MAP.put(667, new VesselFlag("Sierra Leone", "SL")); | ||
| FLAGS_MAP.put(668, new VesselFlag("Sao Tome Principe", "ST")); | ||
| FLAGS_MAP.put(669, new VesselFlag("Swaziland", "SZ")); |
There was a problem hiding this comment.
Update country name to its current official name 'Eswatini'.
| FLAGS_MAP.put(669, new VesselFlag("Swaziland", "SZ")); | |
| FLAGS_MAP.put(669, new VesselFlag("Eswatini", "SZ")); |
No description provided.