/* * Copyright 2023-2024 the original author or authors. * * 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 rife.render; import rife.tools.Localization; import rife.tools.StringUtils; import java.io.IOException; import java.io.StringReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.text.Normalizer; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * Collection of utility-type methods commonly used by the renderers. * * @author Erik C. Thauvin * @since 1.0 */ public final class RenderUtils { /** * The encoding property. */ public static final String ENCODING_PROPERTY = "encoding"; /** * ISO 8601 date formatter. * * @see ISO 8601 */ public static final DateTimeFormatter ISO_8601_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd").withLocale(Localization.getLocale()); /** * ISO 8601 date and time formatter. * * @see ISO 8601 */ public static final DateTimeFormatter ISO_8601_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXXXX").withLocale(Localization.getLocale()); /** * ISO 8601 time formatter. * * @see ISO 8601 */ public static final DateTimeFormatter ISO_8601_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss").withLocale(Localization.getLocale()); /** * ISO 8601 Year formatter. * * @see ISO 8601 */ static public final DateTimeFormatter ISO_8601_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy").withLocale(Localization.getLocale()); /** * RFC 2822 date and time formatter. * * @see RFC 2822 */ public static final DateTimeFormatter RFC_2822_FORMATTER = DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss zzz").withLocale(Localization.getLocale()); private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"; private static final Logger LOGGER = Logger.getLogger(RenderUtils.class.getName()); private RenderUtils() { // no-op } /** * Abbreviates a {@code String} to the given length using a replacement marker. * * @param src the source {@code String} * @param max the maximum length of the resulting {@code String} * @param marker the {@code String} used as a replacement marker * @return the abbreviated {@code String} */ public static String abbreviate(String src, int max, String marker) { if (src == null || src.isBlank() || marker == null) { return src; } var len = src.length(); if (len <= max || max < 0) { return src; } return src.substring(0, max - marker.length()) + marker; } /** * Returns the Swatch Internet (.beat) Time for the give date-time. * * @param zonedDateTime the date and time * @return the .beat time. (eg.: {@code @248}) */ public static String beatTime(ZonedDateTime zonedDateTime) { var zdt = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC+01:00")); var beats = (int) ((zdt.getSecond() + (zdt.getMinute() * 60) + (zdt.getHour() * 3600)) / 86.4); return String.format("@%03d", beats); } /** *
Encodes the source {@code String} to the specified encoding.
* *The supported encodings are:
* *Returns the last 4 digits a credit card number.
* *Shortens a URL using is.gid.
* *The URL {@code String} must be a valid http or https URL.
* *Based on isgd-shorten
* * @param url the source URL * @return the short URL */ public static String shortenUrl(String url) { if (url == null || url.isBlank() || !url.matches("^[Hh][Tt][Tt][Pp][Ss]?://\\w.*")) { return url; } return fetchUrl(String.format("https://is.gd/create.php?format=simple&url=%s", StringUtils.encodeUrl(url.trim())), url); } /** * Swaps the case of a String. * * @param src the {@code String} to swap the case of * @return the modified {@code String} or null */ @SuppressWarnings("PMD.AvoidReassigningLoopVariables") public static String swapCase(String src) { if (src == null || src.isBlank()) { return src; } int offset = 0; var len = src.length(); var buff = new int[len]; for (var i = 0; i < len; ) { int newCodePoint; var curCodePoint = src.codePointAt(i); if (Character.isUpperCase(curCodePoint) || Character.isTitleCase(curCodePoint)) { newCodePoint = Character.toLowerCase(curCodePoint); } else if (Character.isLowerCase(curCodePoint)) { newCodePoint = Character.toUpperCase(curCodePoint); } else { newCodePoint = curCodePoint; } buff[offset++] = newCodePoint; i += Character.charCount(newCodePoint); } return new String(buff, 0, offset); } /** *Returns the formatted server uptime.
* *The default Properties are:
* ** year=\ year\u29F5u0020 * years=\ years\u29F5u0020 * month=\ month\u29F5u0020 * months=\ months\u29F5u0020 * week=\ week\u29F5u0020 * weeks=\ weeks\u29F5u0020 * day=\ day\u29F5u0020 * days=\ days\u29F5u0020 * hour=\ hour\u29F5u0020 * hours=\ hours\u29F5u0020 * minute=\ minute * minutes=\ minutes ** * @param uptime the uptime in milliseconds * @param properties the format properties * @return the formatted uptime */ @SuppressWarnings("UnnecessaryUnicodeEscape") public static String uptime(long uptime, Properties properties) { var sb = new StringBuilder(); var days = TimeUnit.MILLISECONDS.toDays(uptime); var years = days / 365; days %= 365; var months = days / 30; days %= 30; var weeks = days / 7; days %= 7; var hours = TimeUnit.MILLISECONDS.toHours(uptime) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(uptime)); var minutes = TimeUnit.MILLISECONDS.toMinutes(uptime) - TimeUnit.HOURS.toMinutes( TimeUnit.MILLISECONDS.toHours(uptime)); if (years > 0) { sb.append(years).append(plural(years, properties.getProperty("year", " year "), properties.getProperty("years", " years "))); } if (months > 0) { sb.append(months).append(plural(months, properties.getProperty("month", " month "), properties.getProperty("months", " months "))); } if (weeks > 0) { sb.append(weeks).append(plural(weeks, properties.getProperty("week", " week "), properties.getProperty("weeks", " weeks "))); } if (days > 0) { sb.append(days).append(plural(days, properties.getProperty("day", " day "), properties.getProperty("days", " days "))); } if (hours > 0) { sb.append(hours).append(plural(hours, properties.getProperty("hour", " hour "), properties.getProperty("hours", " hours "))); } sb.append(minutes).append(plural(minutes, properties.getProperty("minute", " minute"), properties.getProperty("minutes", " minutes"))); return sb.toString(); } /** * Validates a credit card number using the Luhn algorithm. * * @param cc the credit card number * @return {@code true} if the credit card number is valid */ public static boolean validateCreditCard(String cc) { try { var len = cc.length(); if (len >= 8 && len <= 19) { // Luhn algorithm var sum = 0; boolean second = false; int digit; char c; for (int i = len - 1; i >= 0; i--) { c = cc.charAt(i); if (c >= '0' && c <= '9') { digit = cc.charAt(i) - '0'; if (second) { digit = digit * 2; } sum += digit / 10; sum += digit % 10; second = !second; } } if (sum % 10 == 0) { return true; } } } catch (NumberFormatException ignored) { // do nothing } return false; } }