From 7408173246decabadea21c21994fbd3ad269872b Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sun, 7 Jan 2024 14:31:34 +0100 Subject: [PATCH] LocaleCompat.forLanguageTag: return Optional if parsing fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not obvious that the function will fail in some cases and throw an `IllegalArgumentException`. So instead of just failing if parsing fails, return an Optional that all callers have to decide what to do (e.g. the YoutubeExtractor can just ignore the locale in that case, like it does with most other fields in the json if they are unexpected). --- .../extractor/localization/Localization.java | 22 +++++++++++++------ .../extractors/MediaCCCStreamExtractor.java | 5 ++++- .../extractors/YoutubeStreamExtractor.java | 11 +++++++--- .../extractor/stream/SubtitlesStream.java | 4 +++- .../newpipe/extractor/utils/LocaleCompat.java | 19 ++++++++-------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java index 80ac26663..5727150fe 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java @@ -11,10 +11,12 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; + public class Localization implements Serializable { public static final Localization DEFAULT = new Localization("en", "GB"); @@ -26,20 +28,28 @@ public class Localization implements Serializable { /** * @param localizationCodeList a list of localization code, formatted like {@link * #getLocalizationCode()} + * @throws IllegalArgumentException If any of the localizationCodeList is formatted incorrectly + * @return list of Localization objects */ + @Nonnull public static List listFrom(final String... localizationCodeList) { final List toReturn = new ArrayList<>(); for (final String localizationCode : localizationCodeList) { - toReturn.add(fromLocalizationCode(localizationCode)); + toReturn.add(fromLocalizationCode(localizationCode) + .orElseThrow(() -> new IllegalArgumentException( + "Not a localization code: " + localizationCode + ))); } return Collections.unmodifiableList(toReturn); } /** * @param localizationCode a localization code, formatted like {@link #getLocalizationCode()} + * @return A Localization, if the code was valid. */ - public static Localization fromLocalizationCode(final String localizationCode) { - return fromLocale(LocaleCompat.forLanguageTag(localizationCode)); + @Nonnull + public static Optional fromLocalizationCode(final String localizationCode) { + return LocaleCompat.forLanguageTag(localizationCode).map(Localization::fromLocale); } public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) { @@ -61,10 +71,6 @@ public class Localization implements Serializable { return countryCode == null ? "" : countryCode; } - public Locale asLocale() { - return new Locale(getLanguageCode(), getCountryCode()); - } - public static Localization fromLocale(@Nonnull final Locale locale) { return new Localization(locale.getLanguage(), locale.getCountry()); } @@ -72,6 +78,8 @@ public class Localization implements Serializable { /** * Return a formatted string in the form of: {@code language-Country}, or * just {@code language} if country is {@code null}. + * + * @return A correctly formatted localizationCode for this localization. */ public String getLocalizationCode() { return languageCode + (countryCode == null ? "" : "-" + countryCode); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java index 9f299a71a..5e7641f0f 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java @@ -130,7 +130,10 @@ public class MediaCCCStreamExtractor extends StreamExtractor { // track with multiple languages, so there is no specific language for this stream // Don't set the audio language in this case if (language != null && !language.contains("-")) { - builder.setAudioLocale(LocaleCompat.forLanguageTag(language)); + builder.setAudioLocale(LocaleCompat.forLanguageTag(language).orElseThrow(() -> + new ExtractionException( + "Cannot convert this language to a locale: " + language) + )); } // Not checking containsSimilarStream here, since MediaCCC does not provide enough diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index e77ea16b4..fd6b94445 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -190,7 +190,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { try { // Premiered 20 hours ago final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor( - Localization.fromLocalizationCode("en")); + Localization.fromLocalizationCode("en").get()); final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime(); return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime); } catch (final Exception ignored) { @@ -1378,8 +1378,13 @@ public class YoutubeStreamExtractor extends StreamExtractor { final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf("."); if (audioTrackIdLastLocaleCharacter != -1) { // Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER - itagItem.setAudioLocale(LocaleCompat.forLanguageTag( - audioTrackId.substring(0, audioTrackIdLastLocaleCharacter))); + @Nullable final Locale locale = + LocaleCompat.forLanguageTag( + audioTrackId.substring(0, audioTrackIdLastLocaleCharacter + )).orElse(null); + if (locale != null) { + itagItem.setAudioLocale(locale); + } } itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl)); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java index 983d20ce9..278b4e9a6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java @@ -231,7 +231,9 @@ public final class SubtitlesStream extends Stream { final boolean autoGenerated, @Nullable final String manifestUrl) { super(id, content, isUrl, mediaFormat, deliveryMethod, manifestUrl); - this.locale = LocaleCompat.forLanguageTag(languageCode); + this.locale = LocaleCompat.forLanguageTag(languageCode).orElseThrow( + () -> new IllegalArgumentException( + "not a valid locale language code: " + languageCode)); this.code = languageCode; this.format = mediaFormat; this.autoGenerated = autoGenerated; diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java index a8f85f47b..082a56824 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.extractor.utils; import java.util.Locale; +import java.util.Optional; /** * This class contains a simple implementation of {@link Locale#forLanguageTag(String)} for Android @@ -15,29 +16,29 @@ public final class LocaleCompat { // Source: The AndroidX LocaleListCompat class's private forLanguageTagCompat() method. // Use Locale.forLanguageTag() on Android API level >= 21 / Java instead. - public static Locale forLanguageTag(final String str) { + public static Optional forLanguageTag(final String str) { if (str.contains("-")) { final String[] args = str.split("-", -1); if (args.length > 2) { - return new Locale(args[0], args[1], args[2]); + return Optional.of(new Locale(args[0], args[1], args[2])); } else if (args.length > 1) { - return new Locale(args[0], args[1]); + return Optional.of(new Locale(args[0], args[1])); } else if (args.length == 1) { - return new Locale(args[0]); + return Optional.of(new Locale(args[0])); } } else if (str.contains("_")) { final String[] args = str.split("_", -1); if (args.length > 2) { - return new Locale(args[0], args[1], args[2]); + return Optional.of(new Locale(args[0], args[1], args[2])); } else if (args.length > 1) { - return new Locale(args[0], args[1]); + return Optional.of(new Locale(args[0], args[1])); } else if (args.length == 1) { - return new Locale(args[0]); + return Optional.of(new Locale(args[0])); } } else { - return new Locale(str); + return Optional.of(new Locale(str)); } - throw new IllegalArgumentException("Can not parse language tag: [" + str + "]"); + return Optional.empty(); } }