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 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 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 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 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
+ * @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 extends InputStream> 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 extends Annotation> 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 extends Annotation>... annotations) {
+ Map> allFields = new LinkedHashMap<>();
+ for (Class> c = clazz; c!= null; c = c.getSuperclass()) {
+ for (Field f : c.getDeclaredFields()) {
+ boolean matched = true;
+ for (Class extends Annotation> 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 extends Annotation>... annotations) {
+ Map> allFields = new LinkedHashMap<>();
+ for (Class> c = clazz; c!= null; c = c.getSuperclass()) {
+ for (Field f : c.getDeclaredFields()) {
+ boolean matched = false;
+ for (Class extends Annotation> 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