[YouTube] Try to use lighter requests when extracting client version and key from YouTube and YouTube Music

This is done by fetching https://www.youtube.com/sw.js for YouTube and https://music.youtube.com/sw.js for YouTube Music.

Two new methods in Utils class have been added which allow to try to get a match of regular expressions in a string array, or a Pattern array, on a content, on a specific index or 0.
Also some code refactoring has been made in this class.
This commit is contained in:
TiA4f8R 2022-01-09 22:49:37 +01:00
parent 05b7fee23b
commit 7d07924de8
No known key found for this signature in database
GPG key ID: E6D3E7F5949450DD
3 changed files with 264 additions and 115 deletions

View file

@ -5,6 +5,7 @@ import static org.schabi.newpipe.extractor.utils.Utils.EMPTY_STRING;
import static org.schabi.newpipe.extractor.utils.Utils.HTTP; import static org.schabi.newpipe.extractor.utils.Utils.HTTP;
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS; import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
import static org.schabi.newpipe.extractor.utils.Utils.UTF_8; import static org.schabi.newpipe.extractor.utils.Utils.UTF_8;
import static org.schabi.newpipe.extractor.utils.Utils.getStringResultFromRegexArray;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import com.grack.nanojson.JsonArray; import com.grack.nanojson.JsonArray;
@ -57,20 +58,20 @@ import javax.annotation.Nullable;
* Created by Christian Schabesberger on 02.03.16. * Created by Christian Schabesberger on 02.03.16.
* *
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* YoutubeParsingHelper.java is part of NewPipe. * YoutubeParsingHelper.java is part of NewPipe Extractor.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe Extractor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* NewPipe is distributed in the hope that it will be useful, * NewPipe Extractor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
*/ */
public final class YoutubeParsingHelper { public final class YoutubeParsingHelper {
@ -98,6 +99,15 @@ public final class YoutubeParsingHelper {
private static boolean keyAndVersionExtracted = false; private static boolean keyAndVersionExtracted = false;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static Optional<Boolean> hardcodedClientVersionAndKeyValid = Optional.empty(); private static Optional<Boolean> hardcodedClientVersionAndKeyValid = Optional.empty();
private static final String[] INNERTUBE_CONTEXT_CLIENT_VERSION_REGEXES =
{"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"([0-9\\.]+?)\"",
"innertube_context_client_version\":\"([0-9\\.]+?)\"",
"client.version=([0-9\\.]+)"};
private static final String[] INNERTUBE_API_KEY_REGEXES =
{"INNERTUBE_API_KEY\":\"([0-9a-zA-Z_-]+?)\"",
"innertubeApiKey\":\"([0-9a-zA-Z_-]+?)\""};
private static final String INNERTUBE_CLIENT_NAME_REGEX =
"INNERTUBE_CONTEXT_CLIENT_NAME\":([0-9]+?),";
private static final String CONTENT_PLAYBACK_NONCE_ALPHABET = private static final String CONTENT_PLAYBACK_NONCE_ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@ -484,12 +494,33 @@ public final class YoutubeParsingHelper {
return hardcodedClientVersionAndKeyValid.get(); return hardcodedClientVersionAndKeyValid.get();
} }
private static void extractClientVersionAndKey() throws IOException, ExtractionException {
private static void extractClientVersionAndKeyFromSwJs()
throws IOException, ExtractionException {
if (keyAndVersionExtracted) {
return;
}
final String url = "https://www.youtube.com/sw.js";
final Map<String, List<String>> headers = new HashMap<>();
headers.put("Origin", Collections.singletonList("https://www.youtube.com"));
headers.put("Referer", Collections.singletonList("https://www.youtube.com"));
final String response = getDownloader().get(url, headers).responseBody();
try {
clientVersion = getStringResultFromRegexArray(response,
INNERTUBE_CONTEXT_CLIENT_VERSION_REGEXES, 1);
key = getStringResultFromRegexArray(response, INNERTUBE_API_KEY_REGEXES, 1);
} catch (final Parser.RegexException e) {
throw new ParsingException("Could not extract YouTube WEB InnerTube client version and API key from sw.js", e);
}
keyAndVersionExtracted = true;
}
private static void extractClientVersionAndKeyFromHtmlSearchResultsPage()
throws IOException, ExtractionException {
// Don't extract the client version and the InnerTube key if it has been already extracted // Don't extract the client version and the InnerTube key if it has been already extracted
if (keyAndVersionExtracted) { if (keyAndVersionExtracted) {
return; return;
} }
// Don't provide a search term in order to have a smaller response // Don't provide a search term in order to have a smaller response
final String url = "https://www.youtube.com/results?search_query=&ucbcb=1"; final String url = "https://www.youtube.com/results?search_query=&ucbcb=1";
final Map<String, List<String>> headers = new HashMap<>(); final Map<String, List<String>> headers = new HashMap<>();
@ -526,21 +557,10 @@ public final class YoutubeParsingHelper {
} }
} }
String contextClientVersion; try {
final String[] patterns = { clientVersion = getStringResultFromRegexArray(html,
"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"([0-9\\.]+?)\"", INNERTUBE_CONTEXT_CLIENT_VERSION_REGEXES, 1);
"innertube_context_client_version\":\"([0-9\\.]+?)\"", } catch (final Parser.RegexException ignored) {
"client.version=([0-9\\.]+)"
};
for (final String pattern : patterns) {
try {
contextClientVersion = Parser.matchGroup1(pattern, html);
if (!isNullOrEmpty(contextClientVersion)) {
clientVersion = contextClientVersion;
break;
}
} catch (final Parser.RegexException ignored) {
}
} }
if (!isNullOrEmpty(clientVersion) && !isNullOrEmpty(shortClientVersion)) { if (!isNullOrEmpty(clientVersion) && !isNullOrEmpty(shortClientVersion)) {
@ -548,13 +568,10 @@ public final class YoutubeParsingHelper {
} }
try { try {
key = Parser.matchGroup1("INNERTUBE_API_KEY\":\"([0-9a-zA-Z_-]+?)\"", html); key = getStringResultFromRegexArray(html, INNERTUBE_API_KEY_REGEXES, 1);
} catch (final Parser.RegexException e1) { } catch (final Parser.RegexException e) {
try { throw new ParsingException(
key = Parser.matchGroup1("innertubeApiKey\":\"([0-9a-zA-Z_-]+?)\"", html); "Could not extract YouTube WEB InnerTube client version and API key from HTML search results page");
} catch (final Parser.RegexException e2) {
throw new ParsingException("Could not extract client version and key");
}
} }
keyAndVersionExtracted = true; keyAndVersionExtracted = true;
} }
@ -567,7 +584,11 @@ public final class YoutubeParsingHelper {
return clientVersion; return clientVersion;
} }
extractClientVersionAndKey(); try {
extractClientVersionAndKeyFromSwJs();
} catch (final Exception e) {
extractClientVersionAndKeyFromHtmlSearchResultsPage();
}
if (keyAndVersionExtracted) { if (keyAndVersionExtracted) {
return clientVersion; return clientVersion;
@ -588,7 +609,11 @@ public final class YoutubeParsingHelper {
return key; return key;
} }
extractClientVersionAndKey(); try {
extractClientVersionAndKeyFromSwJs();
} catch (final Exception e) {
extractClientVersionAndKeyFromHtmlSearchResultsPage();
}
if (keyAndVersionExtracted) { if (keyAndVersionExtracted) {
return key; return key;
@ -682,8 +707,8 @@ public final class YoutubeParsingHelper {
return response.responseBody().length() > 500 && response.responseCode() == 200; return response.responseBody().length() > 500 && response.responseCode() == 200;
} }
public static String[] getYoutubeMusicKey() throws IOException, ReCaptchaException, public static String[] getYoutubeMusicKey()
Parser.RegexException { throws IOException, ReCaptchaException, Parser.RegexException {
if (youtubeMusicKey != null && youtubeMusicKey.length == 3) { if (youtubeMusicKey != null && youtubeMusicKey.length == 3) {
return youtubeMusicKey; return youtubeMusicKey;
} }
@ -692,40 +717,34 @@ public final class YoutubeParsingHelper {
return youtubeMusicKey; return youtubeMusicKey;
} }
final String url = "https://music.youtube.com/"; String musicClientVersion = null;
final Map<String, List<String>> headers = new HashMap<>(); String musicKey = null;
addCookieHeader(headers); String musicClientName = null;
final String html = getDownloader().get(url, headers).responseBody();
String innertubeApiKey;
try { try {
innertubeApiKey = Parser.matchGroup1("INNERTUBE_API_KEY\":\"([0-9a-zA-Z_-]+?)\"", html); final String url = "https://music.youtube.com/sw.js";
} catch (final Parser.RegexException e) { final Map<String, List<String>> headers = new HashMap<>();
innertubeApiKey = Parser.matchGroup1("innertube_api_key\":\"([0-9a-zA-Z_-]+?)\"", html); headers.put("Origin", Collections.singletonList("https://music.youtube.com"));
headers.put("Referer", Collections.singletonList("https://music.youtube.com"));
final String response = getDownloader().get(url, headers).responseBody();
musicClientVersion = getStringResultFromRegexArray(response,
INNERTUBE_CONTEXT_CLIENT_VERSION_REGEXES, 1);
musicKey = getStringResultFromRegexArray(response,
INNERTUBE_API_KEY_REGEXES, 1);
musicClientName = Parser.matchGroup1(INNERTUBE_CLIENT_NAME_REGEX, response);
} catch (final Exception e) {
final String url = "https://music.youtube.com/";
final Map<String, List<String>> headers = new HashMap<>();
addCookieHeader(headers);
final String html = getDownloader().get(url, headers).responseBody();
musicKey = getStringResultFromRegexArray(html, INNERTUBE_API_KEY_REGEXES, 1);
musicClientVersion = getStringResultFromRegexArray(html,
INNERTUBE_CONTEXT_CLIENT_VERSION_REGEXES);
musicClientName = Parser.matchGroup1(INNERTUBE_CLIENT_NAME_REGEX, html);
} }
final String innertubeClientName youtubeMusicKey = new String[] { musicKey, musicClientName, musicClientVersion };
= Parser.matchGroup1("INNERTUBE_CONTEXT_CLIENT_NAME\":([0-9]+?),", html);
String innertubeClientVersion;
try {
innertubeClientVersion = Parser.matchGroup1(
"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"([0-9\\.]+?)\"", html);
} catch (final Parser.RegexException e) {
try {
innertubeClientVersion = Parser.matchGroup1(
"INNERTUBE_CLIENT_VERSION\":\"([0-9\\.]+?)\"", html);
} catch (final Parser.RegexException ee) {
innertubeClientVersion = Parser.matchGroup1(
"innertube_context_client_version\":\"([0-9\\.]+?)\"", html);
}
}
youtubeMusicKey = new String[]{
innertubeApiKey,
innertubeClientName,
innertubeClientVersion
};
return youtubeMusicKey; return youtubeMusicKey;
} }

View file

@ -1,3 +1,23 @@
/*
* Created by Christian Schabesberger on 02.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* Parser.java is part of NewPipe Extractor.
*
* NewPipe Extractor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe Extractor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
*/
package org.schabi.newpipe.extractor.utils; package org.schabi.newpipe.extractor.utils;
import org.nibor.autolink.LinkExtractor; import org.nibor.autolink.LinkExtractor;
@ -5,39 +25,21 @@ import org.nibor.autolink.LinkSpan;
import org.nibor.autolink.LinkType; import org.nibor.autolink.LinkType;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import javax.annotation.Nonnull;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.schabi.newpipe.extractor.utils.Utils.UTF_8; import static org.schabi.newpipe.extractor.utils.Utils.UTF_8;
/*
* Created by Christian Schabesberger on 02.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* Parser.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
/** /**
* avoid using regex !!! * Avoid using regex !!!
*/ */
public final class Parser { public final class Parser {
@ -66,8 +68,9 @@ public final class Parser {
return matchGroup(Pattern.compile(pattern), input, group); return matchGroup(Pattern.compile(pattern), input, group);
} }
public static String matchGroup(final Pattern pat, final String input, final int group) public static String matchGroup(@Nonnull final Pattern pat,
throws RegexException { final String input,
final int group) throws RegexException {
final Matcher matcher = pat.matcher(input); final Matcher matcher = pat.matcher(input);
final boolean foundMatch = matcher.find(); final boolean foundMatch = matcher.find();
if (foundMatch) { if (foundMatch) {
@ -75,9 +78,9 @@ public final class Parser {
} else { } else {
// only pass input to exception message when it is not too long // only pass input to exception message when it is not too long
if (input.length() > 1024) { if (input.length() > 1024) {
throw new RegexException("failed to find pattern \"" + pat.pattern() + "\""); throw new RegexException("Failed to find pattern \"" + pat.pattern() + "\"");
} else { } else {
throw new RegexException("failed to find pattern \"" + pat.pattern() throw new RegexException("Failed to find pattern \"" + pat.pattern()
+ "\" inside of \"" + input + "\""); + "\" inside of \"" + input + "\"");
} }
} }
@ -89,14 +92,15 @@ public final class Parser {
return mat.find(); return mat.find();
} }
public static boolean isMatch(final Pattern pattern, final String input) { public static boolean isMatch(@Nonnull final Pattern pattern, final String input) {
final Matcher mat = pattern.matcher(input); final Matcher mat = pattern.matcher(input);
return mat.find(); return mat.find();
} }
public static Map<String, String> compatParseMap(final String input) @Nonnull
public static Map<String, String> compatParseMap(@Nonnull final String input)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
final Map<String, String> map = new HashMap<>(); final Map<String, String> map = new HashMap<>();
for (final String arg : input.split("&")) { for (final String arg : input.split("&")) {
final String[] splitArg = arg.split("="); final String[] splitArg = arg.split("=");
if (splitArg.length > 1) { if (splitArg.length > 1) {
@ -108,9 +112,10 @@ public final class Parser {
return map; return map;
} }
@Nonnull
public static String[] getLinksFromString(final String txt) throws ParsingException { public static String[] getLinksFromString(final String txt) throws ParsingException {
try { try {
final ArrayList<String> links = new ArrayList<>(); final List<String> links = new ArrayList<>();
final LinkExtractor linkExtractor = LinkExtractor.builder() final LinkExtractor linkExtractor = LinkExtractor.builder()
.linkTypes(EnumSet.of(LinkType.URL, LinkType.WWW)) .linkTypes(EnumSet.of(LinkType.URL, LinkType.WWW))
.build(); .build();

View file

@ -2,6 +2,8 @@ package org.schabi.newpipe.extractor.utils;
import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ParsingException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@ -10,6 +12,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -36,7 +39,8 @@ public final class Utils {
* @param toRemove string to remove non-digit chars * @param toRemove string to remove non-digit chars
* @return a string that contains only digits * @return a string that contains only digits
*/ */
public static String removeNonDigitCharacters(final String toRemove) { @Nonnull
public static String removeNonDigitCharacters(@Nonnull final String toRemove) {
return toRemove.replaceAll("\\D+", ""); return toRemove.replaceAll("\\D+", "");
} }
@ -52,8 +56,8 @@ public final class Utils {
* @param numberWord string to be converted to a long * @param numberWord string to be converted to a long
* @return a long * @return a long
*/ */
public static long mixedNumberWordToLong(final String numberWord) throws NumberFormatException, public static long mixedNumberWordToLong(final String numberWord)
ParsingException { throws NumberFormatException, ParsingException {
String multiplier = ""; String multiplier = "";
try { try {
multiplier = Parser.matchGroup("[\\d]+([\\.,][\\d]+)?([KMBkmb])+", numberWord, 2); multiplier = Parser.matchGroup("[\\d]+([\\.,][\\d]+)?([KMBkmb])+", numberWord, 2);
@ -94,7 +98,7 @@ public final class Utils {
return null; return null;
} }
if (!url.isEmpty() && url.startsWith(HTTP)) { if (url.startsWith(HTTP)) {
return HTTPS + url.substring(HTTP.length()); return HTTPS + url.substring(HTTP.length());
} }
return url; return url;
@ -108,7 +112,9 @@ public final class Utils {
* @param parameterName the pattern that will be used to check the url * @param parameterName the pattern that will be used to check the url
* @return a string that contains the value of the query parameter or null if nothing was found * @return a string that contains the value of the query parameter or null if nothing was found
*/ */
public static String getQueryValue(final URL url, final String parameterName) { @Nullable
public static String getQueryValue(@Nonnull final URL url,
final String parameterName) {
final String urlQuery = url.getQuery(); final String urlQuery = url.getQuery();
if (urlQuery != null) { if (urlQuery != null) {
@ -144,11 +150,12 @@ public final class Utils {
* @param url the string to be converted to a URL-Object * @param url the string to be converted to a URL-Object
* @return a URL-Object containing the url * @return a URL-Object containing the url
*/ */
@Nonnull
public static URL stringToURL(final String url) throws MalformedURLException { public static URL stringToURL(final String url) throws MalformedURLException {
try { try {
return new URL(url); return new URL(url);
} catch (final MalformedURLException e) { } catch (final MalformedURLException e) {
// if no protocol is given try prepending "https://" // If no protocol is given try prepending "https://"
if (e.getMessage().equals("no protocol: " + url)) { if (e.getMessage().equals("no protocol: " + url)) {
return new URL(HTTPS + url); return new URL(HTTPS + url);
} }
@ -157,8 +164,8 @@ public final class Utils {
} }
} }
public static boolean isHTTP(final URL url) { public static boolean isHTTP(@Nonnull final URL url) {
// make sure its http or https // Make sure it's HTTP or HTTPS
final String protocol = url.getProtocol(); final String protocol = url.getProtocol();
if (!protocol.equals("http") && !protocol.equals("https")) { if (!protocol.equals("http") && !protocol.equals("https")) {
return false; return false;
@ -180,7 +187,7 @@ public final class Utils {
return url; return url;
} }
public static String removeUTF8BOM(final String s) { public static String removeUTF8BOM(@Nonnull final String s) {
String result = s; String result = s;
if (result.startsWith("\uFEFF")) { if (result.startsWith("\uFEFF")) {
result = result.substring(1); result = result.substring(1);
@ -198,7 +205,7 @@ public final class Utils {
} catch (final MalformedURLException e) { } catch (final MalformedURLException e) {
final String message = e.getMessage(); final String message = e.getMessage();
if (message.startsWith("unknown protocol: ")) { if (message.startsWith("unknown protocol: ")) {
// return just the protocol (e.g. vnd.youtube) // Return just the protocol (e.g. vnd.youtube)
return message.substring("unknown protocol: ".length()); return message.substring("unknown protocol: ".length());
} }
@ -214,17 +221,16 @@ public final class Utils {
* @return an url with no Google search redirects * @return an url with no Google search redirects
*/ */
public static String followGoogleRedirectIfNeeded(final String url) { public static String followGoogleRedirectIfNeeded(final String url) {
// if the url is a redirect from a Google search, extract the actual url // If the url is a redirect from a Google search, extract the actual URL
try { try {
final URL decoded = Utils.stringToURL(url); final URL decoded = Utils.stringToURL(url);
if (decoded.getHost().contains("google") && decoded.getPath().equals("/url")) { if (decoded.getHost().contains("google") && decoded.getPath().equals("/url")) {
return URLDecoder.decode(Parser.matchGroup1("&url=([^&]+)(?:&|$)", url), return URLDecoder.decode(Parser.matchGroup1("&url=([^&]+)(?:&|$)", url), UTF_8);
UTF_8);
} }
} catch (final Exception ignored) { } catch (final Exception ignored) {
} }
// url is not a google search redirect // URL is not a Google search redirect
return url; return url;
} }
@ -232,13 +238,29 @@ public final class Utils {
return str == null || str.isEmpty(); return str == null || str.isEmpty();
} }
// can be used for JsonArrays /**
* Checks if a collection is null or empty.
*
* <p>
* This method can be also used for {@link com.grack.nanojson.JsonArray JsonArray}s.
* </p>
* @param collection the collection on which check if it's null or empty
* @return whether the collection is null or empty
*/
public static boolean isNullOrEmpty(final Collection<?> collection) { public static boolean isNullOrEmpty(final Collection<?> collection) {
return collection == null || collection.isEmpty(); return collection == null || collection.isEmpty();
} }
// can be used for JsonObjects /**
public static boolean isNullOrEmpty(final Map<?, ?> map) { * Checks if a {@link Map map} is null or empty.
*
* <p>
* This method can be also used for {@link com.grack.nanojson.JsonObject JsonObject}s.
* </p>
* @param map the {@link Map map} on which check if it's null or empty
* @return whether the {@link Map map} is null or empty
*/
public static <K,V> boolean isNullOrEmpty(final Map<K, V> map) {
return map == null || map.isEmpty(); return map == null || map.isEmpty();
} }
@ -261,8 +283,9 @@ public final class Utils {
return true; return true;
} }
@Nonnull
public static String join(final CharSequence delimiter, public static String join(final CharSequence delimiter,
final Iterable<? extends CharSequence> elements) { @Nonnull final Iterable<? extends CharSequence> elements) {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
final Iterator<? extends CharSequence> iterator = elements.iterator(); final Iterator<? extends CharSequence> iterator = elements.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -274,11 +297,14 @@ public final class Utils {
return stringBuilder.toString(); return stringBuilder.toString();
} }
public static String join(final String delimiter, final String mapJoin, @Nonnull
final Map<? extends CharSequence, ? extends CharSequence> elements) { public static String join(
final String delimiter,
final String mapJoin,
@Nonnull final Map<? extends CharSequence, ? extends CharSequence> elements) {
final List<String> list = new LinkedList<>(); final List<String> list = new LinkedList<>();
for (final Map.Entry<? extends CharSequence, ? extends CharSequence> entry : elements for (final Map.Entry<? extends CharSequence, ? extends CharSequence> entry
.entrySet()) { : elements.entrySet()) {
list.add(entry.getKey() + mapJoin + entry.getValue()); list.add(entry.getKey() + mapJoin + entry.getValue());
} }
return join(delimiter, list); return join(delimiter, list);
@ -287,10 +313,109 @@ public final class Utils {
/** /**
* Concatenate all non-null, non-empty and strings which are not equal to <code>"null"</code>. * Concatenate all non-null, non-empty and strings which are not equal to <code>"null"</code>.
*/ */
@Nonnull
public static String nonEmptyAndNullJoin(final CharSequence delimiter, public static String nonEmptyAndNullJoin(final CharSequence delimiter,
final String[] elements) { final String[] elements) {
final List<String> list = new java.util.ArrayList<>(Arrays.asList(elements)); final List<String> list = new ArrayList<>(Arrays.asList(elements));
list.removeIf(s -> isNullOrEmpty(s) || s.equals("null")); list.removeIf(s -> isNullOrEmpty(s) || s.equals("null"));
return join(delimiter, list); return join(delimiter, list);
} }
/**
* Find the result of an array of string regular expressions inside an input on the first
* group ({@code 0}).
*
* @param input the input on which using the regular expressions
* @param regexes the string array of regular expressions
* @return the result
* @throws Parser.RegexException if none of the patterns match the input
*/
@Nonnull
public static String getStringResultFromRegexArray(@Nonnull final String input,
@Nonnull final String[] regexes)
throws Parser.RegexException {
return getStringResultFromRegexArray(input, regexes, 0);
}
/**
* Find the result of an array of {@link Pattern}s inside an input on the first group
* ({@code 0}).
*
* @param input the input on which using the regular expressions
* @param regexes the {@link Pattern} array
* @return the result
* @throws Parser.RegexException if none of the patterns match the input
*/
@Nonnull
public static String getStringResultFromRegexArray(@Nonnull final String input,
@Nonnull final Pattern[] regexes)
throws Parser.RegexException {
return getStringResultFromRegexArray(input, regexes, 0);
}
/**
* Find the result of an array of string regular expressions inside an input on a specific
* group.
*
* @param input the input on which using the regular expressions
* @param regexes the string array of regular expressions
* @param group the group to match
* @return the result
* @throws Parser.RegexException if none of the patterns match the input, or at least in the
* specified group
*/
@Nonnull
public static String getStringResultFromRegexArray(@Nonnull final String input,
@Nonnull final String[] regexes,
final int group)
throws Parser.RegexException {
String result = null;
for (final String regex : regexes) {
try {
result = Parser.matchGroup(regex, input, group);
if (result != null) {
// Continue if the result is null
break;
}
} catch (final Parser.RegexException ignored) {
}
}
if (result == null) {
throw new Parser.RegexException("No regex matched the input on group " + group);
}
return result;
}
/**
* Find the result of an array of {@link Pattern}s inside an input on a specific
* group.
*
* @param input the input on which using the regular expressions
* @param regexes the {@link Pattern} array
* @param group the group to match
* @return the result
* @throws Parser.RegexException if none of the patterns match the input, or at least in the
* specified group
*/
@Nonnull
public static String getStringResultFromRegexArray(@Nonnull final String input,
@Nonnull final Pattern[] regexes,
final int group)
throws Parser.RegexException {
String result = null;
for (final Pattern regex : regexes) {
try {
result = Parser.matchGroup(regex, input, group);
if (result != null) {
// Continue if the result is null
break;
}
} catch (final Parser.RegexException ignored) {
}
}
if (result == null) {
throw new Parser.RegexException("No regex matched the input on group " + group);
}
return result;
}
} }