* improved OpenSubtitles support
* refactoring
This commit is contained in:
parent
978cbe881d
commit
2fe8bd1306
@ -2,6 +2,8 @@
|
|||||||
package net.sourceforge.filebot.similarity;
|
package net.sourceforge.filebot.similarity;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.AbstractCollection;
|
import java.util.AbstractCollection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -239,21 +241,6 @@ public class SeriesNameMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String join(Object[] values, String separator) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
sb.append(values[i]);
|
|
||||||
|
|
||||||
if (i < values.length - 1) {
|
|
||||||
sb.append(separator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Map<File, String[]> mapNamesByFolder(File... files) {
|
private Map<File, String[]> mapNamesByFolder(File... files) {
|
||||||
Map<File, List<File>> filesByFolder = new LinkedHashMap<File, List<File>>();
|
Map<File, List<File>> filesByFolder = new LinkedHashMap<File, List<File>>();
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
package net.sourceforge.filebot.subtitle;
|
package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
package net.sourceforge.filebot.subtitle;
|
package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -49,7 +51,7 @@ public class SubRipReader extends SubtitleReader {
|
|||||||
lines.add(line);
|
lines.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SubtitleElement(t1, t2, join(lines.toArray(), "\n"));
|
return new SubtitleElement(t1, t2, join(lines, "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
package net.sourceforge.filebot.subtitle;
|
package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
package net.sourceforge.filebot.subtitle;
|
package net.sourceforge.filebot.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.InputMismatchException;
|
import java.util.InputMismatchException;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
@ -65,21 +65,6 @@ public abstract class SubtitleReader implements Iterator<SubtitleElement>, Close
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String join(Object[] values, String delimiter) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
sb.append(values[i]);
|
|
||||||
|
|
||||||
if (i < values.length - 1) {
|
|
||||||
sb.append(delimiter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
scanner.close();
|
scanner.close();
|
||||||
|
@ -8,6 +8,11 @@ public class MovieDescriptor extends SearchResult {
|
|||||||
private final int imdbId;
|
private final int imdbId;
|
||||||
|
|
||||||
|
|
||||||
|
public MovieDescriptor(String name, int imdbId) {
|
||||||
|
this(name, -1, imdbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor(String name, int year, int imdbId) {
|
public MovieDescriptor(String name, int year, int imdbId) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
@ -45,6 +50,9 @@ public class MovieDescriptor extends SearchResult {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
if (year < 0)
|
||||||
|
return name;
|
||||||
|
|
||||||
return String.format("%s (%d)", name, year);
|
return String.format("%s (%d)", name, year);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,9 @@ package net.sourceforge.filebot.web;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -56,8 +59,11 @@ public class OpenSubtitlesClient implements SubtitleProvider {
|
|||||||
// require login
|
// require login
|
||||||
login();
|
login();
|
||||||
|
|
||||||
|
// singleton array with or empty array
|
||||||
|
String[] languageFilter = languageName != null ? new String[] { getSubLanguageID(languageName) } : new String[0];
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<SubtitleDescriptor> subtitles = (List) xmlrpc.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageName);
|
List<SubtitleDescriptor> subtitles = (List) xmlrpc.searchSubtitles(((MovieDescriptor) searchResult).getImdbId(), languageFilter);
|
||||||
|
|
||||||
return subtitles;
|
return subtitles;
|
||||||
}
|
}
|
||||||
@ -99,4 +105,31 @@ public class OpenSubtitlesClient implements SubtitleProvider {
|
|||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubLanguageID by English language name
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> subLanguageCache = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
|
||||||
|
private String getSubLanguageID(String languageName) throws Exception {
|
||||||
|
// fetch languages if necessary
|
||||||
|
synchronized (subLanguageCache) {
|
||||||
|
if (subLanguageCache.isEmpty()) {
|
||||||
|
for (Entry<String, String> entry : xmlrpc.getSubLanguages().entrySet()) {
|
||||||
|
// map id by name
|
||||||
|
subLanguageCache.put(entry.getValue(), entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = subLanguageCache.get(languageName);
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
throw new IllegalArgumentException(String.format("SubLanguageID for '%s' not found", languageName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return getProperty(Property.SubFileName);
|
return getProperty(Property.MovieReleaseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,15 +3,14 @@ package net.sourceforge.filebot.web;
|
|||||||
|
|
||||||
|
|
||||||
import static java.util.Collections.*;
|
import static java.util.Collections.*;
|
||||||
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.InputMismatchException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -29,7 +28,7 @@ import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
|
|||||||
*/
|
*/
|
||||||
public class OpenSubtitlesXmlRpc {
|
public class OpenSubtitlesXmlRpc {
|
||||||
|
|
||||||
private static final String url = "http://www.opensubtitles.org/xml-rpc";
|
private final String url = "http://www.opensubtitles.org/xml-rpc";
|
||||||
|
|
||||||
private final String useragent;
|
private final String useragent;
|
||||||
|
|
||||||
@ -107,6 +106,112 @@ public class OpenSubtitlesXmlRpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, String> getServerInfo() throws XmlRpcFault {
|
||||||
|
return (Map<String, String>) invoke("ServerInfo", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String... sublanguageids) throws XmlRpcFault {
|
||||||
|
return searchSubtitles(null, null, imdbid, sublanguageids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(String moviehash, long moviebytesize, String... sublanguageids) throws XmlRpcFault {
|
||||||
|
return searchSubtitles(moviehash, moviebytesize, null, sublanguageids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(String moviehash, Long moviebytesize, Integer imdbid, String... sublanguageids) throws XmlRpcFault {
|
||||||
|
ParameterMap query = new ParameterMap(4);
|
||||||
|
|
||||||
|
// put parameters, but ignore null or empty values
|
||||||
|
query.put("moviehash", moviehash);
|
||||||
|
query.put("moviebytesize", moviebytesize);
|
||||||
|
query.put("imdbid", imdbid);
|
||||||
|
query.put("sublanguageid", join(sublanguageids, ","));
|
||||||
|
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, singletonList(query));
|
||||||
|
|
||||||
|
List<OpenSubtitlesSubtitleDescriptor> subtitles = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Map<String, String> subtitleData : response.get("data")) {
|
||||||
|
subtitles.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
|
||||||
|
}
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// if the response is an error message, generic types won't match
|
||||||
|
throw new XmlRpcException("Illegal response: " + response.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return subtitles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
|
||||||
|
|
||||||
|
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
||||||
|
|
||||||
|
for (Map<String, String> movie : response.get("data")) {
|
||||||
|
try {
|
||||||
|
// get non-aka title (aka titles are separated by Â)
|
||||||
|
Scanner titleScanner = new Scanner(movie.get("title")).useDelimiter("\u00C2");
|
||||||
|
|
||||||
|
movies.add(new MovieDescriptor(titleScanner.next(), Integer.parseInt(movie.get("id"))));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return movies;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Map<String, String> getSubLanguages() throws XmlRpcFault {
|
||||||
|
return getSubLanguages("en");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, String> getSubLanguages(String languageCode) throws XmlRpcFault {
|
||||||
|
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("GetSubLanguages", languageCode);
|
||||||
|
|
||||||
|
Map<String, String> subLanguageMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
for (Map<String, String> language : response.get("data")) {
|
||||||
|
subLanguageMap.put(language.get("SubLanguageID"), language.get("LanguageName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return subLanguageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean noOperation() {
|
||||||
|
try {
|
||||||
|
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
|
||||||
|
checkStatus(response.get("status"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
|
||||||
|
try {
|
||||||
|
XmlRpcClient rpc = new XmlRpcClient(url, false);
|
||||||
|
return rpc.invoke(method, arguments);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether status is OK or not
|
* Check whether status is OK or not
|
||||||
*
|
*
|
||||||
@ -126,108 +231,29 @@ public class OpenSubtitlesXmlRpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Object invoke(String method, Object... arguments) throws XmlRpcFault {
|
private static class ParameterMap extends HashMap<String, String> {
|
||||||
try {
|
|
||||||
XmlRpcClient rpc = new XmlRpcClient(url, false);
|
public ParameterMap(int initialCapacity) {
|
||||||
return rpc.invoke(method, arguments);
|
super(initialCapacity);
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* This simple function returns basic server info.
|
public String put(String key, String value) {
|
||||||
*/
|
if (value != null && !value.isEmpty()) {
|
||||||
@SuppressWarnings("unchecked")
|
return super.put(key, value);
|
||||||
public Map<String, String> getServerInfo() throws XmlRpcFault {
|
}
|
||||||
return (Map<String, String>) invoke("ServerInfo", token);
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
public String put(String key, Object value) {
|
||||||
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, String languageName) throws XmlRpcFault {
|
if (value != null) {
|
||||||
Map<String, String> query = new HashMap<String, String>(2);
|
return put(key, value.toString());
|
||||||
|
|
||||||
query.put("imdbid", String.format("%07d", imdbid));
|
|
||||||
query.put("sublanguageid", getSubLanguageID(languageName));
|
|
||||||
|
|
||||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, singletonList(query));
|
|
||||||
|
|
||||||
List<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (Map<String, String> subtitleData : response.get("data")) {
|
|
||||||
subs.add(new OpenSubtitlesSubtitleDescriptor(Property.asEnumMap(subtitleData)));
|
|
||||||
}
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
// if the response is an error message, generic types won't match
|
|
||||||
throw new XmlRpcException("Illegal response: " + response.toString(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return subs;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<String, String> languageMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public String getSubLanguageID(String languageName) throws XmlRpcFault {
|
|
||||||
synchronized (languageMap) {
|
|
||||||
if (languageMap.isEmpty()) {
|
|
||||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("GetSubLanguages", "en");
|
|
||||||
|
|
||||||
for (Map<String, String> language : response.get("data")) {
|
|
||||||
languageMap.put(language.get("LanguageName"), language.get("SubLanguageID"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return languageMap.get(languageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
|
|
||||||
|
|
||||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
|
|
||||||
|
|
||||||
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
|
||||||
Pattern moviePattern = Pattern.compile("(.+) \\((\\d{4})\\).*");
|
|
||||||
|
|
||||||
for (Map<String, String> movie : response.get("data")) {
|
|
||||||
try {
|
|
||||||
// get non-aka title (aka titles are separated by Â)
|
|
||||||
Scanner titleScanner = new Scanner(movie.get("title")).useDelimiter("\u00C2");
|
|
||||||
|
|
||||||
Matcher matcher = moviePattern.matcher(titleScanner.next().trim());
|
|
||||||
|
|
||||||
if (!matcher.matches())
|
|
||||||
throw new InputMismatchException("Cannot parse movie: " + movie);
|
|
||||||
|
|
||||||
String title = matcher.group(1);
|
|
||||||
String year = matcher.group(2);
|
|
||||||
|
|
||||||
movies.add(new MovieDescriptor(title, Integer.parseInt(year), Integer.parseInt(movie.get("id"))));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return movies;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean noOperation() {
|
|
||||||
try {
|
|
||||||
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
|
|
||||||
checkStatus(response.get("status"));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
source/net/sourceforge/tuned/StringUtilities.java
Normal file
52
source/net/sourceforge/tuned/StringUtilities.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
||||||
|
public final class StringUtilities {
|
||||||
|
|
||||||
|
public static String join(Object[] values, CharSequence delimiter) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
sb.append(values[i]);
|
||||||
|
|
||||||
|
if (i < values.length - 1) {
|
||||||
|
sb.append(delimiter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String join(Iterable<?> values, CharSequence separator) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
|
||||||
|
sb.append(iterator.next());
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
sb.append(separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isNullOrEmpty(String value) {
|
||||||
|
return value == null || value.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy constructor to prevent instantiation.
|
||||||
|
*/
|
||||||
|
private StringUtilities() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user