LocaleCompat.forLanguageTag: return Optional if parsing fails

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).
This commit is contained in:
Profpatsch 2024-01-07 14:31:34 +01:00
parent 3402cdb666
commit 7408173246
5 changed files with 40 additions and 21 deletions

View File

@ -11,10 +11,12 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class Localization implements Serializable { public class Localization implements Serializable {
public static final Localization DEFAULT = new Localization("en", "GB"); 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 * @param localizationCodeList a list of localization code, formatted like {@link
* #getLocalizationCode()} * #getLocalizationCode()}
* @throws IllegalArgumentException If any of the localizationCodeList is formatted incorrectly
* @return list of Localization objects
*/ */
@Nonnull
public static List<Localization> listFrom(final String... localizationCodeList) { public static List<Localization> listFrom(final String... localizationCodeList) {
final List<Localization> toReturn = new ArrayList<>(); final List<Localization> toReturn = new ArrayList<>();
for (final String localizationCode : localizationCodeList) { 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); return Collections.unmodifiableList(toReturn);
} }
/** /**
* @param localizationCode a localization code, formatted like {@link #getLocalizationCode()} * @param localizationCode a localization code, formatted like {@link #getLocalizationCode()}
* @return A Localization, if the code was valid.
*/ */
public static Localization fromLocalizationCode(final String localizationCode) { @Nonnull
return fromLocale(LocaleCompat.forLanguageTag(localizationCode)); public static Optional<Localization> fromLocalizationCode(final String localizationCode) {
return LocaleCompat.forLanguageTag(localizationCode).map(Localization::fromLocale);
} }
public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) { public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) {
@ -61,10 +71,6 @@ public class Localization implements Serializable {
return countryCode == null ? "" : countryCode; return countryCode == null ? "" : countryCode;
} }
public Locale asLocale() {
return new Locale(getLanguageCode(), getCountryCode());
}
public static Localization fromLocale(@Nonnull final Locale locale) { public static Localization fromLocale(@Nonnull final Locale locale) {
return new Localization(locale.getLanguage(), locale.getCountry()); 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 * Return a formatted string in the form of: {@code language-Country}, or
* just {@code language} if country is {@code null}. * just {@code language} if country is {@code null}.
*
* @return A correctly formatted localizationCode for this localization.
*/ */
public String getLocalizationCode() { public String getLocalizationCode() {
return languageCode + (countryCode == null ? "" : "-" + countryCode); return languageCode + (countryCode == null ? "" : "-" + countryCode);

View File

@ -130,7 +130,10 @@ public class MediaCCCStreamExtractor extends StreamExtractor {
// track with multiple languages, so there is no specific language for this stream // track with multiple languages, so there is no specific language for this stream
// Don't set the audio language in this case // Don't set the audio language in this case
if (language != null && !language.contains("-")) { 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 // Not checking containsSimilarStream here, since MediaCCC does not provide enough

View File

@ -190,7 +190,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
try { // Premiered 20 hours ago try { // Premiered 20 hours ago
final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor( final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor(
Localization.fromLocalizationCode("en")); Localization.fromLocalizationCode("en").get());
final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime(); final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime();
return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime); return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime);
} catch (final Exception ignored) { } catch (final Exception ignored) {
@ -1378,8 +1378,13 @@ public class YoutubeStreamExtractor extends StreamExtractor {
final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf("."); final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf(".");
if (audioTrackIdLastLocaleCharacter != -1) { if (audioTrackIdLastLocaleCharacter != -1) {
// Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER // Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER
itagItem.setAudioLocale(LocaleCompat.forLanguageTag( @Nullable final Locale locale =
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter))); LocaleCompat.forLanguageTag(
audioTrackId.substring(0, audioTrackIdLastLocaleCharacter
)).orElse(null);
if (locale != null) {
itagItem.setAudioLocale(locale);
}
} }
itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl)); itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl));
} }

View File

@ -231,7 +231,9 @@ public final class SubtitlesStream extends Stream {
final boolean autoGenerated, final boolean autoGenerated,
@Nullable final String manifestUrl) { @Nullable final String manifestUrl) {
super(id, content, isUrl, mediaFormat, deliveryMethod, 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.code = languageCode;
this.format = mediaFormat; this.format = mediaFormat;
this.autoGenerated = autoGenerated; this.autoGenerated = autoGenerated;

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.extractor.utils; package org.schabi.newpipe.extractor.utils;
import java.util.Locale; import java.util.Locale;
import java.util.Optional;
/** /**
* This class contains a simple implementation of {@link Locale#forLanguageTag(String)} for Android * 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. // Source: The AndroidX LocaleListCompat class's private forLanguageTagCompat() method.
// Use Locale.forLanguageTag() on Android API level >= 21 / Java instead. // Use Locale.forLanguageTag() on Android API level >= 21 / Java instead.
public static Locale forLanguageTag(final String str) { public static Optional<Locale> forLanguageTag(final String str) {
if (str.contains("-")) { if (str.contains("-")) {
final String[] args = str.split("-", -1); final String[] args = str.split("-", -1);
if (args.length > 2) { 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) { } 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) { } else if (args.length == 1) {
return new Locale(args[0]); return Optional.of(new Locale(args[0]));
} }
} else if (str.contains("_")) { } else if (str.contains("_")) {
final String[] args = str.split("_", -1); final String[] args = str.split("_", -1);
if (args.length > 2) { 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) { } 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) { } else if (args.length == 1) {
return new Locale(args[0]); return Optional.of(new Locale(args[0]));
} }
} else { } else {
return new Locale(str); return Optional.of(new Locale(str));
} }
throw new IllegalArgumentException("Can not parse language tag: [" + str + "]"); return Optional.empty();
} }
} }