diff --git a/jerrat-boost-tiny/pom.xml b/jerrat-boost-tiny/pom.xml index 64b194a..57b80ac 100644 --- a/jerrat-boost-tiny/pom.xml +++ b/jerrat-boost-tiny/pom.xml @@ -13,6 +13,12 @@ jerrat-boost-tiny 1.0.0-SNAPSHOT + + 3.2.5 + 2.10.3 + + + com.google.guava @@ -73,6 +79,41 @@ 3.0.1-b08 - + + + cglib + cglib + ${cglib.version} + + + + com.esotericsoftware + reflectasm + 1.11.0 + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + com.fasterxml.jackson.module + jackson-module-parameter-names + ${jackson.version} + + + - \ No newline at end of file + \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Dates.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Dates.java index f0c6ca7..acb61e4 100644 --- a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Dates.java +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Dates.java @@ -12,45 +12,395 @@ * limitations under the License. */ + package io.github.javajerrat.boost.basetools.datetime; import java.text.ParseException; -import java.time.Instant; +import java.util.Calendar; import java.util.Date; -import java.util.Optional; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import lombok.AllArgsConstructor; +import lombok.ToString; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.time.FastDateFormat; +import org.jetbrains.annotations.NotNull; /** - * @see DateUtils - * @see DateFormatUtils - * + * @author Frapples + * @date 2019/11/13 */ -public class Dates { +public class Dates extends DateUtils { + /** + * Returns the nearest future time that satisfies the given point. + * @param hour hour + * @param min min + * @param seconds seconds + * @param ms ms + */ + public static Date nextNearDay(int hour, int min, int seconds, int ms) { + return nextNearDay(TimeZone.getDefault(), hour, min, seconds, ms); + } - public static Optional parseDate(String data, String parsePatterns) { - try { - Date date = DateUtils.parseDate(data, parsePatterns); - return Optional.ofNullable(date); - } catch (ParseException e) { - return Optional.empty(); + /** + * Returns the nearest future time that satisfies the given point. + * @param timeZone timeZone + * @param hour hour + * @param min min + * @param seconds seconds + * @param ms ms + */ + public static Date nextNearDay(TimeZone timeZone, int hour, int min, int seconds, int ms) { + Date now = new Date(); + Date nextDate = Dates.setTime(now, timeZone, hour, min, seconds, ms); + if (nextDate.compareTo(now) <= 0) { + nextDate = DateUtils.addDays(nextDate, 1); } + return nextDate; + + } + + /** + * Returns the nearest future time that satisfies the given point. + * @param day day + * @param hour hour + * @param min min + * @param seconds seconds + * @param ms ms + */ + public static Date nextNearMonth(int day, int hour, int min, int seconds, int ms) { + return nextNearMonth(TimeZone.getDefault(), day, hour, min, seconds, ms); + } + + /** + * Returns the nearest future time that satisfies the given point. + * @param timeZone timeZone + * @param day day + * @param hour hour + * @param min min + * @param seconds seconds + * @param ms ms + */ + public static Date nextNearMonth(TimeZone timeZone, int day, int hour, int min, int seconds, int ms) { + Date now = new Date(); + Dates.DateTuple dateTuple = Dates.getDateTuple(now, timeZone); + Date nextDate = Dates.setDate(now, timeZone, dateTuple.year, dateTuple.month, day); + nextDate = Dates.setTime(nextDate, timeZone, hour, min, seconds, ms); + if (nextDate.compareTo(now) < 0) { + nextDate = DateUtils.addMonths(nextDate, 1); + } + return nextDate; } + /** + * Calculate time delta in milliseconds. + * @param date1 subtrahend date + * @param date2 minuend date + * @return time delta + */ + public static long interval(Date date1, Date date2) { + return date1.getTime() - date2.getTime(); + } + + /** + * Convert the date object to a more readable string. + * This function is designed for debugging. + * @param date date + * @return display string + */ + public static String toDisplayString(@NotNull Date date) { + Objects.requireNonNull(date); + return DateFormatUtils.format(date, DateFormats.YYYY_MM_DD_HH_MM_SS_SSS) + " " + displayTimeZone(TimeZone.getDefault()); + } + + private static String displayTimeZone(TimeZone timeZone) { + // Thanks for https://mkyong.com/java/java-display-list-of-timezone-with-gmt/ + long hours = TimeUnit.MILLISECONDS.toHours(timeZone.getRawOffset()); + long minutes = TimeUnit.MILLISECONDS.toMinutes(timeZone.getRawOffset()) + - TimeUnit.HOURS.toMinutes(hours); + // avoid -4:-30 issue + minutes = Math.abs(minutes); + if (hours > 0) { + return String.format("(GMT+%d:%02d) %s", hours, minutes, timeZone.getID()); + } else { + return String.format("(GMT%d:%02d) %s", hours, minutes, timeZone.getID()); + } + } + + /** + * Similar to {@link DateUtils#parseDate(String, String...)}, but the difference is that Time zone can be customized. + * @param str the date to parse, not null + * @param timeZone timeZone + * @param parsePattern the date format patterns to use, see SimpleDateFormat, not null + * @return the parsed date + * @throws ParseException if none of the date patterns were suitable (or there were none) + */ + public static Date parseDate(String str, TimeZone timeZone, String parsePattern) throws ParseException { + FastDateFormat fastDateFormat = FastDateFormat.getInstance(parsePattern, timeZone); + return fastDateFormat.parse(str); + } + + /** + *

Formats a date/time into a specific pattern.

+ * + * @param date the date to format, not null + * @param pattern the pattern to use to format the date, not null + * @return the formatted date + */ public static String format(Date date, String pattern) { return DateFormatUtils.format(date, pattern); } - public static Date unixEpoch2Date(long epoch) { - return new Date(epoch * 1000L); + /** + *

Formats a date/time into a specific pattern in a time zone.

+ * + * @param date the date to format, not null + * @param pattern the pattern to use to format the date, not null + * @param timeZone the time zone to use, may be null + * @return the formatted date + */ + public static String format(Date date, String pattern, TimeZone timeZone) { + return DateFormatUtils.format(date, pattern, timeZone); + } + + /** + *

Formats a date/time into a specific pattern in a time zone and locale.

+ * + * @param date the date to format, not null + * @param pattern the pattern to use to format the date, not null, not null + * @param timeZone the time zone to use, may be null + * @param locale the locale to use, may be null + * @return the formatted date + */ + public static String format(Date date, String pattern, TimeZone timeZone, Locale locale) { + return DateFormatUtils.format(date, pattern, timeZone, locale); + } + + /** + *

Truncates a date, leaving the field specified as the most + * significant field.

+ * + *

For example, if you had the date-time of 28 Mar 2002 + * 13:45:01.231, if you passed with HOUR, it would return 28 Mar + * 2002 13:00:00.000. If this was passed with MONTH, it would + * return 1 Mar 2002 0:00:00.000.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different truncated date, not null + * @throws IllegalArgumentException if the date is null + * @throws ArithmeticException if the year is over 280 million + */ + public static Date truncate(final Date date, TimeZone timeZone, final int field) { + // Thanks for https://stackoverflow.com/questions/28266988/dateutilstruncate-to-work-in-utc-timezone + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + return DateUtils.truncate(calendar, field).getTime(); + } + + /** + *

Rounds a date, leaving the field specified as the most + * significant field.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different rounded date, not null + * @throws IllegalArgumentException if the date is null + * @throws ArithmeticException if the year is over 280 million + */ + public static Date round(final Date date, TimeZone timeZone, final int field) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + return DateUtils.round(calendar, field).getTime(); + } + + /** + *

Gets a date ceiling, leaving the field specified as the most + * significant field.

+ * + * @param date the date to work with, not null + * @param field the field from {@code Calendar} or SEMI_MONTH + * @return the different ceil date, not null + * @throws IllegalArgumentException if the date is null + * @throws ArithmeticException if the year is over 280 million + */ + public static Date ceiling(final Date date, TimeZone timeZone, final int field) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + return DateUtils.ceiling(calendar, field).getTime(); + } + + @AllArgsConstructor + @ToString + public static class DateTuple { + public final int year; + public final int month; + public final int day; + public final int hour; + public final int minute; + public final int second; + public final int millisecond; + } + + /** + * Gets a tuple containing year, month, day, hour, minute, second and millisecond. + * @param date date + * @return a tuple + */ + public static DateTuple getDateTuple(Date date) { + return getDateTuple(date, TimeZone.getDefault()); } - public static long unixEpoch() { - return Instant.now().getEpochSecond(); + /** + * Gets a tuple containing year, month, day, hour, minute, second and millisecond. + * @param date date + * @return a tuple + */ + public static DateTuple getDateTuple(Date date, TimeZone timeZone) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + return new DateTuple( + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, + calendar.get(Calendar.DAY_OF_MONTH), + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + calendar.get(Calendar.SECOND), + calendar.get(Calendar.MILLISECOND)); } + + /** + * Set the hours, minutes, and seconds of the date + * @param date date + * @return A new date + */ + public static Date setTime(Date date, TimeZone timeZone, int hour, int min, int seconds) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, seconds); + return calendar.getTime(); + } + + /** + * Set the hours, minutes, seconds and milliseconds of the date + * @param date date + * @return A new date + */ + public static Date setTime(Date date, int hour, int min, int seconds, int ms) { + return setTime(date, TimeZone.getDefault(), hour, min, seconds, ms); + } + + /** + * Set the hours, minutes, seconds and milliseconds of the date + * @param date date + * @return A new date + */ + public static Date setTime(Date date, TimeZone timeZone, int hour, int min, int seconds, int ms) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, seconds); + calendar.set(Calendar.MILLISECOND, ms); + return calendar.getTime(); + } + + /** + * Set the hours, minutes and seconds of the date + * @param date date + * @return A new date + */ + public static Date setTime(Date date, int hour, int min, int seconds) { + return setTime(date, TimeZone.getDefault(), hour, min, seconds); + } + + /** + * Set the year, month and day of the date + * @param date date + * @return A new date + */ + public static Date setDate(Date date, int year, int month, int day) { + return setDate(date, TimeZone.getDefault(), year, month, day); + } + + /** + * Set the year, month and day of the date + * @param date date + * @return A new date + */ + public static Date setDate(Date date, TimeZone timeZone, int year, int month, int day) { + Calendar calendar = Calendar.getInstance(timeZone); + calendar.setTime(date); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + return calendar.getTime(); + } + + /** + * Whether the given two dates are the same year and month. + * @param date1 date + * @param date2 date + * @return True if they are the same, false otherwise + */ + public static boolean isSameMonth(Date date1, Date date2) { + return isSameMonth(date1, date2, TimeZone.getDefault()); + } + + /** + * Whether the given two dates are the same year and month. + * @param timeZone timeZone + * @param date1 date + * @param date2 date + * @return True if they are the same, false otherwise + */ + public static boolean isSameMonth(Date date1, Date date2, TimeZone timeZone) { + DateTuple dateTuple1 = getDateTuple(date1, timeZone); + DateTuple dateTuple2 = getDateTuple(date2, timeZone); + return dateTuple1.year == dateTuple2.year && dateTuple1.month == dateTuple2.month; + } + + /** + * @return Returns the current timestamp + */ public static long timestamp() { return System.currentTimeMillis(); } + + /** + * This function is only used in special cases and fixes some historical errors. + * A date is parsed in a wrong time zone given by the fromTimeZone parameter, + * and corrected to the correct time zone given by the toTimeZone parameter. + * @param date date + * @param fromTimeZone fromTimeZone + * @param toTimeZone toTimeZone + * @return Corrected time zone + */ + public static Date correctTimeZone(Date date, TimeZone fromTimeZone, TimeZone toTimeZone) { + // Thanks for https://stackoverflow.com/questions/2891361/how-to-set-time-zone-of-a-java-util-date + long fromTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, fromTimeZone); + long toTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, toTimeZone); + + return new Date(date.getTime() + (toTimeZoneOffset - fromTimeZoneOffset)); + } + + /** + * Calculates the offset of the timeZone from UTC, factoring in any + * additional offset due to the time zone being in daylight savings time as of + * the given date. + * @param date date + * @param timeZone timeZone + * @return the offset + */ + private static long getTimeZoneUTCAndDSTOffset(Date date, TimeZone timeZone) { + return timeZone.getRawOffset() + + (timeZone.inDaylightTime(date) ? timeZone.getDSTSavings() : 0); + } } diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Zones.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Zones.java new file mode 100644 index 0000000..12c83e8 --- /dev/null +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/datetime/Zones.java @@ -0,0 +1,29 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.basetools.datetime; + +import java.time.ZoneId; +import java.util.TimeZone; + +public class Zones { + + public static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC"); + + public static final ZoneId UTC_ID = ZoneId.of("UTC"); + + public static final TimeZone ASIA_SHANGHAI_ZONE = TimeZone.getTimeZone("Asia/Shanghai"); + + public static final ZoneId ASIA_SHANGHAI_ID = ZoneId.of("Asia/Shanghai"); +} diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/MoreFiles.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/MoreFiles.java new file mode 100644 index 0000000..178ce73 --- /dev/null +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/MoreFiles.java @@ -0,0 +1,195 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.basetools.file; + +import com.google.common.annotations.Beta; +import com.google.common.collect.AbstractIterator; +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.DirectoryFileFilter; +import org.apache.commons.io.filefilter.FalseFileFilter; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.filefilter.IOFileFilter; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * @author Frapples + * @date 2019/11/18 + * + * @see java.nio.file.Files + * @see org.apache.commons.io.FileUtils + * @see com.google.common.io.Files + */ +public class MoreFiles { + + /** + * Finds files within a given directory (and optionally its + * subdirectories). All files found are filtered by an IOFileFilter. + *

+ * The resulting collection includes the starting directory and + * any subdirectories that match the directory filter. + *

+ * + * @param directory the directory to search in + * @param fileFilter filter to apply when finding files. + * @param dirFilter optional filter to apply when finding subdirectories. + * If this parameter is {@code null}, subdirectories will not be included in the + * search. Use TrueFileFilter.INSTANCE to match all directories. + * @param includeSubDirectories indicates if will include the subdirectories themselves + * @return a collection of java.io.File with the matching files + */ + @NotNull + public static Collection listFiles(@NotNull File directory, @NotNull FileFilter fileFilter, @NotNull FileFilter dirFilter, boolean includeSubDirectories) { + IOFileFilter fileIOFileFilter = asFileFilter(fileFilter); + IOFileFilter dirIOFileFilter = asFileFilter(dirFilter); + if (includeSubDirectories) { + return FileUtils.listFilesAndDirs(directory, fileIOFileFilter, dirIOFileFilter); + } else { + return FileUtils.listFiles(directory, fileIOFileFilter, dirIOFileFilter); + } + } + + /** + * Similar to a {@link MoreFiles#listFiles(File, IOFileFilter, IOFileFilter, boolean) }, + * except that the function use lazy loading without loading all data into memory at once. + * So if you predict that the amount of data is too large, you can use this function. + * + * If you only want specific file suffixes, use {@link org.apache.commons.io.filefilter.SuffixFileFilter} + * + * @param directory the directory to search in + * @param fileFilter filter to apply when finding files. + * @param dirFilter optional filter to apply when finding subdirectories. + * If this parameter is {@code null}, subdirectories will not be included in the + * search. Use TrueFileFilter.INSTANCE to match all directories. + * @param includeSubDirectories indicates if will include the subdirectories themselves + * + * @return Returns an iterable object that triggers the execution of real logic when iterating. + */ + @Beta + @NotNull + public static Iterable files(File directory, @NotNull FileFilter fileFilter, @NotNull FileFilter dirFilter, boolean includeSubDirectories) { + if (!directory.isDirectory()) { + throw new IllegalArgumentException("Parameter 'directory' is not a directory: " + directory); + } + if (fileFilter == null) { + throw new NullPointerException("Parameter 'fileFilter' is null"); + } + + IOFileFilter fileIOFileFilter = asFileFilter(fileFilter); + IOFileFilter dirIOFileFilter = asFileFilter(dirFilter); + + final IOFileFilter effFileFilter = FileFilterUtils.and(fileIOFileFilter, FileFilterUtils.notFileFilter(DirectoryFileFilter.INSTANCE)); + final IOFileFilter effDirFilter = dirIOFileFilter == null ? + FalseFileFilter.INSTANCE : + FileFilterUtils.and(dirIOFileFilter, DirectoryFileFilter.INSTANCE); + return innerFiles(directory, FileFilterUtils.or(effFileFilter, effDirFilter), includeSubDirectories); + } + + /** + * The function behaves the same as {@link FileUtils#innerListFiles(Collection, File, IOFileFilter, boolean)}, except that the function implements lazy loading. + * This function is implemented using a stack, referring to the recursive implementation of the above function. + * + * @return Returns an iterable object that triggers the execution of real logic when iterating. + */ + @Beta + private static Iterable innerFiles(File directory, IOFileFilter filter, boolean includeSubDirectories) { + final int STEP_START = 0; + final int STEP_DIRECTORY_RE = 1; + final int STEP_END = 2; + + @AllArgsConstructor + class Item { + File[] file; + int index; + int step; + } + + return () -> new AbstractIterator() { + Deque stack = new ArrayDeque<>(); + { + stack.push(new Item(new File[] {directory}, 0, STEP_START)); + } + + @Override + protected File computeNext() { + Item item = stack.getFirst(); + switch (item.step) { + case STEP_START: { + File file = item.file[item.index]; + if (file.isDirectory()) { + item.step = STEP_DIRECTORY_RE; + if (includeSubDirectories) { + return file; + } else { + return computeNext(); + } + } else { + item.step = STEP_END; + return file; + } + } + + case STEP_DIRECTORY_RE: { + File file = item.file[item.index]; + final File[] found = file.listFiles((FileFilter) filter); + if (found != null) { + stack.push(new Item(found, 0, STEP_START)); + } else { + item.step = STEP_END; + } + return computeNext(); + } + + case STEP_END: { + if (item.index + 1 < item.file.length) { + item.index++; + } else { + stack.pop(); + if (stack.isEmpty()) { + return endOfData(); + } + } + return computeNext(); + } + + default: { + throw new IllegalStateException(); + } + } + } + }; + } + + @Nullable + @Contract("null -> null") + private static IOFileFilter asFileFilter(@Nullable FileFilter fileFilter) { + if (fileFilter == null) { + return null; + } + if (fileFilter instanceof IOFileFilter) { + return (IOFileFilter) fileFilter; + } else { + return FileFilterUtils.asFileFilter(fileFilter); + } + } + +} diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/Paths.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/Paths.java index aca57a4..dff8df0 100644 --- a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/Paths.java +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/basetools/file/Paths.java @@ -15,9 +15,14 @@ package io.github.javajerrat.boost.basetools.file; +import com.google.common.annotations.Beta; +import io.github.javajerrat.boost.lang.string.Strings; +import java.io.File; import java.util.Arrays; import java.util.List; +import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; +import org.jetbrains.annotations.NotNull; /** * @author Frapples @@ -27,48 +32,107 @@ */ public class Paths { - public static final char SEP = System.getProperty("file.separator").charAt(0); + public static final char SEPARATOR = File.separatorChar; - private static final List ALL_SEP = Arrays.asList('/', '\\', SEP); + private static final List ALL_SEPARATOR = Arrays.asList('/', '\\', SEPARATOR); + + /** + * If path starts with ~, replace it with home directory. + * TODO: This function currently cannot handle syntax like {@code ~user} + * + * @param path Replaced path + * @return A string representing the processed path + */ + @Beta + @NotNull + public static String expanduser(@NotNull String path) { + String user = System.getProperty("user.home"); + return path.replaceFirst("~", user); + } + + private static Pattern varPattern = Pattern.compile("\\$(\\{?)([a-zA-Z0-9_]+)(}?)"); + + /** + * This function replaces environment variables that appear in the path. + * If the relevant environment variable is not found, it will not be replaced. + * The syntax of the environment variable is {@code $DEMOVAR } or {@code ${DEMOVAR}}. + * + * @param path Replace path + * @return A string representing the processed path + */ + @Beta + @NotNull + public static String expandvars(@NotNull String path) { + return Strings.regReplace(path, varPattern, (matches, index) -> { + String all = matches.group(0); + String leftBrace = matches.group(1); + String var = matches.group(2); + String rightBrace = matches.group(3); + if (leftBrace.isEmpty() == rightBrace.isEmpty()) { + String value = System.getenv(var); + return value != null ? value : all; + } else if (leftBrace.isEmpty()) { + String value = System.getenv(var); + return value != null ? value + rightBrace : all; + } else { + return all; + } + }); + } - public static String join(String... seg) { - if (seg == null || seg.length == 0) { + /** + * + * Join two or more pathname components, inserting '/' as needed. + * @param components pathname components + * @return path + */ + public static String join(String... components) { + if (components == null || components.length == 0) { return ""; } StringBuilder path = new StringBuilder(); - for (int i = 0; i < seg.length; i++) { - String s = seg[i]; + for (int i = 0; i < components.length; i++) { + String s = components[i]; if (s.length() > 0) { int first = 0; int end = s.length() - 1; - if (ALL_SEP.contains(s.charAt(first))) { + if (ALL_SEPARATOR.contains(s.charAt(first))) { first++; } - if (ALL_SEP.contains(s.charAt(end))) { + if (ALL_SEPARATOR.contains(s.charAt(end))) { end--; } path.append(s, first, end + 1); - if (i != seg.length - 1) { - path.append(SEP); + if (i != components.length - 1) { + path.append(SEPARATOR); } } } return path.toString(); } + /** + * @param path pathname + * @return Returns the directory component of a pathname + */ public static String dirname(String path) { if (path == null || path.isEmpty()) { return ""; } - if (ALL_SEP.contains(path.charAt(path.length() - 1))) { + if (ALL_SEPARATOR.contains(path.charAt(path.length() - 1))) { path = path.substring(0, path.length() - 1); } return FilenameUtils.getFullPath(path); } + /** + * @param path the file path + * @return the name of the file without the path, or an empty string if none exists. + * Null bytes inside string will be removed + */ public static String basename(String path) { return FilenameUtils.getName(path); } diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/codec/json/Jacksons.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/codec/json/Jacksons.java new file mode 100644 index 0000000..e2e13cc --- /dev/null +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/codec/json/Jacksons.java @@ -0,0 +1,198 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.codec.json; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.deser.std.DateDeserializers; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * @author Frapples + * @date 2019/12/20 + */ + +public class Jacksons { + + public Jacksons () { + throw new UnsupportedOperationException(); + } + + /** + * Be careful not to modify it!!! + */ + public static ObjectMapper DEFAULT_OBJECT_MAPPER = jacksonObjectMapper(); + + public static ObjectMapper jacksonObjectMapper() { + return jacksonObjectMapper(true, false); + } + + /** + * Provide a mapper with common configuration + * @param pretty If true, The mapper will output pretty json. + * @param serializeNumberAsString If true, + * + * @return A new ObjectMapper object. + */ + public static ObjectMapper jacksonObjectMapper(boolean pretty, boolean serializeNumberAsString) { + + ObjectMapper mapper = new ObjectMapper(); + configureJavaTime(mapper); + configureBase(mapper); + if (pretty) { + mapper.enable(SerializationFeature.INDENT_OUTPUT); + } + if (serializeNumberAsString) { + configureSerializeNumberAsString(mapper); + } + return mapper; + } + + private static void configureBase(ObjectMapper mapper) { + mapper + .configure(MapperFeature.USE_STD_BEAN_NAMING, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .setTimeZone(TimeZone.getDefault()); + } + + /** + * Serialize BigDecimal type, BigInteger type, and Long type to string type to prevent loss of precision + * + * @param mapper mapper + */ + private static void configureSerializeNumberAsString(ObjectMapper mapper) { + mapper.registerModule(new SimpleModule() + .addSerializer(BigDecimal.class, ToStringSerializer.instance) + .addSerializer(BigInteger.class, ToStringSerializer.instance) + .addSerializer(Long.class, ToStringSerializer.instance)); + } + + public static void configureJavaTime(ObjectMapper jackson2ObjectMapper) { + jackson2ObjectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + + JavaTimeModule timeModule = new JavaTimeModule(); + timeModule.addDeserializer(LocalDate.class, + new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + + timeModule.addDeserializer(LocalDateTime.class, + new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + + timeModule.addSerializer(LocalDate.class, + new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); + + timeModule.addSerializer(LocalDateTime.class, + new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + + jackson2ObjectMapper.registerModule(new SimpleModule() + .addDeserializer(Date.class, new MultiDateDeserializer( + Arrays.asList( + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd" + )))); + + jackson2ObjectMapper + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + // Please note that if not set, jackson will use UTC instead of java default time zone + // This feature is easy to make mistakes + .setTimeZone(TimeZone.getDefault()) + .registerModule(timeModule) + .registerModule(new ParameterNamesModule(JsonCreator.Mode.DELEGATING)) + .registerModule(new Jdk8Module()); + } + + /** + * Supports multiple formats when deserializing dates + */ + public static class MultiDateDeserializer extends DateDeserializers.DateDeserializer { + private static final long serialVersionUID = 1L; + + private final List dateFormats; + + private final TimeZone timeZone; + + public MultiDateDeserializer(List dataFormats) { + this.dateFormats = dataFormats; + this.timeZone = TimeZone.getDefault(); + } + + public MultiDateDeserializer(List dataFormats, TimeZone timeZone) { + this.dateFormats = dataFormats; + this.timeZone = timeZone; + } + + + @Override + protected DateDeserializers.DateDeserializer withDateFormat(DateFormat df, String formatString) { + List dateFormats = new ArrayList<>(); + dateFormats.add(formatString); + dateFormats.addAll(this.dateFormats); + return new MultiDateDeserializer(dateFormats, df.getTimeZone()); + } + + @Override + public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + final String date = node.textValue(); + + for (String dateFormat : dateFormats) { + try { + SimpleDateFormat df = new SimpleDateFormat(dateFormat); + df.setTimeZone(this.timeZone); + return df.parse(date); + } catch (ParseException ignored) { + } + } + if (date == null || date.isEmpty()) { + return null; + } + throw new JsonParseException(jp, "Unparseable date: \"" + date + "\". Supported formats: " + dateFormats); + } + } +} diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/Slf4jLogs.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/Slf4jLogs.java index 1863ed9..a056ca1 100644 --- a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/Slf4jLogs.java +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/Slf4jLogs.java @@ -14,9 +14,11 @@ package io.github.javajerrat.boost.lang.debug; +import lombok.SneakyThrows; import org.jooq.lambda.fi.util.function.CheckedSupplier; import org.slf4j.Logger; import org.slf4j.event.Level; +import org.slf4j.helpers.MessageFormatter; /** * @author Frapples @@ -24,28 +26,42 @@ */ public class Slf4jLogs { - public static void log(Logger log, Level level, String format, Object... args) { + /** + * Similar to {@link Logger#error(String)}, {@link Logger#warn(String)} and so on. + * According to the log level, it dispatches to a matching function. + * @param log logger + * @param level log level + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + public static void log(Logger log, Level level, String format, Object... arguments) { switch (level) { case ERROR: - log.error(format, args); + log.error(format, arguments); break; case WARN: - log.warn(format, args); + log.warn(format, arguments); break; case INFO: - log.info(format, args); + log.info(format, arguments); break; case DEBUG: - log.debug(format, args); + log.debug(format, arguments); break; case TRACE: - log.trace(format, args); + log.trace(format, arguments); break; default: throw new IllegalStateException("Illegal state"); } } + /** + * Similar to {@link Logger#isErrorEnabled()}, {@link Logger#isWarnEnabled()} and so on. + * According to the log level, it dispatches to a matching function. + * @param log logger + * @param level log level + */ public static boolean isEnable(Logger log, Level level) { switch (level) { case ERROR: @@ -63,8 +79,38 @@ public static boolean isEnable(Logger log, Level level) { } } + /** + * Format string in slf4j style. + * @param message String format + * @param args args + * @return Formatted string + */ + public static String format(String message, Object... args) { + return MessageFormatter.arrayFormat(message, args).getMessage(); + } + + + /** + * A helper function to implement delayed log to improve performance. + * @param checkedSupplier Log logic. + * @return An object that triggers log logic when it's toString method is called. + */ public static LazyString lazy(CheckedSupplier checkedSupplier) { return new LazyString(checkedSupplier); } + public static class LazyString { + private final CheckedSupplier stringSupplier; + + + LazyString(final CheckedSupplier stringSupplier) { + this.stringSupplier = stringSupplier; + } + + @SneakyThrows + @Override + public String toString() { + return String.valueOf(stringSupplier.get()); + } + } } diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/io/IOs.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/io/IOs.java index fb79122..7f44744 100644 --- a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/io/IOs.java +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/io/IOs.java @@ -16,6 +16,7 @@ package io.github.javajerrat.boost.lang.io; +import com.google.common.collect.AbstractIterator; import com.google.common.collect.Iterators; import io.github.javajerrat.boost.lang.functions.IntFunction2; import java.io.BufferedReader; @@ -28,9 +29,11 @@ import java.io.Reader; import java.io.SequenceInputStream; import java.io.StringReader; +import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.Charset; import java.util.Arrays; +import javax.validation.constraints.NotNull; import lombok.NonNull; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; @@ -64,29 +67,68 @@ public static void foreachLines(@NonNull Reader reader, @NonNull IntFunction2 lines) throws IOException { - writeLines(writer, lines, null); + /** + * Returns a lazy iterable. Foreach this iteration object to sequentially get each row in the stream. + * {@link BufferedReader#lines()} + * + * @param bufferedReader bufferedReader + * @return Returns a lazy iterable. + */ + public static Iterable lines(@NotNull BufferedReader bufferedReader) { + return () -> new AbstractIterator() { + @Override + protected String computeNext() { + try { + String line = bufferedReader.readLine(); + return line != null ? line : endOfData(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }; + } + + /** + * Write multiple lines of text to the writer, with system line separator. + * @param writer the writer + * @param lines the lines + */ + public static void writeLines(Writer writer, Iterable lines) throws IOException { + writeLines(writer, lines, IOUtils.LINE_SEPARATOR); } - public static void writeLines(Writer writer, Iterable lines, String lineEnding) throws IOException { + /** + * Write multiple lines of text to the writer, with custom line separator. + * @param writer the writer + * @param lines the lines + * @param lineEnding line separator + */ + public static void writeLines(Writer writer, Iterable lines, @NotNull String lineEnding) throws IOException { if (lines == null) { return; } - if (lineEnding == null) { - lineEnding = IOUtils.LINE_SEPARATOR; - } - for (final Object line : lines) { + for (String line : lines) { if (line != null) { - writer.write(line.toString()); + writer.write(line); } writer.write(lineEnding); } } + /** + * Concat two or more inputStream. + * @param inputStreams A set of inputStreams + * @return A decorative InputStream object + */ public static InputStream concat(InputStream... inputStreams) { return concatInputStream(Arrays.asList(inputStreams)); } + /** + * Concat two or more inputStream. + * @param inputStreams A set of inputStreams + * @return A decorative InputStream object + */ public static InputStream concatInputStream(Iterable inputStreams) { return new SequenceInputStream(Iterators.asEnumeration(inputStreams.iterator())); } diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/reflect/Reflects.java b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/reflect/Reflects.java new file mode 100644 index 0000000..c0f3824 --- /dev/null +++ b/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/reflect/Reflects.java @@ -0,0 +1,246 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.lang.reflect; + +import io.github.javajerrat.boost.lang.string.Strings; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Frapples + * @date 2019/7/11 + * @see FieldUtils + */ +public class Reflects extends ClassUtils { + + /** + * Create jdk dynamic proxy object + * @param interfaceType Proxy interface + * @param handler Invocation handler + * @return A jdk dynamic proxy object + */ + public static T newProxy(InvocationHandler handler, Class interfaceType) { + Object object = Proxy.newProxyInstance( + interfaceType.getClassLoader(), new Class[] {interfaceType}, handler); + return interfaceType.cast(object); + } + + /** + * @see FieldUtils#getFieldsListWithAnnotation(Class, Class) + */ + public static List getFieldsListWithAnnotation(Class cls, Class annotationCls) { + return FieldUtils.getFieldsListWithAnnotation(cls, annotationCls); + } + + /** + * @see FieldUtils#getAllFieldsList(Class) + */ + public static List getAllFieldsList(Class clazz) { + return FieldUtils.getAllFieldsList(clazz); + } + + /** + * Get the reflection of all fields of the bean. + * If the same field appears multiple times on the inheritance chain, + * they will be put into a List with the child class first and the parent class later. + */ + public static Map> getAllFields(Class clazz) { + Map> allFields = new LinkedHashMap<>(); + for (Class c = clazz; c!= null; c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + List fields = allFields.getOrDefault(f.getName(), new ArrayList<>()); + fields.add(f); + allFields.put(f.getName(), fields); + } + } + return allFields; + } + /** + * Get the reflection of those fields with given annotations. + * If the same field appears multiple times on the inheritance chain, + * they will be put into a List with the child class first and the parent class later. + */ + @SafeVarargs + public static Map> getAllFieldsWithAnnotationAllMatch(Class clazz, Class... annotations) { + Map> allFields = new LinkedHashMap<>(); + for (Class c = clazz; c!= null; c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + boolean matched = true; + for (Class annotation : annotations) { + if (!f.isAnnotationPresent(annotation)) { + matched = false; + break; + } + } + if (matched) { + List fields = allFields.getOrDefault(f.getName(), new ArrayList<>()); + fields.add(f); + allFields.put(f.getName(), fields); + } + } + } + return allFields; + } + + /** + * Get the reflection of those fields with given annotations. + * If the same field appears multiple times on the inheritance chain, + * they will be put into a List with the child class first and the parent class later. + */ + @SafeVarargs + public static Map> getAllFieldsWithAnnotationAnyMatch(Class clazz, Class... annotations) { + Map> allFields = new LinkedHashMap<>(); + for (Class c = clazz; c!= null; c = c.getSuperclass()) { + for (Field f : c.getDeclaredFields()) { + boolean matched = false; + for (Class annotation : annotations) { + if (f.isAnnotationPresent(annotation)) { + matched = true; + break; + } + } + if (matched) { + List fields = allFields.getOrDefault(f.getName(), new ArrayList<>()); + fields.add(f); + allFields.put(f.getName(), fields); + } + } + } + return allFields; + } + + /** + * Search the class inheritance tree. + * If there is a parameterized type of the raw class {@code matched}, return it. + * + * For example, If {@code ExampleList} is a {@code List}, + * You can use this function to get the generic parameter @{code String}. + * + * @param matched Searched raw class + * @return If found, returns its reflection, otherwise returns null. + */ + @Nullable + public static ParameterizedType getGenericSuperType(@NotNull Class clazz, @NotNull Class matched) { + Type type = clazz.getGenericSuperclass(); + if (isParameterizedType(type, matched)) { + return (ParameterizedType)type; + } + Type[] interfaces = clazz.getGenericInterfaces(); + interfaces = interfaces == null ? new Type[0] : interfaces; + for (Type i : interfaces) { + if (isParameterizedType(i, matched)) { + return (ParameterizedType) i; + } + } + + if (type instanceof Class) { + ParameterizedType result = getGenericSuperType((Class) type, matched); + if (result != null) { + return result; + } + } + for (Type i : interfaces) { + if (i instanceof Class) { + ParameterizedType result = getGenericSuperType((Class) i, matched); + if (result != null) { + return result; + } + } + } + return null; + } + + private static boolean isParameterizedType(Type type, Class rawType) { + return type instanceof ParameterizedType && + rawType.isAssignableFrom((Class) ((ParameterizedType) type).getRawType()); + } + + + @FunctionalInterface + public interface MethodReferenceGetter extends Serializable, Function { + } + + + @AllArgsConstructor + @ToString + @EqualsAndHashCode + public static class MethodReferenceTuple { + + public final String className; + public final String methodName; + } + + /** + * @param methodReferenceGetter Method reference of getter + * @return Reflection on getter, including class name and getter name + */ + @Nullable + public static MethodReferenceTuple getLambda(MethodReferenceGetter methodReferenceGetter) { + @Nullable SerializedLambda lambda = getLambda((Serializable) methodReferenceGetter); + if (lambda != null) { + return new MethodReferenceTuple( + Strings.replace(lambda.getImplClass(), "/", "."), lambda.getImplMethodName()); + } else { + return null; + } + } + + /** + * @param lambda Method reference + * @return SerializedLambda + */ + @Nullable + private static SerializedLambda getLambda(Serializable lambda) { + // Thanks for: https://stackoverflow.com/questions/31178103/how-can-i-find-the-target-of-a-java8-method-reference + for (Class clazz = lambda.getClass(); clazz != null; clazz = clazz.getSuperclass()) { + try { + Method method = clazz.getDeclaredMethod("writeReplace"); + method.setAccessible(true); + Object replacement = method.invoke(lambda); + if (!(replacement instanceof SerializedLambda)) { + break; // custom interface implementation + } + return (SerializedLambda) replacement; + } catch (NoSuchMethodException ignored) { + } catch (IllegalAccessException | InvocationTargetException e) { + break; + } + } + + return null; + } + +} \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/datetime/DatesTest.java b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/datetime/DatesTest.java new file mode 100644 index 0000000..f42cfa3 --- /dev/null +++ b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/datetime/DatesTest.java @@ -0,0 +1,42 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.basetools.datetime; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Date; +import org.junit.jupiter.api.Test; + +/** + * @author Frapples + * @date 2020/3/9 + */ +class DatesTest { + + @Test + void setTime() { + Date date = new Date(); + date = Dates.setTime(date, 8, 0 ,0); + System.out.println(Dates.format(date, DateFormats.YYYY_MM_DD_HH_MM_SS_SSS)); + System.out.println(Dates.getDateTuple(date)); + } + + void setDate() { + Date date = new Date(); + date = Dates.setDate(date, 2000, 2 ,28); + System.out.println(Dates.format(date, DateFormats.YYYY_MM_DD_HH_MM_SS_SSS)); + System.out.println(Dates.getDateTuple(date)); + } +} \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/LazyString.java b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/file/PathsTest.java similarity index 53% rename from jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/LazyString.java rename to jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/file/PathsTest.java index a5d0f80..8d7aab8 100644 --- a/jerrat-boost-tiny/src/main/java/io/github/javajerrat/boost/lang/debug/LazyString.java +++ b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/basetools/file/PathsTest.java @@ -13,27 +13,27 @@ */ -package io.github.javajerrat.boost.lang.debug; +package io.github.javajerrat.boost.basetools.file; -import lombok.SneakyThrows; -import org.jooq.lambda.fi.util.function.CheckedSupplier; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; /** * @author Frapples - * @date 2019/6/25 + * @date 2019/12/23 */ +class PathsTest { -public class LazyString { - private final CheckedSupplier stringSupplier; - - - LazyString(final CheckedSupplier stringSupplier) { - this.stringSupplier = stringSupplier; + @Test + void expanduser() { } - @Override - @SneakyThrows - public String toString() { - return String.valueOf(stringSupplier.get()); + @Test + void expandvars() { + System.out.println(Paths.expandvars("/test/$JAVA_HOME/abc/$a")); + System.out.println(Paths.expandvars("/test/${JAVA_HOME/abc/$a")); + System.out.println(Paths.expandvars("/test/$JAVA_HOME}/abc/$a")); + System.out.println(Paths.expandvars("/test/${JAVA_HOME}/abc/$a")); } -} +} \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/codec/json/JacksonsTest.java b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/codec/json/JacksonsTest.java new file mode 100644 index 0000000..9f4b2b1 --- /dev/null +++ b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/codec/json/JacksonsTest.java @@ -0,0 +1,79 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.codec.json; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.javajerrat.boost.basetools.datetime.DateFormats; +import io.github.javajerrat.boost.lang.collection.Colls; +import java.util.Date; +import java.util.Map; +import lombok.Data; +import lombok.SneakyThrows; +import org.apache.commons.lang3.time.DateUtils; +import org.junit.jupiter.api.Test; + +/** + * @author Frapples + * @date 2019/12/20 + */ +class JacksonsTest { + + @Data + public static class TimeBean { + @JsonFormat(pattern = DateFormats.HH_MM_SS) + Date time; + } + + @SneakyThrows + @Test + public void test() { + ObjectMapper jacksonMapper = Jacksons.jacksonObjectMapper(); + + // Test time zone settings + { + String json = "{\"time\": \"11:12:00\"}"; + TimeBean bean = jacksonMapper.readValue(json, TimeBean.class); + Date expected = DateUtils.parseDate("1970-01-01 11:12:00", DateFormats.YYYY_MM_DD_HH_MM_SS); + assertEquals(bean.getTime(), expected); + } + + { + Map map = Colls.mapOf("time", "11:12:00"); + TimeBean bean = jacksonMapper.convertValue(map, TimeBean.class); + Date expected = DateUtils.parseDate("1970-01-01 11:12:00", DateFormats.YYYY_MM_DD_HH_MM_SS); + assertEquals(bean.getTime(), expected); + } + + // Test support for multiple time formats + { + Map map = Colls.mapOf("time", "2019-01-01 11:12:00"); + TimeBean bean = jacksonMapper.convertValue(map, TimeBean.class); + Date expected = DateUtils.parseDate("2019-01-01 11:12:00", DateFormats.YYYY_MM_DD_HH_MM_SS); + assertEquals(bean.getTime(), expected); + } + + { + Map map = Colls.mapOf("time", "2019-01-01"); + TimeBean bean = jacksonMapper.convertValue(map, TimeBean.class); + Date expected = DateUtils.parseDate("2019-01-01 00:00:00", DateFormats.YYYY_MM_DD_HH_MM_SS); + assertEquals(bean.getTime(), expected); + } + + } + +} \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/io/IOsTest.java b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/io/IOsTest.java index be7751b..0036b3d 100644 --- a/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/io/IOsTest.java +++ b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/io/IOsTest.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import org.junit.jupiter.api.Test; @@ -34,4 +35,13 @@ void foreachLines() throws IOException { return true; }); } + + @Test + void lines() { + BufferedReader bufferedReader = IOs.toBufferedReader(IOs.toReader("a cat on the sit\n this is a test line\n")); + for (String line : IOs.lines(bufferedReader)) { + System.out.println(line); + } + + } } \ No newline at end of file diff --git a/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/reflect/ReflectsTest.java b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/reflect/ReflectsTest.java new file mode 100644 index 0000000..34bbd49 --- /dev/null +++ b/jerrat-boost-tiny/src/main/test/java/io/github/javajerrat/boost/lang/reflect/ReflectsTest.java @@ -0,0 +1,132 @@ +/* + * 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 + + * http://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 io.github.javajerrat.boost.lang.reflect; + +import static org.junit.jupiter.api.Assertions.*; + +import io.github.javajerrat.boost.lang.reflect.Reflects.MethodReferenceTuple; +import java.awt.Point; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +/** + * @author Frapples + * @date 2019/12/21 + */ +class ReflectsTest { + + @Test + void getLambda() { + { + Optional vo = Reflects.getLambda(Point::getX); + assertEquals(vo.orElse(null), + new MethodReferenceTuple("java.awt.Point", "getX")); + } + + { + Optional vo = Reflects.getLambda(Object::hashCode); + assertEquals(vo.orElse(null), + new MethodReferenceTuple("java.lang.Object", "hashCode")); + } + } + + private static class TestMap extends HashMap { + + } + + private interface List1 extends List { + + } + + private static class TestList1 extends ArrayList implements List1 { + + } + + private static class TestList2 extends TestList1 implements List1 { + } + + private static class TestList3 extends ArrayList { + } + + private static class TestList4 extends ArrayList> { + } + + private static class TestList5 extends ArrayList { + } + + @Test + void getGenericSuperType() { + { + + ParameterizedType type = Reflects.getGenericSuperType(TestMap.class, HashMap.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), HashMap.class); + } + { + ParameterizedType type = Reflects.getGenericSuperType(TestMap.class, List.class); + System.out.println(type); + assertNull(type); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(TestList1.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), ArrayList.class); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(List1.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), List.class); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(TestList2.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), ArrayList.class); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(TestList3.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), ArrayList.class); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(TestList4.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), ArrayList.class); + } + + { + ParameterizedType type = Reflects.getGenericSuperType(TestList5.class, List.class); + System.out.println(type); + assertNotNull(type); + assertEquals(type.getRawType(), ArrayList.class); + } + } +} \ No newline at end of file