406 lines
13 KiB
Java
406 lines
13 KiB
Java
package org.gcube.datacatalogue.utillibrary.server.utils;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLDecoder;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.LinkedListMultimap;
|
|
import com.google.common.collect.Multimap;
|
|
|
|
|
|
/**
|
|
* The Class GenericUtils.
|
|
*
|
|
* @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it)
|
|
*
|
|
* Feb 9, 2021
|
|
*/
|
|
public final class GenericUtils {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(GenericUtils.class.getName());
|
|
|
|
/**
|
|
*
|
|
* Checks if provided array is non null and non empty .
|
|
*
|
|
* @param errorMessageTemplate a template for the exception message should
|
|
* the check fail. The message is formed by replacing each {@code %s}
|
|
* placeholder in the template with an argument. These are matched by
|
|
* position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
|
|
* Unmatched arguments will be appended to the formatted message in square
|
|
* braces. Unmatched placeholders will be left as-is.
|
|
* @param errorMessageArgs the arguments to be substituted into the message
|
|
* template. Arguments are converted to strings using
|
|
* {@link String#valueOf(Object)}.
|
|
* @throws IllegalArgumentException if {@code array} is empty or null
|
|
* @throws NullPointerException if the check fails and either
|
|
* {@code errorMessageTemplate} or {@code errorMessageArgs} is null (don't
|
|
* let this happen)
|
|
*
|
|
* @return a non-null non-empty array
|
|
*/
|
|
public static <T> T[] checkNotEmpty(@Nullable T[] array,
|
|
@Nullable String errorMessageTemplate,
|
|
@Nullable Object... errorMessageArgs) {
|
|
String formattedMessage = format(errorMessageTemplate, errorMessageArgs);
|
|
|
|
checkArgument(array != null, "%s -- Reason: Found null iterable.", formattedMessage);
|
|
if (array.length == 0) {
|
|
throw new IllegalArgumentException(formattedMessage + " -- Reason: Found empty array.");
|
|
}
|
|
return array;
|
|
}
|
|
|
|
/**
|
|
* Tolerance for probabilities
|
|
*/
|
|
public static final double TOLERANCE = 0.001;
|
|
|
|
/**
|
|
* Used for unparseable dates and other stuff.
|
|
*/
|
|
public static final String UNPARSEABLE = "unparseable:";
|
|
|
|
|
|
|
|
/**
|
|
* Tod Commons build properties path.
|
|
*/
|
|
public static final String BUILD_PROPERTIES_PATH = "tod.commons.build.properties";
|
|
|
|
|
|
|
|
/**
|
|
* Converts a language code to Java Locale. On null input returns
|
|
* {@link Locale#ROOT}
|
|
*
|
|
* Notice Java 7 introduced {@link Locale#forLanguageTag(String)}, but that
|
|
* method throws null pointer exception on null string, which unfortunately
|
|
* can happen quite often, so we use this method instead.
|
|
*
|
|
* @see Locale#forLanguageTag(String)
|
|
* @see #localeToLanguageTag(Locale) localeToLanguageTag(Locale) for the inverse operation
|
|
*/
|
|
public static Locale languageTagToLocale(@Nullable String languageTag) {
|
|
|
|
if (languageTag == null) {
|
|
LOG.warn("Found null locale, returning Locale.ROOT");
|
|
return Locale.ROOT;
|
|
}
|
|
return Locale.forLanguageTag(languageTag);
|
|
}
|
|
|
|
/**
|
|
* Converts a Java locale to a String. On null input returns the empty
|
|
* string (which corresponds to Java {@link Locale#ROOT})
|
|
*
|
|
* @see #languageTagToLocale(java.lang.String) #languageTagToLocale(java.lang.String) for the inverse operation
|
|
*/
|
|
public static String localeToLanguageTag(@Nullable Locale locale) {
|
|
if (locale == null) {
|
|
LOG.warn("Found null locale, returning empty string (which corresponds to Locale.ROOT)");
|
|
return "";
|
|
}
|
|
return locale.getLanguage();
|
|
}
|
|
|
|
/**
|
|
* Returns a new url with a slash added at the end of provided url. If
|
|
* provided url already ends with a slash it just returns it.
|
|
*
|
|
* @param url
|
|
*/
|
|
public static String addSlash(String url) {
|
|
checkNotNull(url, "invalid url!");
|
|
String trimmedUrl = url.trim();
|
|
if (trimmedUrl.endsWith("/")) {
|
|
return trimmedUrl;
|
|
} else {
|
|
return trimmedUrl + "/";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the provided url with all trailing slash at the end removed.
|
|
*/
|
|
public static String removeTrailingSlash(String url) {
|
|
checkNotNull(url, "invalid url!");
|
|
String tempUrl = url.trim();
|
|
while (tempUrl.endsWith("/")) {
|
|
tempUrl = tempUrl.substring(0, tempUrl.length() - 1);
|
|
}
|
|
return tempUrl;
|
|
}
|
|
|
|
/**
|
|
* Checks if provided URL is to be considered 'dirty'. Method may use some
|
|
* heuristics to detect oddities, like i.e. the string "null" inside the
|
|
* url.
|
|
*
|
|
* @deprecated Moved to
|
|
* {@link eu.trentorise.opendata.commons.validation.Preconditions#checkNotDirtyUrl(java.lang.String, java.lang.Object) } @
|
|
* param url the URL to check
|
|
* @param prependedErrorMessage
|
|
* the exception message to use if the check fails; will be
|
|
* converted to a string using String.valueOf(Object) and
|
|
* prepended to more specific error messages.
|
|
*
|
|
* @throws IllegalArgumentException
|
|
* if provided URL fails validation.
|
|
*
|
|
* @return the non-dirty URL that was validated
|
|
*
|
|
*/
|
|
public static String checkNotDirtyUrl(@Nullable String url, @Nullable Object prependedErrorMessage) {
|
|
checkNotEmpty(url, prependedErrorMessage);
|
|
|
|
if (url.equalsIgnoreCase("null")) {
|
|
throw new IllegalArgumentException(String.valueOf(prependedErrorMessage)
|
|
+ " -- Reason: Found URL with string \"" + url + "\" as content!");
|
|
}
|
|
|
|
// todo delete this is a too radical checker...
|
|
if (url.toLowerCase()
|
|
.endsWith("/null")) {
|
|
throw new IllegalArgumentException(
|
|
String.valueOf(prependedErrorMessage) + " -- Reason: Found URL ending with /\"null\": " + url);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Checks if provided string is non null and non empty.
|
|
*
|
|
* @throws IllegalArgumentException
|
|
* if provided string fails validation
|
|
*
|
|
* @return the non-empty string that was validated
|
|
*/
|
|
public static String checkNotEmpty(String string, @Nullable Object prependedErrorMessage) {
|
|
checkArgument(string != null, "%s -- Reason: Found null string.", prependedErrorMessage);
|
|
if (string.length() == 0) {
|
|
throw new IllegalArgumentException(
|
|
String.valueOf(prependedErrorMessage) + " -- Reason: Found empty string.");
|
|
}
|
|
return string;
|
|
}
|
|
|
|
/**
|
|
* Returns true if provided string is non null and non empty .
|
|
*/
|
|
public static boolean isNotEmpty(@Nullable String string) {
|
|
return string != null && string.length() != 0;
|
|
}
|
|
|
|
/**
|
|
* Parses an URL having a numeric ID after the provided prefix, i.e. for
|
|
* prefix 'http://entitypedia.org/concepts/' and url
|
|
* http://entitypedia.org/concepts/14324 returns 14324
|
|
*
|
|
* @deprecated this shouldn't be here.....
|
|
*
|
|
* @throws IllegalArgumentException
|
|
* on invalid URL
|
|
*/
|
|
public static long parseNumericalId(String prefix, String url) {
|
|
|
|
checkNotNull(prefix, "prefix can't be null!");
|
|
checkNotEmpty(url, "Invalid url!");
|
|
|
|
String s;
|
|
if (prefix.length() > 0) {
|
|
int pos = url.indexOf(prefix);
|
|
if (pos != 0) {
|
|
throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url);
|
|
}
|
|
s = url.substring(prefix.length());
|
|
} else {
|
|
s = url;
|
|
}
|
|
try {
|
|
return Long.parseLong(s);
|
|
} catch (NumberFormatException ex) {
|
|
throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url, ex);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Substitutes each {@code %s} in {@code template} with an argument. These
|
|
* are matched by position: the first {@code %s} gets {@code args[0]}, etc.
|
|
* If there are more arguments than placeholders, the unmatched arguments
|
|
* will be appended to the end of the formatted message in square braces.
|
|
* <br/>
|
|
* <br/>
|
|
* (Copied from Guava's
|
|
* {@link com.google.common.base.Preconditions#format(java.lang.String, java.lang.Object...) }
|
|
* )
|
|
*
|
|
* @param template
|
|
* a non-null string containing 0 or more {@code %s}
|
|
* placeholders.
|
|
* @param args
|
|
* the arguments to be substituted into the message template.
|
|
* Arguments are converted to strings using
|
|
* {@link String#valueOf(Object)}. Arguments can be null.
|
|
*
|
|
* @since 1.1
|
|
*/
|
|
public static String format(String template, @Nullable Object... args) {
|
|
if (template == null) {
|
|
LOG.warn("Found null template while formatting, converting it to \"null\"");
|
|
}
|
|
template = String.valueOf(template); // null -> "null"
|
|
|
|
// start substituting the arguments into the '%s' placeholders
|
|
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
|
|
int templateStart = 0;
|
|
int i = 0;
|
|
while (i < args.length) {
|
|
int placeholderStart = template.indexOf("%s", templateStart);
|
|
if (placeholderStart == -1) {
|
|
break;
|
|
}
|
|
builder.append(template.substring(templateStart, placeholderStart));
|
|
builder.append(args[i++]);
|
|
templateStart = placeholderStart + 2;
|
|
}
|
|
builder.append(template.substring(templateStart));
|
|
|
|
// if we run out of placeholders, append the extra args in square braces
|
|
if (i < args.length) {
|
|
builder.append(" [");
|
|
builder.append(args[i++]);
|
|
while (i < args.length) {
|
|
builder.append(", ");
|
|
builder.append(args[i++]);
|
|
}
|
|
builder.append(']');
|
|
}
|
|
|
|
return builder.toString();
|
|
}
|
|
|
|
/**
|
|
* Extracts parameters from given url. Works also with multiple params with
|
|
* same name.
|
|
*
|
|
* @return map of param name : [args]
|
|
* @throws IllegalArgumentException
|
|
* @since 1.1
|
|
*/
|
|
public static Multimap<String, String> parseUrlParams(String url) {
|
|
URL u;
|
|
try {
|
|
u = new URL(url);
|
|
} catch (MalformedURLException ex) {
|
|
throw new IllegalArgumentException("Ill formed url!", ex);
|
|
}
|
|
Multimap<String, String> queryPairs = LinkedListMultimap.create();
|
|
final String[] pairs = u.getQuery()
|
|
.split("&");
|
|
|
|
try {
|
|
for (String pair : pairs) {
|
|
final int idx = pair.indexOf("=");
|
|
|
|
final String key;
|
|
|
|
key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
|
|
|
|
final String value = idx > 0 && pair.length() > idx + 1
|
|
? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : "";
|
|
queryPairs.put(key, value);
|
|
}
|
|
return queryPairs;
|
|
} catch (UnsupportedEncodingException ex) {
|
|
throw new IllegalArgumentException("Encoding not supported!", ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a copy of provided map with {@code newObject} set under the given
|
|
* key. If key already exists replaces its value in the returned object. Not
|
|
* efficient, but sometimes we need it.
|
|
*
|
|
* @param newObject
|
|
* Must be an immutable object.
|
|
* @since 1.1
|
|
*/
|
|
public static <K, V> ImmutableMap<K, V> putKey(Map<K, V> map, K key, V newObject) {
|
|
ImmutableMap.Builder<K, V> mapb = ImmutableMap.builder();
|
|
|
|
for (K k : map.keySet()) {
|
|
if (!k.equals(key)) {
|
|
mapb.put(k, map.get(k));
|
|
}
|
|
}
|
|
mapb.put(key, newObject);
|
|
return mapb.build();
|
|
}
|
|
|
|
// private static final FastDateFormat ISO_YEAR_FORMAT = FastDateFormat.getInstance("yyyy");
|
|
//
|
|
// private static final FastDateFormat ISO_YEAR_MONTH_FORMAT = FastDateFormat.getInstance("yyyy-MM");
|
|
//
|
|
// /**
|
|
// * @deprecated experimental, try to avoid using it for now
|
|
// * @since 1.1
|
|
// * @throws TodParseException
|
|
// */
|
|
// // todo this parser is horrible
|
|
// public static Date parseIso8061(String s) {
|
|
//
|
|
// try {
|
|
// return DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// try {
|
|
// return DateFormatUtils.ISO_DATETIME_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// try {
|
|
// return DateFormatUtils.ISO_DATE_TIME_ZONE_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// try {
|
|
// return DateFormatUtils.ISO_DATE_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// try {
|
|
// return ISO_YEAR_MONTH_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// try {
|
|
// return ISO_YEAR_FORMAT.parse(s);
|
|
// } catch (ParseException ex) {
|
|
// }
|
|
//
|
|
// // todo week dates, ordinal dates
|
|
//
|
|
// throw new TodParseException("Couldn't parse date as ISO8061. Unparseable date was:" + s);
|
|
// }
|
|
//
|
|
|
|
}
|