All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.util.Utils Maven / Gradle / Ivy

There is a newer version: 4.5.1
Show newest version
/*
 * Copyright OmniFaces
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.omnifaces.util;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableMap;
import static java.util.logging.Level.FINEST;
import static java.util.regex.Pattern.quote;
import static org.omnifaces.util.FacesLocal.getRequestDomainURL;
import static org.omnifaces.util.Reflection.toClassOrNull;
import static org.omnifaces.util.Servlets.getSubmittedFileName;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Consumer;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

import javax.faces.application.Resource;
import javax.faces.context.FacesContext;
import javax.servlet.http.Part;

/**
 * 

* Collection of general utility methods that do not fit in one of the more specific classes. * *

This class is not listed in showcase! Should I use it?

*

* This class is indeed intented for internal usage only. We won't add methods here on user request. We only add methods * here once we encounter non-DRY code in OmniFaces codebase. The methods may be renamed/changed without notice. *

* We don't stop you from using it if you think you find it useful, but you'd really better pick e.g. Google Guava or * perhaps the good 'ol Apache Commons. This Utils class exists because OmniFaces intends to be free of 3rd party * dependencies. * * @author Arjan Tijms * @author Bauke Scholtz */ public final class Utils { // Constants ------------------------------------------------------------------------------------------------------ private static final Logger logger = Logger.getLogger(Utils.class.getName()); private static final int DEFAULT_STREAM_BUFFER_SIZE = 10240; private static final String PATTERN_RFC1123_DATE = "EEE, dd MMM yyyy HH:mm:ss zzz"; private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT"); private static final Pattern PATTERN_ISO639_ISO3166_LOCALE = Pattern.compile("[a-z]{2,3}(_[A-Z]{2})?"); private static final int UNICODE_3_BYTES = 0xfff; private static final int UNICODE_2_BYTES = 0xff; private static final int UNICODE_1_BYTE = 0xf; private static final int UNICODE_END_PRINTABLE_ASCII = 0x7f; private static final int UNICODE_BEGIN_PRINTABLE_ASCII = 0x20; private static final Map, Object> PRIMITIVE_DEFAULTS = collectPrimitiveDefaults(); private static final Map, Class> PRIMITIVE_TYPES = collectPrimitiveTypes(); private static final String ERROR_UNSUPPORTED_ENCODING = "UTF-8 is apparently not supported on this platform."; private static final String ERROR_UNSUPPORTED_DATE = "Only java.util.Date, java.util.Calendar and java.time.Temporal are supported."; private static final String ERROR_UNSUPPORTED_TIMEZONE = "Only java.lang.String, java.util.TimeZone and java.time.ZoneId are supported."; // Constructors --------------------------------------------------------------------------------------------------- private Utils() { // Hide constructor. } // Initialization ------------------------------------------------------------------------------------------------- private static Map, Object> collectPrimitiveDefaults() { Map, Object> primitiveDefaults = new HashMap<>(); primitiveDefaults.put(boolean.class, false); primitiveDefaults.put(byte.class, (byte) 0); primitiveDefaults.put(short.class, (short) 0); primitiveDefaults.put(char.class, (char) 0); primitiveDefaults.put(int.class, 0); primitiveDefaults.put(long.class, (long) 0); primitiveDefaults.put(float.class, (float) 0); primitiveDefaults.put(double.class, (double) 0); return unmodifiableMap(primitiveDefaults); } private static Map, Class> collectPrimitiveTypes() { Map, Class> primitiveTypes = new HashMap<>(); primitiveTypes.put(Boolean.class, boolean.class); primitiveTypes.put(Byte.class, byte.class); primitiveTypes.put(Short.class, short.class); primitiveTypes.put(Character.class, char.class); primitiveTypes.put(Integer.class, int.class); primitiveTypes.put(Long.class, long.class); primitiveTypes.put(Float.class, float.class); primitiveTypes.put(Double.class, double.class); return unmodifiableMap(primitiveTypes); } // Lang ----------------------------------------------------------------------------------------------------------- /** * Returns true if the given string is null or is empty. * @param string The string to be checked on emptiness. * @return true if the given string is null or is empty. */ public static boolean isEmpty(String string) { return string == null || string.isEmpty(); } /** * Returns true if the given collection is null or is empty. * @param collection The collection to be checked on emptiness. * @return true if the given collection is null or is empty. */ public static boolean isEmpty(Collection collection) { return collection == null || collection.isEmpty(); } /** * Returns true if the given map is null or is empty. * @param map The map to be checked on emptiness. * @return true if the given map is null or is empty. */ public static boolean isEmpty(Map map) { return map == null || map.isEmpty(); } /** * Returns true if the given part is null or is empty. * @param part The part to be checked on emptiness. * @return true if the given part is null or is empty. * @since 2.6 */ public static boolean isEmpty(Part part) { return part == null || (isEmpty(getSubmittedFileName(part)) && part.getSize() <= 0); } /** * Returns true if the given object is null or an empty array or has an empty toString() result. * @param value The value to be checked on emptiness. * @return true if the given object is null or an empty array or has an empty toString() result. */ public static boolean isEmpty(Object value) { if (value == null) { return true; } else if (value instanceof String) { return isEmpty((String) value); } else if (value instanceof Collection) { return isEmpty((Collection) value); } else if (value instanceof Map) { return isEmpty((Map) value); } else if (value instanceof Part) { return isEmpty((Part) value); } else if (value.getClass().isArray()) { return Array.getLength(value) == 0; } else { return value.toString() == null || value.toString().isEmpty(); } } /** * Returns true if at least one value is empty. * @param values the values to be checked on emptiness * @return true if any value is empty and false if no values are empty * @since 1.8 */ public static boolean isAnyEmpty(Object... values) { for (Object value : values) { if (isEmpty(value)) { return true; } } return false; } /** * Returns true if the given string is null or is empty or contains whitespace only. In addition to * {@link #isEmpty(String)}, this thus also returns true when string.trim().isEmpty() * returns true. * @param string The string to be checked on blankness. * @return True if the given string is null or is empty or contains whitespace only. * @since 1.5 */ public static boolean isBlank(String string) { return isEmpty(string) || string.trim().isEmpty(); } /** * Returns true if the given string is parseable as a number. I.e. it is not null, nor blank and contains solely * digits. I.e., it won't throw a NumberFormatException when parsing as Long. * @param string The string to be checked as number. * @return true if the given string is parseable as a number. * @since 1.5. */ public static boolean isNumber(String string) { try { // Performance tests taught that this approach is in general faster than regex or char-by-char checking. return Long.valueOf(string) != null; } catch (Exception ignore) { logger.log(FINEST, "Ignoring thrown exception; the sole intent is to return false instead.", ignore); return false; } } /** * Returns true if the given string is parseable as a decimal. I.e. it is not null, nor blank and contains solely * digits. I.e., it won't throw a NumberFormatException when parsing as Double. * @param string The string to be checked as decimal. * @return true if the given string is parseable as a decimal. * @since 1.5. */ public static boolean isDecimal(String string) { try { // Performance tests taught that this approach is in general faster than regex or char-by-char checking. return Double.valueOf(string) != null; } catch (Exception ignore) { logger.log(FINEST, "Ignoring thrown exception; the sole intent is to return false instead.", ignore); return false; } } /** * Returns the first non-null object of the argument list, or null if there is no such * element. * @param The generic object type. * @param objects The argument list of objects to be tested for non-null. * @return The first non-null object of the argument list, or null if there is no such * element. */ @SafeVarargs public static T coalesce(T... objects) { for (T object : objects) { if (object != null) { return object; } } return null; } /** * Returns true if the given object equals one of the given objects. * @param The generic object type. * @param object The object to be checked if it equals one of the given objects. * @param objects The argument list of objects to be tested for equality. * @return true if the given object equals one of the given objects. */ @SafeVarargs public static boolean isOneOf(T object, T... objects) { for (Object other : objects) { if (Objects.equals(object, other)) { return true; } } return false; } /** * Returns true if the given string starts with one of the given prefixes. * @param string The object to be checked if it starts with one of the given prefixes. * @param prefixes The argument list of prefixes to be checked * @return true if the given string starts with one of the given prefixes. * @since 1.4 */ public static boolean startsWithOneOf(String string, String... prefixes) { for (String prefix : prefixes) { if (string.startsWith(prefix)) { return true; } } return false; } /** * Returns true if the given string ends with one of the given suffixes. * @param string The object to be checked if it ends with one of the given suffixes. * @param suffixes The argument list of suffixes to be checked * @return true if the given string ends with one of the given suffixes. * @since 3.1 */ public static boolean endsWithOneOf(String string, String... suffixes) { for (String suffix : suffixes) { if (string.endsWith(suffix)) { return true; } } return false; } /** * Returns true if an instance of the given class could also be an instance of one of the given classes. * @param cls The class to be checked if it could also be an instance of one of the given classes. * @param classes The argument list of classes to be tested. * @return true if the given class could also be an instance of one of the given classes. * @since 2.0 */ public static boolean isOneInstanceOf(Class cls, Class... classes) { for (Class other : classes) { if (cls == null ? other == null : other.isAssignableFrom(cls)) { return true; } } return false; } /** * Returns true if the given class has at least one of the given annotations. * @param cls The class to be checked if it has at least one of the given annotations. * @param annotations The argument list of annotations to be tested on the given class. * @return true if the given clazz would be an instance of one of the given clazzes. * @since 2.0 */ @SafeVarargs public static boolean isOneAnnotationPresent(Class cls, Class... annotations) { for (Class annotation : annotations) { if (cls.isAnnotationPresent(annotation)) { return true; } } return false; } /** * Returns the default value of the given class, covering primitives. * E.g. if given class is int.class, then it will return 0. Autoboxing will do the rest. * Non-primitives and void.class will return null. * @param cls The class to obtain the default value for. * @return The default value of the given class, covering primitives. * @since 2.4 */ public static Object getDefaultValue(Class cls) { return cls.isPrimitive() ? PRIMITIVE_DEFAULTS.get(cls) : null; } /** * Returns the primitive type of the given class, if any. * E.g. if given class is Integer.class, then it will return int.class. * Non-primitives and void.class will return null. * @param cls The class to obtain the primitive type for. * @return The primitive type of the given class, if any. * @since 2.7.2 */ public static Class getPrimitiveType(Class cls) { return cls.isPrimitive() ? cls : PRIMITIVE_TYPES.get(cls); } // I/O ------------------------------------------------------------------------------------------------------------ /** * Stream the given input to the given output via NIO {@link Channels} and a directly allocated NIO * {@link ByteBuffer}. Both the input and output streams will implicitly be closed after streaming, * regardless of whether an exception is been thrown or not. * @param input The input stream. * @param output The output stream. * @return The length of the written bytes. * @throws IOException When an I/O error occurs. */ public static long stream(InputStream input, OutputStream output) throws IOException { try (ReadableByteChannel inputChannel = Channels.newChannel(input); WritableByteChannel outputChannel = Channels.newChannel(output)) { ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE); long size = 0; while (inputChannel.read(buffer) != -1) { buffer.flip(); size += outputChannel.write(buffer); buffer.clear(); } return size; } } /** * Stream a specified range of the given file to the given output via NIO {@link Channels} and a directly allocated * NIO {@link ByteBuffer}. The output stream will only implicitly be closed after streaming when the specified range * represents the whole file, regardless of whether an exception is been thrown or not. * @param file The file. * @param output The output stream. * @param start The start position (offset). * @param length The (intented) length of written bytes. * @return The (actual) length of the written bytes. This may be smaller when the given length is too large. * @throws IOException When an I/O error occurs. * @since 2.2 */ public static long stream(File file, OutputStream output, long start, long length) throws IOException { if (start == 0 && length >= file.length()) { return stream(new FileInputStream(file), output); } try (FileChannel fileChannel = (FileChannel) Files.newByteChannel(file.toPath(), StandardOpenOption.READ)) { WritableByteChannel outputChannel = Channels.newChannel(output); ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_STREAM_BUFFER_SIZE); long size = 0; while (fileChannel.read(buffer, start + size) != -1) { buffer.flip(); if (size + buffer.limit() > length) { buffer.limit((int) (length - size)); } size += outputChannel.write(buffer); if (size >= length) { break; } buffer.clear(); } return size; } } /** * Read the given input stream into a byte array. The given input stream will implicitly be closed after streaming, * regardless of whether an exception is been thrown or not. * @param input The input stream. * @return The input stream as a byte array. * @throws IOException When an I/O error occurs. * @since 2.0 */ public static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); stream(input, output); return output.toByteArray(); } /** * Check if the given resource is not null and then close it, whereby any caught {@link IOException} * is been returned instead of thrown, so that the caller can if necessary handle (log) or just ignore it without * the need to put another try-catch. * @param resource The closeable resource to be closed. * @return The caught {@link IOException}, or null if none is been thrown. */ public static IOException close(Closeable resource) { if (resource != null) { try { resource.close(); } catch (IOException e) { return e; } } return null; } /** * Returns true if the given object is guaranteed to be serializable. This does that by checking * whether {@link ObjectOutputStream#writeObject(Object)} on the given object doesn't throw an exception * rather than checking if the given object is an instance of {@link Serializable}. * @param object The object to be tested. * @return true if the given object is guaranteed to be serializable. * @since 2.4 */ public static boolean isSerializable(Object object) { try (ObjectOutputStream output = new ObjectOutputStream(new NullOutputStream())) { output.writeObject(object); return true; } catch (IOException ignore) { logger.log(FINEST, "Ignoring thrown exception; the sole intent is to return false instead.", ignore); return false; } } private static final class NullOutputStream extends OutputStream { @Override public void write(int b) throws IOException { // NOOP. } @Override public void write(byte[] b) throws IOException { // NOOP. } @Override public void write(byte[] b, int off, int len) throws IOException { // NOOP. } } // Collections ---------------------------------------------------------------------------------------------------- /** * Creates an unmodifiable set based on the given values. If one of the values is an instance of an array or a * collection, then each of its values will also be merged into the set. Nested arrays or collections will result * in a {@link ClassCastException}. * @param The expected set element type. * @param values The values to create an unmodifiable set for. * @return An unmodifiable set based on the given values. * @throws ClassCastException When one of the values or one of the arrays or collections is of wrong type. * @since 1.1 */ @SuppressWarnings("unchecked") public static Set unmodifiableSet(Object... values) { Set set = new HashSet<>(); for (Object value : values) { if (value instanceof Object[]) { for (Object item : (Object[]) value) { set.add((E) item); } } else if (value instanceof Collection) { for (Object item : (Collection) value) { set.add((E) item); } } else { set.add((E) value); } } return Collections.unmodifiableSet(set); } /** * Converts an iterable into a list. *

* This method makes NO guarantee to whether changes to the source iterable are * reflected in the returned list or not. For instance if the given iterable * already is a list, it's returned directly. * * @param The generic iterable element type. * @param iterable The iterable to be converted. * @return The list representation of the given iterable, possibly the same instance as that iterable. * @since 1.5 */ public static List iterableToList(Iterable iterable) { if (iterable instanceof List) { return (List) iterable; } else if (iterable instanceof Collection) { return new ArrayList<>((Collection) iterable); } else { List list = new ArrayList<>(); Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { list.add(iterator.next()); } return list; } } /** * Converts comma separated values in a string into a list with those values. *

* E.g. a string with "foo, bar, kaz" will be converted into a List * with values: *

    *
  • "foo"
  • *
  • "bar"
  • *
  • "kaz"
  • *
* * Note that whitespace will be stripped. Empty entries are not supported. This method defaults to * using a comma (",") as delimiter. See {@link Utils#csvToList(String, String)} for when * a different delimiter is needed. * * @param values string with comma separated values * @return a list with all values encountered in the values argument, can be the empty list. * @since 1.4 */ public static List csvToList(String values) { return csvToList(values, ","); } /** * Converts comma separated values in a string into a list with those values. *

* E.g. a string with "foo, bar, kaz" will be converted into a List * with values: *

    *
  • "foo"
  • *
  • "bar"
  • *
  • "kaz"
  • *
* * Note that whitespace will be stripped. Empty entries are not supported. * * @param values string with comma separated values * @param delimiter the delimiter used to separate the actual values in the values parameter. * @return a list with all values encountered in the values argument, can be the empty list. * @since 1.4 */ public static List csvToList(String values, String delimiter) { if (isEmpty(values)) { return emptyList(); } List list = new ArrayList<>(); for (String value : values.split(quote(delimiter))) { String trimmedValue = value.trim(); if (!isEmpty(trimmedValue)) { list.add(trimmedValue); } } return list; } /** * Returns a new map that contains the reverse of the given map. *

* The reverse of a map means that every value X becomes a key X' with as corresponding * value Y' the key Y that was originally associated with the value X. * * @param The generic map key/value type. * @param source the map that is to be reversed * @return the reverse of the given map */ public static Map reverse(Map source) { Map target = new HashMap<>(); for (Entry entry : source.entrySet()) { target.put(entry.getValue(), entry.getKey()); } return target; } /** * Checks if the given collection contains an object with the given class name. * * @param objects collection of objects to check * @param className name of the class to be checked for * @return true if the collection contains at least one object with the given class name, false otherwise * @since 1.6 */ public static boolean containsByClassName(Collection objects, String className) { Class cls = toClassOrNull(className); for (Object object : objects) { if (object.getClass() == cls) { return true; } } return false; } /** * Returns a stream of given object. Supported types are: *

    *
  • {@link Iterable} *
  • {@link Map} (returns a stream of entryset) *
  • int[] *
  • long[] *
  • double[] *
  • Object[] *
  • {@link Stream} *
* Anything else is returned as a single-element stream. Null is returned as an empty stream. * * @param The expected stream type. * @param object Any object to get a stream for. * @return A stream of given object. * @throws ClassCastException When T is of wrong type. * @since 3.0 */ @SuppressWarnings("unchecked") public static Stream stream(Object object) { if (object == null) { return Stream.empty(); } if (object instanceof Iterable) { return (Stream) StreamSupport.stream(((Iterable) object).spliterator(), false); } else if (object instanceof Map) { return (Stream) ((Map) object).entrySet().stream(); } else if (object instanceof int[]) { return (Stream) Arrays.stream((int[]) object).boxed(); } else if (object instanceof long[]) { return (Stream) Arrays.stream((long[]) object).boxed(); } else if (object instanceof double[]) { return (Stream) Arrays.stream((double[]) object).boxed(); } else if (object instanceof Object[]) { return (Stream) Arrays.stream((Object[]) object); } else if (object instanceof Stream) { return (Stream) object; } else { return (Stream) Stream.of(object); } } /** * Returns a stream of given iterable. * @param The generic iterable element type. * @param iterable Any iterable to get a stream for. * @return A stream of given iterable. * @since 3.0 */ public static Stream stream(Iterable iterable) { return iterable == null ? Stream.empty() : StreamSupport.stream(iterable.spliterator(), false); } /** * Returns a stream of given map. * @param The generic map key type. * @param The generic map value type. * @param map Any map to get a stream for. * @return A stream of given map. * @since 3.0 */ public static Stream> stream(Map map) { return map == null ? Stream.empty() : map.entrySet().stream(); } /** * Returns a stream of given array. * @param The generic array item type. * @param array Any array to get a stream for. * @return A stream of given array. * @since 3.0 */ public static Stream stream(T[] array) { return array == null ? Stream.empty() : Arrays.stream(array); } /** * Performs an action for each element of given object which is streamed using {@link Utils#stream(Object)}. * @param object Any streamable object. * @param action A non-interfering action to perform on each element. * @since 3.0 */ public static void forEach(Object object, Consumer action) { stream(object).forEach(action); } // Dates ---------------------------------------------------------------------------------------------------------- /** * Formats the given {@link Date} to a string in RFC1123 format. This format is used in HTTP headers and in * JavaScript Date constructor. * @param date The Date to be formatted to a string in RFC1123 format. * @return The formatted string. * @since 1.2 */ public static String formatRFC1123(Date date) { SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US); sdf.setTimeZone(TIMEZONE_GMT); return sdf.format(date); } /** * Parses the given string in RFC1123 format to a {@link Date} object. * @param string The string in RFC1123 format to be parsed to a Date object. * @return The parsed Date. * @throws ParseException When the given string is not in RFC1123 format. * @since 1.2 */ public static Date parseRFC1123(String string) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US); return sdf.parse(string); } /** * Obtain {@link ZoneId} from D. * When D is {@code null} or {@link Date}, then return {@link ZoneId#systemDefault()}. * When D is {@link Calendar}, then return {@link TimeZone#toZoneId()} of {@link Calendar#getTimeZone()} * When D is {@link Temporal} and supports {@link ChronoField#OFFSET_SECONDS}, then return {@link ZoneId#from(java.time.temporal.TemporalAccessor)}. * When D is {@link Temporal} and supports {@link ChronoField#CLOCK_HOUR_OF_DAY}, then return {@link ZoneId#systemDefault()}. * When D is {@link Temporal} and supports neither, then return {@link ZoneOffset#UTC}. * @param The date type, can be {@code null}, {@link Date}, {@link Calendar} or {@link Temporal}. * @throws IllegalArgumentException When date is not {@link Date}, {@link Calendar} or {@link Temporal}. * @since 3.6 */ public static ZoneId getZoneId(D date) { if (date == null || date instanceof Date) { return ZoneId.systemDefault(); } else if (date instanceof Calendar) { return ((Calendar) date).getTimeZone().toZoneId(); } else if (date instanceof Temporal) { Temporal temporal = (Temporal) date; if (temporal.isSupported(ChronoField.OFFSET_SECONDS)) { return ZoneId.from((Temporal) date); } else if (temporal.isSupported(ChronoField.CLOCK_HOUR_OF_DAY)) { return ZoneId.systemDefault(); } else { return ZoneOffset.UTC; } } else { throw new IllegalArgumentException(ERROR_UNSUPPORTED_DATE); } } /** * Convert Z to {@link ZoneId}. * When Z is {@code null}, then return {@link ZoneId#systemDefault()}. * When Z is {@link ZoneId}, then return it. * When Z is {@link TimeZone}, then return {@link TimeZone#toZoneId()}. * When Z is {@link String}, then return {@link ZoneId#of(String)}. * @param The timezone type, can be {@code null}, {@link String}, {@link TimeZone} or {@link ZoneId}. * @throws IllegalArgumentException When Z is not {@code null}, {@link ZoneId}, {@link TimeZone} or {@link String}. * @since 3.6 */ public static ZoneId toZoneId(Z timezone) { if (timezone == null) { return ZoneId.systemDefault(); } else if (timezone instanceof ZoneId) { return (ZoneId) timezone; } else if (timezone instanceof TimeZone) { return ((TimeZone) timezone).toZoneId(); } else if (timezone instanceof String) { return ZoneId.of((String) timezone); } else { throw new IllegalArgumentException(ERROR_UNSUPPORTED_TIMEZONE); } } /** * Convert D to {@link ZonedDateTime}. * This method is guaranteed repeatable when combined with {@link #fromZonedDateTime(ZonedDateTime, Class)}. * @param The date type, can be {@link Date}, {@link Calendar} or {@link Temporal}. * @throws IllegalArgumentException When date is not {@link Date}, {@link Calendar} or {@link Temporal}. * @since 3.6 */ public static ZonedDateTime toZonedDateTime(D date) { if (date == null) { return null; } ZoneId zone = getZoneId(date); if (date instanceof java.util.Date) { return ZonedDateTime.ofInstant(Instant.ofEpochMilli(((java.util.Date) date).getTime()), zone); } else if (date instanceof Calendar) { return ((Calendar) date).toInstant().atZone(zone); } else if (date instanceof Instant) { return ((Instant) date).atZone(zone); } else if (date instanceof ZonedDateTime) { return (ZonedDateTime) date; } else if (date instanceof OffsetDateTime) { return ((OffsetDateTime) date).toZonedDateTime(); } else if (date instanceof LocalDateTime) { return ((LocalDateTime) date).atZone(zone); } else if (date instanceof LocalDate) { return ((LocalDate) date).atStartOfDay(zone); } else if (date instanceof OffsetTime) { return ((OffsetTime) date).atDate(LocalDate.now()).toZonedDateTime(); } else if (date instanceof LocalTime) { return (((LocalTime) date).atDate(LocalDate.now())).atZone(zone); } else if (date instanceof Temporal) { return fromTemporalToZonedDateTime((Temporal) date, zone); } else { throw new IllegalArgumentException(ERROR_UNSUPPORTED_DATE); } } private static ZonedDateTime fromTemporalToZonedDateTime(Temporal temporal, ZoneId zone) { if (temporal.isSupported(ChronoField.INSTANT_SECONDS)) { long epoch = temporal.getLong(ChronoField.INSTANT_SECONDS); long nano = temporal.getLong(ChronoField.NANO_OF_SECOND); return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epoch, nano), zone); } int year = temporal.isSupported(ChronoField.YEAR) ? temporal.get(ChronoField.YEAR) : 1; int month = temporal.isSupported(ChronoField.MONTH_OF_YEAR) ? temporal.get(ChronoField.MONTH_OF_YEAR) : 1; int day = temporal.isSupported(ChronoField.DAY_OF_MONTH) ? temporal.get(ChronoField.DAY_OF_MONTH) : 1; int hour = temporal.isSupported(ChronoField.HOUR_OF_DAY) ? temporal.get(ChronoField.HOUR_OF_DAY) : 0; int minute = temporal.isSupported(ChronoField.MINUTE_OF_HOUR) ? temporal.get(ChronoField.MINUTE_OF_HOUR) : 0; int second = temporal.isSupported(ChronoField.SECOND_OF_MINUTE) ? temporal.get(ChronoField.SECOND_OF_MINUTE) : 0; int nano = temporal.isSupported(ChronoField.NANO_OF_SECOND) ? temporal.get(ChronoField.NANO_OF_SECOND) : 0; return ZonedDateTime.of(year, month, day, hour, minute, second, nano, zone); } /** * Convert {@link ZonedDateTime} to D. * This method is guaranteed repeatable when combined with {@link #toZonedDateTime(Object)}. * @param The date type, can be {@link Date}, {@link Calendar} or {@link Temporal} or any of its subclasses. * @throws NullPointerException When type is null. * @throws IllegalArgumentException When type is not {@link Date}, {@link Calendar} or {@link Temporal} or any of its subclasses. * @since 3.6 */ @SuppressWarnings("unchecked") public static D fromZonedDateTime(ZonedDateTime zonedDateTime, Class type) { if (zonedDateTime == null) { return null; } else if (java.sql.Timestamp.class.isAssignableFrom(type)) { return (D) new java.sql.Timestamp(zonedDateTime.toInstant().toEpochMilli()); } else if (java.sql.Date.class.isAssignableFrom(type)) { return (D) new java.sql.Date(zonedDateTime.toInstant().toEpochMilli()); } else if (java.sql.Time.class.isAssignableFrom(type)) { return (D) new java.sql.Time(zonedDateTime.toInstant().toEpochMilli()); } else if (java.util.Date.class.isAssignableFrom(type)) { return (D) java.util.Date.from(zonedDateTime.toInstant()); } else if (Calendar.class.isAssignableFrom(type)) { Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(zonedDateTime.getZone())); calendar.setTime(java.util.Date.from(zonedDateTime.toInstant())); return (D) calendar; } else if (type == Instant.class) { return (D) zonedDateTime.toInstant(); } else if (type == ZonedDateTime.class) { return (D) zonedDateTime; } else if (type == OffsetDateTime.class) { return (D) zonedDateTime.toOffsetDateTime(); } else if (type == LocalDateTime.class) { return (D) zonedDateTime.toLocalDateTime(); } else if (type == LocalDate.class) { return (D) zonedDateTime.toLocalDate(); } else if (type == OffsetTime.class) { return (D) zonedDateTime.toOffsetDateTime().toOffsetTime(); } else if (type == LocalTime.class) { return (D) zonedDateTime.toLocalDateTime().toLocalTime(); } else if (Temporal.class.isAssignableFrom(type)) { return fromZonedDateTimeToTemporal(zonedDateTime, type); } else { throw new IllegalArgumentException(ERROR_UNSUPPORTED_DATE); } } @SuppressWarnings("unchecked") private static T fromZonedDateTimeToTemporal(ZonedDateTime zonedDateTime, Class type) { // Basically finds public static method in T which takes 1 argument of Temporal.class and returns T. // This matches Temporal#from(TemporalAccessor) methods of all known Temporal subclasses listed above. // There might be custom implementations supporting this as well although this is undocumented. // We just try our best :) Optional converter = stream(type.getMethods()).filter(method -> Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 1 && method.getParameterTypes()[0].isAssignableFrom(Temporal.class) && type.isAssignableFrom(method.getReturnType()) ).findFirst(); try { if (converter.isPresent()) { return (T) converter.get().invoke(null, zonedDateTime); } else { throw new NoSuchMethodException(); } } catch (Exception e) { throw new IllegalArgumentException(ERROR_UNSUPPORTED_DATE, e); } } // Locale --------------------------------------------------------------------------------------------------------- /** * Parses the given object representing the locale to a {@link Locale} object. * If it is null, then return null. * Else if it is already an instance of Locale, then just return it. * Else if it is in pattern ISO 639 alpha-2/3, optionally followed by "_" and ISO 3166-1 alpha-2 country code, then * split the language/country and construct a new Locale with it. * Else parse it via {@link Locale#forLanguageTag(String)} and return it. * @param locale The object representing the locale. * @return The parsed Locale. * @since 2.3 */ public static Locale parseLocale(Object locale) { if (locale == null) { return null; } else if (locale instanceof Locale) { return (Locale) locale; } else { String localeString = locale.toString(); if (PATTERN_ISO639_ISO3166_LOCALE.matcher(localeString).matches()) { String[] languageAndCountry = localeString.split("_"); String language = languageAndCountry[0]; String country = languageAndCountry.length > 1 ? languageAndCountry[1] : ""; return new Locale(language, country); } else { return Locale.forLanguageTag(localeString); } } } // Encoding/decoding ---------------------------------------------------------------------------------------------- /** * Serialize the given string to the short possible unique URL-safe representation. The current implementation will * decode the given string with UTF-8 and then compress it with ZLIB using "best compression" algorithm and then * Base64-encode the resulting bytes without the = padding, whereafter the Base64 characters * + and / are been replaced by respectively - and _ to make it * URL-safe (so that no platform-sensitive URL-encoding needs to be done when used in URLs). * @param string The string to be serialized. * @return The serialized URL-safe string, or null when the given string is itself null. * @since 1.2 */ public static String serializeURLSafe(String string) { if (string == null) { return null; } try { InputStream raw = new ByteArrayInputStream(string.getBytes(UTF_8)); ByteArrayOutputStream deflated = new ByteArrayOutputStream(); stream(raw, new DeflaterOutputStream(deflated, new Deflater(Deflater.BEST_COMPRESSION))); return Base64.getUrlEncoder().withoutPadding().encodeToString(deflated.toByteArray()); } catch (IOException e) { // This will occur when ZLIB and/or UTF-8 are not supported, but this is not to be expected these days. throw new UnsupportedOperationException(e); } } /** * Unserialize the given serialized URL-safe string. This does the inverse of {@link #serializeURLSafe(String)}. * @param string The serialized URL-safe string to be unserialized. * @return The unserialized string, or null when the given string is by itself null. * @throws IllegalArgumentException When the given serialized URL-safe string is not in valid format as returned by * {@link #serializeURLSafe(String)}. * @since 1.2 */ public static String unserializeURLSafe(String string) { if (string == null) { return null; } try { InputStream deflated = new ByteArrayInputStream(Base64.getUrlDecoder().decode(string)); return new String(toByteArray(new InflaterInputStream(deflated)), UTF_8); } catch (UnsupportedEncodingException e) { // This will occur when UTF-8 is not supported, but this is not to be expected these days. throw new UnsupportedOperationException(e); } catch (Exception e) { // This will occur when the string is not in valid Base64 or ZLIB format. throw new IllegalArgumentException(e); } } /** * URL-encode the given string using UTF-8. * @param string The string to be URL-encoded using UTF-8. * @return The given string, URL-encoded using UTF-8, or null if null was given. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @since 1.4 */ public static String encodeURL(String string) { if (string == null) { return null; } try { return URLEncoder.encode(string, UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new UnsupportedOperationException(ERROR_UNSUPPORTED_ENCODING, e); } } /** * URL-decode the given string using UTF-8. * @param string The string to be URL-decode using UTF-8. * @return The given string, URL-decode using UTF-8, or null if null was given. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @since 1.4 */ public static String decodeURL(String string) { if (string == null) { return null; } try { return URLDecoder.decode(string, UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new UnsupportedOperationException(ERROR_UNSUPPORTED_ENCODING, e); } } /** * URI-encode the given string using UTF-8. URIs (paths and filenames) have different encoding rules as compared to * URL query string parameters. {@link URLEncoder} is actually only for www (HTML) form based query string parameter * values (as used when a webbrowser submits a HTML form). URI encoding has a lot in common with URL encoding, but * the space has to be %20 and some chars doesn't necessarily need to be encoded. * @param string The string to be URI-encoded using UTF-8. * @return The given string, URI-encoded using UTF-8, or null if null was given. * @throws UnsupportedOperationException When this platform does not support UTF-8. * @since 2.4 */ public static String encodeURI(String string) { if (string == null) { return null; } return encodeURL(string) .replace("+", "%20") .replace("%21", "!") .replace("%27", "'") .replace("%28", "(") .replace("%29", ")") .replace("%7E", "~"); } /** * Format given URL with given query string. If given URL is empty, assume / as URL. If given query * string is empty, return URL right away. If given URL contains a ?, prepend query string with * &, else with ?. Finally append query string to URL and return it. * @param url URL to be formatted with given query string. * @param queryString Query string to be appended to given URL. * @return Formatted URL with query string. * @since 3.0 */ public static String formatURLWithQueryString(String url, String queryString) { String normalizedURL = url.isEmpty() ? "/" : url; if (isEmpty(queryString)) { return normalizedURL; } return normalizedURL + (normalizedURL.contains("?") ? "&" : "?") + queryString; } /** * Returns {@code true} when given URL contains a query string parameter with given name. * @param url URL to be checked. * @param parameterName Parameter name to be checked. * @return {@code true} when given URL contains a query string parameter with given name. * @since 3.14.5 */ public static boolean containsQueryStringParameter(String url, String parameterName) { String[] pathAndQueryString = url.split(quote("?")); if (pathAndQueryString.length > 1) { String[] parameters = pathAndQueryString[1].split(quote("&")); for (String parameter : parameters) { String[] nameAndValue = parameter.split(quote("=")); if (nameAndValue.length > 0 && parameterName.equals(decodeURL(nameAndValue[0]))) { return true; } } } return false; } // Escaping/unescaping -------------------------------------------------------------------------------------------- /** * Escapes the given string according the JavaScript code rules. This escapes among others the special characters, * the whitespace, the quotes and the unicode characters. Useful whenever you want to use a Java string variable as * a JavaScript string variable. * @param string The string to be escaped according the JavaScript code rules. * @param escapeSingleQuote Whether to escape single quotes as well or not. Set to false if you want * to escape it for usage in JSON. * @return The escaped string according the JavaScript code rules. */ public static String escapeJS(String string, boolean escapeSingleQuote) { if (string == null) { return null; } StringBuilder builder = new StringBuilder(string.length()); for (char c : string.toCharArray()) { if (c > UNICODE_3_BYTES) { builder.append("\\u").append(Integer.toHexString(c)); } else if (c > UNICODE_2_BYTES) { builder.append("\\u0").append(Integer.toHexString(c)); } else if (c > UNICODE_END_PRINTABLE_ASCII) { builder.append("\\u00").append(Integer.toHexString(c)); } else if (c < UNICODE_BEGIN_PRINTABLE_ASCII) { escapeJSControlCharacter(builder, c); } else { escapeJSASCIICharacter(builder, c, escapeSingleQuote); } } return builder.toString(); } private static void escapeJSControlCharacter(StringBuilder builder, char c) { switch (c) { case '\b': builder.append('\\').append('b'); break; case '\n': builder.append('\\').append('n'); break; case '\t': builder.append('\\').append('t'); break; case '\f': builder.append('\\').append('f'); break; case '\r': builder.append('\\').append('r'); break; default: if (c > UNICODE_1_BYTE) { builder.append("\\u00").append(Integer.toHexString(c)); } else { builder.append("\\u000").append(Integer.toHexString(c)); } break; } } private static void escapeJSASCIICharacter(StringBuilder builder, char c, boolean escapeSingleQuote) { switch (c) { case '\'': if (escapeSingleQuote) { builder.append('\\'); } builder.append('\''); break; case '"': builder.append('\\').append('"'); break; case '\\': builder.append('\\').append('\\'); break; case '/': builder.append('\\').append('/'); break; default: builder.append(c); break; } } // Resources ------------------------------------------------------------------------------------------------------ /** * Returns connection to given resource, taking into account possibly buggy component libraries. * @param context The involved faces context. * @param resource The resource to obtain connection from. * @return Connection to given resource. * @since 3.6 */ public static URLConnection openConnection(FacesContext context, Resource resource) { try { return resource.getURL().openConnection(); } catch (Exception richFacesDoesNotSupportThis) { logger.log(FINEST, "Ignoring thrown exception; this can only be caused by a buggy component library.", richFacesDoesNotSupportThis); try { return new URL(getRequestDomainURL(context) + resource.getRequestPath()).openConnection(); } catch (IOException ignore) { logger.log(FINEST, "Ignoring thrown exception; cannot handle it at this point, it would be thrown during getInputStream() anyway.", ignore); return null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy