From 9a37a141577c3aa0d7ff11343c8af1c1fa2b9c74 Mon Sep 17 00:00:00 2001 From: zhuyijun Date: Mon, 27 Mar 2023 17:01:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - server/zyjblogs-gateway/.gitignore | 1 - server/zyjblogs-gateway/bin/assembly.xml | 39 + server/zyjblogs-oauth/.gitignore | 1 - server/zyjblogs-oauth/bin/assembly.xml | 39 + server/zyjblogs-rbac/.gitignore | 1 - server/zyjblogs-rbac/bin/assembly.xml | 39 + .../pom.xml | 4 + .../collections/map/ConcurrentHashSet.java | 117 +++ .../exception/AbstractBusinessException.java | 10 + .../common/exception/UtilException.java | 21 + .../starter/common/function/Filter.java | 15 + .../common/io/FastByteArrayOutputStream.java | 427 +++++++++ .../starter/common/io/UnsafeStringWriter.java | 93 ++ .../io/UpdateMessageDigestInputStream.java | 27 + .../utils/collections/CollectionUtils.java | 179 ++++ .../starter/common/utils/date/DateUtils.java | 245 +++++ .../starter/common/utils/net/NetUtils.java | 212 ++++ .../common/utils/string/CharUtils.java | 73 ++ .../common/utils/string/StringUtils.java | 905 ++++++++++++++++++ .../starter/common/utils/type/TypeUtils.java | 296 ++++++ 21 files changed, 2741 insertions(+), 4 deletions(-) create mode 100644 server/zyjblogs-gateway/bin/assembly.xml create mode 100644 server/zyjblogs-oauth/bin/assembly.xml create mode 100644 server/zyjblogs-rbac/bin/assembly.xml create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/collections/map/ConcurrentHashSet.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/UtilException.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/function/Filter.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/FastByteArrayOutputStream.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UnsafeStringWriter.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UpdateMessageDigestInputStream.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/collections/CollectionUtils.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/date/DateUtils.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/net/NetUtils.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/CharUtils.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/StringUtils.java create mode 100644 stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/type/TypeUtils.java diff --git a/.gitignore b/.gitignore index 8d458ae..f91c446 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,6 @@ gen *.rar hs_err_pid* .metadata -bin/ tmp/ *.tmp *.bak diff --git a/server/zyjblogs-gateway/.gitignore b/server/zyjblogs-gateway/.gitignore index 8d458ae..f91c446 100644 --- a/server/zyjblogs-gateway/.gitignore +++ b/server/zyjblogs-gateway/.gitignore @@ -70,7 +70,6 @@ gen *.rar hs_err_pid* .metadata -bin/ tmp/ *.tmp *.bak diff --git a/server/zyjblogs-gateway/bin/assembly.xml b/server/zyjblogs-gateway/bin/assembly.xml new file mode 100644 index 0000000..4e26b22 --- /dev/null +++ b/server/zyjblogs-gateway/bin/assembly.xml @@ -0,0 +1,39 @@ + + bin + + zip + + + true + + + + false + lib + false + + + + + ${project.build.directory} + / + + *.jar + + + + / + + *.md + + + + + + src/main/resources/ + config/${project.artifactId} + + 0644 + + + \ No newline at end of file diff --git a/server/zyjblogs-oauth/.gitignore b/server/zyjblogs-oauth/.gitignore index 8d458ae..f91c446 100644 --- a/server/zyjblogs-oauth/.gitignore +++ b/server/zyjblogs-oauth/.gitignore @@ -70,7 +70,6 @@ gen *.rar hs_err_pid* .metadata -bin/ tmp/ *.tmp *.bak diff --git a/server/zyjblogs-oauth/bin/assembly.xml b/server/zyjblogs-oauth/bin/assembly.xml new file mode 100644 index 0000000..ee4084f --- /dev/null +++ b/server/zyjblogs-oauth/bin/assembly.xml @@ -0,0 +1,39 @@ + + bin + + zip + + + true + + + + false + lib + false + + + + + ${project.build.directory} + / + + *.jar + + + + / + + *.md + + + + + + src/main/resources/ + config/${project.artifactId} + + 0644 + + + \ No newline at end of file diff --git a/server/zyjblogs-rbac/.gitignore b/server/zyjblogs-rbac/.gitignore index 8d458ae..f91c446 100644 --- a/server/zyjblogs-rbac/.gitignore +++ b/server/zyjblogs-rbac/.gitignore @@ -70,7 +70,6 @@ gen *.rar hs_err_pid* .metadata -bin/ tmp/ *.tmp *.bak diff --git a/server/zyjblogs-rbac/bin/assembly.xml b/server/zyjblogs-rbac/bin/assembly.xml new file mode 100644 index 0000000..4e26b22 --- /dev/null +++ b/server/zyjblogs-rbac/bin/assembly.xml @@ -0,0 +1,39 @@ + + bin + + zip + + + true + + + + false + lib + false + + + + + ${project.build.directory} + / + + *.jar + + + + / + + *.md + + + + + + src/main/resources/ + config/${project.artifactId} + + 0644 + + + \ No newline at end of file diff --git a/stater/zyjblogs-common-spring-boot-starter/pom.xml b/stater/zyjblogs-common-spring-boot-starter/pom.xml index 86edde4..8380b1f 100644 --- a/stater/zyjblogs-common-spring-boot-starter/pom.xml +++ b/stater/zyjblogs-common-spring-boot-starter/pom.xml @@ -34,6 +34,10 @@ io.swagger swagger-annotations + + com.alibaba.fastjson2 + fastjson2 + com.alibaba transmittable-thread-local diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/collections/map/ConcurrentHashSet.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/collections/map/ConcurrentHashSet.java new file mode 100644 index 0000000..9e6fd29 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/collections/map/ConcurrentHashSet.java @@ -0,0 +1,117 @@ +package cn.zyjblogs.starter.common.collections.map; + +import java.util.AbstractSet; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * ConcurrentHashSet + */ +public class ConcurrentHashSet extends AbstractSet implements Set, java.io.Serializable { + + private static final long serialVersionUID = -8672117787651310382L; + + private static final Object PRESENT = new Object(); + + private final ConcurrentMap map; + + public ConcurrentHashSet() { + map = new ConcurrentHashMap(); + } + + public ConcurrentHashSet(int initialCapacity) { + map = new ConcurrentHashMap(initialCapacity); + } + + /** + * Returns an iterator over the elements in this set. The elements are + * returned in no particular order. + * + * @return an Iterator over the elements in this set + * @see ConcurrentModificationException + */ + @Override + public Iterator iterator() { + return map.keySet().iterator(); + } + + /** + * Returns the number of elements in this set (its cardinality). + * + * @return the number of elements in this set (its cardinality) + */ + @Override + public int size() { + return map.size(); + } + + /** + * Returns true if this set contains no elements. + * + * @return true if this set contains no elements + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Returns true if this set contains the specified element. More + * formally, returns true if and only if this set contains an + * element e such that + * (o==null ? e==null : o.equals(e)). + * + * @param o element whose presence in this set is to be tested + * @return true if this set contains the specified element + */ + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + /** + * Adds the specified element to this set if it is not already present. More + * formally, adds the specified element e to this set if this set + * contains no element e2 such that + * (e==null ? e2==null : e.equals(e2)). If this + * set already contains the element, the call leaves the set unchanged and + * returns false. + * + * @param e element to be added to this set + * @return true if this set did not already contain the specified + * element + */ + @Override + public boolean add(E e) { + return map.put(e, PRESENT) == null; + } + + /** + * Removes the specified element from this set if it is present. More + * formally, removes an element e such that + * (o==null ? e==null : o.equals(e)), if this + * set contains such an element. Returns true if this set contained + * the element (or equivalently, if this set changed as a result of the + * call). (This set will not contain the element once the call returns.) + * + * @param o object to be removed from this set, if present + * @return true if the set contained the specified element + */ + @Override + public boolean remove(Object o) { + return map.remove(o) == PRESENT; + } + + /** + * Removes all elements from this set. The set will be empty after + * this call returns. + */ + @Override + public void clear() { + map.clear(); + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/AbstractBusinessException.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/AbstractBusinessException.java index a2f3b89..976db9d 100644 --- a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/AbstractBusinessException.java +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/AbstractBusinessException.java @@ -34,4 +34,14 @@ public class AbstractBusinessException extends RuntimeException { this.message = message; } + /** + * 创建业务异常对象 + * + * @param message 错误消息 + */ + public AbstractBusinessException(String message) { + super(message); + this.responseCode = HttpCode.INTERNAL_SERVER_ERROR; + this.message = message; + } } diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/UtilException.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/UtilException.java new file mode 100644 index 0000000..867bb39 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/exception/UtilException.java @@ -0,0 +1,21 @@ +package cn.zyjblogs.starter.common.exception; + + +import cn.zyjblogs.starter.common.entity.response.HttpCode; + +/** + * 工具类异常 + * + * @author lingyi + */ +public class UtilException extends AbstractBusinessException { + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(String message) { + super(message); + } + + public UtilException(HttpCode code, String message) { + super(code, message); + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/function/Filter.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/function/Filter.java new file mode 100644 index 0000000..be8b71e --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/function/Filter.java @@ -0,0 +1,15 @@ +package cn.zyjblogs.starter.common.function; + +/** + * 过滤器接口 + */ +@FunctionalInterface +public interface Filter { + /** + * 是否接受对象 + * + * @param t 检查的对象 + * @return 是否接受对象 + */ + boolean accept(T t); +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/FastByteArrayOutputStream.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/FastByteArrayOutputStream.java new file mode 100644 index 0000000..10beba8 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/FastByteArrayOutputStream.java @@ -0,0 +1,427 @@ +package cn.zyjblogs.starter.common.io; + +import org.springframework.util.Assert; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.Iterator; +import java.util.LinkedList; + +public class FastByteArrayOutputStream extends OutputStream { + + private static final int DEFAULT_BLOCK_SIZE = 256; + + + // The buffers used to store the content bytes + private final LinkedList buffers = new LinkedList(); + + // The size, in bytes, to use when allocating the first byte[] + private final int initialBlockSize; + + // The size, in bytes, to use when allocating the next byte[] + private int nextBlockSize = 0; + + // The number of bytes in previous buffers. + // (The number of bytes in the current buffer is in 'index'.) + private int alreadyBufferedSize = 0; + + // The index in the byte[] found at buffers.getLast() to be written next + private int index = 0; + + // Is the stream closed? + private boolean closed = false; + + + /** + * Create a new FastByteArrayOutputStream + * with the default initial capacity of 256 bytes. + */ + public FastByteArrayOutputStream() { + this(DEFAULT_BLOCK_SIZE); + } + + /** + * Create a new FastByteArrayOutputStream + * with the specified initial capacity. + * + * @param initialBlockSize the initial buffer size in bytes + */ + public FastByteArrayOutputStream(int initialBlockSize) { + if (initialBlockSize <= 0) { + throw new IllegalArgumentException("Initial block size must be greater than 0"); + } + this.initialBlockSize = initialBlockSize; + this.nextBlockSize = initialBlockSize; + } + + + // Overridden methods + + @Override + public void write(int datum) throws IOException { + if (this.closed) { + throw new IOException("Stream closed"); + } else { + if (this.buffers.peekLast() == null || this.buffers.getLast().length == this.index) { + addBuffer(1); + } + // store the byte + this.buffers.getLast()[this.index++] = (byte) datum; + } + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + if (data == null) { + throw new NullPointerException(); + } else if (offset < 0 || offset + length > data.length || length < 0) { + throw new IndexOutOfBoundsException(); + } else if (this.closed) { + throw new IOException("Stream closed"); + } else { + if (this.buffers.peekLast() == null || this.buffers.getLast().length == this.index) { + addBuffer(length); + } + if (this.index + length > this.buffers.getLast().length) { + int pos = offset; + do { + if (this.index == this.buffers.getLast().length) { + addBuffer(length); + } + int copyLength = this.buffers.getLast().length - this.index; + if (length < copyLength) { + copyLength = length; + } + System.arraycopy(data, pos, this.buffers.getLast(), this.index, copyLength); + pos += copyLength; + this.index += copyLength; + length -= copyLength; + } + while (length > 0); + } else { + // copy in the sub-array + System.arraycopy(data, offset, this.buffers.getLast(), this.index, length); + this.index += length; + } + } + } + + @Override + public void close() { + this.closed = true; + } + + + @Override + public String toString() { + return new String(toByteArrayUnsafe()); + } + + + public int size() { + return (this.alreadyBufferedSize + this.index); + } + + + public byte[] toByteArrayUnsafe() { + int totalSize = size(); + if (totalSize == 0) { + return new byte[0]; + } + resize(totalSize); + return this.buffers.getFirst(); + } + + + public byte[] toByteArray() { + byte[] bytesUnsafe = toByteArrayUnsafe(); + byte[] ret = new byte[bytesUnsafe.length]; + System.arraycopy(bytesUnsafe, 0, ret, 0, bytesUnsafe.length); + return ret; + } + + /** + * Reset the contents of this FastByteArrayOutputStream. + *

All currently accumulated output in the output stream is discarded. + * The output stream can be used again. + */ + public void reset() { + this.buffers.clear(); + this.nextBlockSize = this.initialBlockSize; + this.closed = false; + this.index = 0; + this.alreadyBufferedSize = 0; + } + + public InputStream getInputStream() { + return new FastByteArrayInputStream(this); + } + + /** + * Write the buffers content to the given OutputStream. + * + * @param out the OutputStream to write to + */ + public void writeTo(OutputStream out) throws IOException { + Iterator it = this.buffers.iterator(); + while (it.hasNext()) { + byte[] bytes = it.next(); + if (it.hasNext()) { + out.write(bytes, 0, bytes.length); + } else { + out.write(bytes, 0, this.index); + } + } + } + + + public void resize(int targetCapacity) { + Assert.isTrue(targetCapacity >= size(), "New capacity must not be smaller than current size"); + if (this.buffers.peekFirst() == null) { + this.nextBlockSize = targetCapacity - size(); + } else if (size() == targetCapacity && this.buffers.getFirst().length == targetCapacity) { + // do nothing - already at the targetCapacity + } else { + int totalSize = size(); + byte[] data = new byte[targetCapacity]; + int pos = 0; + Iterator it = this.buffers.iterator(); + while (it.hasNext()) { + byte[] bytes = it.next(); + if (it.hasNext()) { + System.arraycopy(bytes, 0, data, pos, bytes.length); + pos += bytes.length; + } else { + System.arraycopy(bytes, 0, data, pos, this.index); + } + } + this.buffers.clear(); + this.buffers.add(data); + this.index = totalSize; + this.alreadyBufferedSize = 0; + } + } + + /** + * Create a new buffer and store it in the LinkedList + *

Adds a new buffer that can store at least {@code minCapacity} bytes. + */ + private void addBuffer(int minCapacity) { + if (this.buffers.peekLast() != null) { + this.alreadyBufferedSize += this.index; + this.index = 0; + } + if (this.nextBlockSize < minCapacity) { + this.nextBlockSize = nextPowerOf2(minCapacity); + } + this.buffers.add(new byte[this.nextBlockSize]); + this.nextBlockSize *= 2; // block size doubles each time + } + + /** + * Get the next power of 2 of a number (ex, the next power of 2 of 119 is 128). + */ + private static int nextPowerOf2(int val) { + val--; + val = (val >> 1) | val; + val = (val >> 2) | val; + val = (val >> 4) | val; + val = (val >> 8) | val; + val = (val >> 16) | val; + val++; + return val; + } + + + private static final class FastByteArrayInputStream extends UpdateMessageDigestInputStream { + + private final FastByteArrayOutputStream fastByteArrayOutputStream; + + private final Iterator buffersIterator; + + private byte[] currentBuffer; + + private int currentBufferLength = 0; + + private int nextIndexInCurrentBuffer = 0; + + private int totalBytesRead = 0; + + /** + * Create a new FastByteArrayOutputStreamInputStream backed + * by the given FastByteArrayOutputStream. + */ + public FastByteArrayInputStream(FastByteArrayOutputStream fastByteArrayOutputStream) { + this.fastByteArrayOutputStream = fastByteArrayOutputStream; + this.buffersIterator = fastByteArrayOutputStream.buffers.iterator(); + if (this.buffersIterator.hasNext()) { + this.currentBuffer = this.buffersIterator.next(); + if (this.currentBuffer == fastByteArrayOutputStream.buffers.getLast()) { + this.currentBufferLength = fastByteArrayOutputStream.index; + } else { + this.currentBufferLength = this.currentBuffer.length; + } + } + } + + @Override + public int read() { + if (this.currentBuffer == null) { + // This stream doesn't have any data in it... + return -1; + } else { + if (this.nextIndexInCurrentBuffer < this.currentBufferLength) { + this.totalBytesRead++; + return this.currentBuffer[this.nextIndexInCurrentBuffer++]; + } else { + if (this.buffersIterator.hasNext()) { + this.currentBuffer = this.buffersIterator.next(); + if (this.currentBuffer == this.fastByteArrayOutputStream.buffers.getLast()) { + this.currentBufferLength = this.fastByteArrayOutputStream.index; + } else { + this.currentBufferLength = this.currentBuffer.length; + } + this.nextIndexInCurrentBuffer = 0; + } else { + this.currentBuffer = null; + } + return read(); + } + } + } + + @Override + public int read(byte[] b) { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } else { + if (this.currentBuffer == null) { + // This stream doesn't have any data in it... + return -1; + } else { + if (this.nextIndexInCurrentBuffer < this.currentBufferLength) { + int bytesToCopy = Math.min(len, this.currentBufferLength - this.nextIndexInCurrentBuffer); + System.arraycopy(this.currentBuffer, this.nextIndexInCurrentBuffer, b, off, bytesToCopy); + this.totalBytesRead += bytesToCopy; + this.nextIndexInCurrentBuffer += bytesToCopy; + int remaining = read(b, off + bytesToCopy, len - bytesToCopy); + return bytesToCopy + Math.max(remaining, 0); + } else { + if (this.buffersIterator.hasNext()) { + this.currentBuffer = this.buffersIterator.next(); + if (this.currentBuffer == this.fastByteArrayOutputStream.buffers.getLast()) { + this.currentBufferLength = this.fastByteArrayOutputStream.index; + } else { + this.currentBufferLength = this.currentBuffer.length; + } + this.nextIndexInCurrentBuffer = 0; + } else { + this.currentBuffer = null; + } + return read(b, off, len); + } + } + } + } + + @Override + public long skip(long n) throws IOException { + if (n > Integer.MAX_VALUE) { + throw new IllegalArgumentException("n exceeds maximum (" + Integer.MAX_VALUE + "): " + n); + } else if (n == 0) { + return 0; + } else if (n < 0) { + throw new IllegalArgumentException("n must be 0 or greater: " + n); + } + int len = (int) n; + if (this.currentBuffer == null) { + // This stream doesn't have any data in it... + return 0; + } else { + if (this.nextIndexInCurrentBuffer < this.currentBufferLength) { + int bytesToSkip = Math.min(len, this.currentBufferLength - this.nextIndexInCurrentBuffer); + this.totalBytesRead += bytesToSkip; + this.nextIndexInCurrentBuffer += bytesToSkip; + return (bytesToSkip + skip(len - bytesToSkip)); + } else { + if (this.buffersIterator.hasNext()) { + this.currentBuffer = this.buffersIterator.next(); + if (this.currentBuffer == this.fastByteArrayOutputStream.buffers.getLast()) { + this.currentBufferLength = this.fastByteArrayOutputStream.index; + } else { + this.currentBufferLength = this.currentBuffer.length; + } + this.nextIndexInCurrentBuffer = 0; + } else { + this.currentBuffer = null; + } + return skip(len); + } + } + } + + @Override + public int available() { + return (this.fastByteArrayOutputStream.size() - this.totalBytesRead); + } + + /** + * Update the message digest with the remaining bytes in this stream. + * + * @param messageDigest The message digest to update + */ + public void updateMessageDigest(MessageDigest messageDigest) { + updateMessageDigest(messageDigest, available()); + } + + /** + * Update the message digest with the next len bytes in this stream. + * Avoids creating new byte arrays and use internal buffers for performance. + * + * @param messageDigest The message digest to update + * @param len how many bytes to read from this stream and use to update the message digest + */ + public void updateMessageDigest(MessageDigest messageDigest, int len) { + if (this.currentBuffer == null) { + // This stream doesn't have any data in it... + return; + } else if (len == 0) { + return; + } else if (len < 0) { + throw new IllegalArgumentException("len must be 0 or greater: " + len); + } else { + if (this.nextIndexInCurrentBuffer < this.currentBufferLength) { + int bytesToCopy = Math.min(len, this.currentBufferLength - this.nextIndexInCurrentBuffer); + messageDigest.update(this.currentBuffer, this.nextIndexInCurrentBuffer, bytesToCopy); + this.nextIndexInCurrentBuffer += bytesToCopy; + updateMessageDigest(messageDigest, len - bytesToCopy); + } else { + if (this.buffersIterator.hasNext()) { + this.currentBuffer = this.buffersIterator.next(); + if (this.currentBuffer == this.fastByteArrayOutputStream.buffers.getLast()) { + this.currentBufferLength = this.fastByteArrayOutputStream.index; + } else { + this.currentBufferLength = this.currentBuffer.length; + } + this.nextIndexInCurrentBuffer = 0; + } else { + this.currentBuffer = null; + } + updateMessageDigest(messageDigest, len); + } + } + } + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UnsafeStringWriter.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UnsafeStringWriter.java new file mode 100644 index 0000000..51c9b9f --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UnsafeStringWriter.java @@ -0,0 +1,93 @@ +package cn.zyjblogs.starter.common.io; + +import java.io.IOException; +import java.io.Writer; + +/** + * Thread-unsafe StringWriter. + * + * @author lingyi + */ +public class UnsafeStringWriter extends Writer { + private StringBuilder mBuffer; + + public UnsafeStringWriter() { + lock = mBuffer = new StringBuilder(); + } + + public UnsafeStringWriter(int size) { + if (size < 0) { + throw new IllegalArgumentException("Negative buffer size"); + } + + lock = mBuffer = new StringBuilder(); + } + + @Override + public void write(int c) { + mBuffer.append((char) c); + } + + @Override + public void write(char[] cs) throws IOException { + mBuffer.append(cs, 0, cs.length); + } + + @Override + public void write(char[] cs, int off, int len) throws IOException { + if ((off < 0) || (off > cs.length) || (len < 0) || + ((off + len) > cs.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + + if (len > 0) { + mBuffer.append(cs, off, len); + } + } + + @Override + public void write(String str) { + mBuffer.append(str); + } + + @Override + public void write(String str, int off, int len) { + mBuffer.append(str, off, off + len); + } + + @Override + public Writer append(CharSequence csq) { + if (csq == null) { + write("null"); + } else { + write(csq.toString()); + } + return this; + } + + @Override + public Writer append(CharSequence csq, int start, int end) { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + @Override + public Writer append(char c) { + mBuffer.append(c); + return this; + } + + @Override + public void close() { + } + + @Override + public void flush() { + } + + @Override + public String toString() { + return mBuffer.toString(); + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UpdateMessageDigestInputStream.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UpdateMessageDigestInputStream.java new file mode 100644 index 0000000..31a34a0 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/io/UpdateMessageDigestInputStream.java @@ -0,0 +1,27 @@ +package cn.zyjblogs.starter.common.io; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; + + +abstract class UpdateMessageDigestInputStream extends InputStream { + + + public void updateMessageDigest(MessageDigest messageDigest) throws IOException { + int data; + while ((data = read()) != -1) { + messageDigest.update((byte) data); + } + } + + public void updateMessageDigest(MessageDigest messageDigest, int len) throws IOException { + int data; + int bytesRead = 0; + while (bytesRead < len && (data = read()) != -1) { + messageDigest.update((byte) data); + bytesRead++; + } + } + +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/collections/CollectionUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/collections/CollectionUtils.java new file mode 100644 index 0000000..cd36d68 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/collections/CollectionUtils.java @@ -0,0 +1,179 @@ +package cn.zyjblogs.starter.common.utils.collections; + +import org.apache.commons.lang3.ArrayUtils; + +import java.util.*; + +/** + * 集合相关工具类 + * + * @author lingyi + */ +public class CollectionUtils { + /** + * 集合是否为非空 + * + * @param collection 集合 + * @return 是否为非空 + */ + public static boolean isNotEmpty(Collection collection) { + return !isEmpty(collection); + } + + + /** + * 集合是否为空 + * + * @param collection 集合 + * @return 是否为空 + */ + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } + + /** + * Enumeration是否为空 + * + * @param enumeration {@link Enumeration} + * @return 是否为空 + */ + public static boolean isNotEmpty(Enumeration enumeration) { + return null != enumeration && enumeration.hasMoreElements(); + } + + /** + * 获取集合中指定下标的元素值,下标可以为负数,例如-1表示最后一个元素
+ * 如果元素越界,返回null + * + * @param 元素类型 + * @param collection 集合 + * @param index 下标,支持负数 + * @return 元素值 + */ + public static T get(Collection collection, int index) { + if (null == collection) { + return null; + } + + final int size = collection.size(); + if (0 == size) { + return null; + } + + if (index < 0) { + index += size; + } + + // 检查越界 + if (index >= size) { + return null; + } + + if (collection instanceof List) { + final List list = ((List) collection); + return list.get(index); + } else { + int i = 0; + for (T t : collection) { + if (i > index) { + break; + } else if (i == index) { + return t; + } + i++; + } + } + return null; + } + + + /** + * 加入全部 + * + * @param 集合元素类型 + * @param collection 被加入的集合 {@link Collection} + * @param enumeration 要加入的内容{@link Enumeration} + * @return 原集合 + */ + public static Collection addAll(Collection collection, Enumeration enumeration) { + if (null != collection && null != enumeration) { + while (enumeration.hasMoreElements()) { + collection.add(enumeration.nextElement()); + } + } + return collection; + } + + /** + * 新建一个ArrayList + * + * @param 集合元素类型 + * @param values 数组 + * @return ArrayList对象 + */ + @SafeVarargs + public static ArrayList newArrayList(T... values) { + return toList(values); + } + + /** + * 新建一个ArrayList + * + * @param 集合元素类型 + * @param values 数组 + * @return ArrayList对象 + */ + @SafeVarargs + public static ArrayList toList(T... values) { + return (ArrayList) list(false, values); + } + + /** + * 新建一个List + * + * @param 集合元素类型 + * @param isLinked 是否新建LinkedList + * @param values 数组 + * @return List对象 + */ + @SafeVarargs + public static List list(boolean isLinked, T... values) { + if (ArrayUtils.isEmpty(values)) { + return list(isLinked); + } + final List arrayList = isLinked ? new LinkedList<>() : new ArrayList<>(values.length); + Collections.addAll(arrayList, values); + return arrayList; + } + + /** + * 新建一个HashSet + * + * @param 集合元素类型 + * @param ts 元素数组 + * @return HashSet对象 + */ + @SafeVarargs + public static HashSet newHashSet(T... ts) { + return set(false, ts); + } + + /** + * 新建一个HashSet + * + * @param 集合元素类型 + * @param isSorted 是否有序,有序返回 {@link LinkedHashSet},否则返回 {@link HashSet} + * @param ts 元素数组 + * @return HashSet对象 + */ + @SafeVarargs + public static HashSet set(boolean isSorted, T... ts) { + if (null == ts) { + return isSorted ? new LinkedHashSet<>() : new HashSet<>(); + } + int initialCapacity = Math.max((int) (ts.length / .75f) + 1, 16); + final HashSet set = isSorted ? new LinkedHashSet<>(initialCapacity) : new HashSet<>(initialCapacity); + Collections.addAll(set, ts); + return set; + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/date/DateUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/date/DateUtils.java new file mode 100644 index 0000000..10d891b --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/date/DateUtils.java @@ -0,0 +1,245 @@ +package cn.zyjblogs.starter.common.utils.date; + + +import cn.zyjblogs.starter.common.exception.UtilException; +import cn.zyjblogs.starter.common.utils.string.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.temporal.TemporalAccessor; +import java.util.Date; + +/** + * 时间工具类 + * + * @author xuechao.sxc + */ +public class DateUtils { + + /** + * 标准日期格式:yyyyMMdd + */ + public final static String PURE_DATE_PATTERN = "yyyyMMdd"; + public final static SimpleDateFormat PURE_DATE_FORMAT = new SimpleDateFormat(PURE_DATE_PATTERN); + + /** + * 标准日期格式:HHmmss + */ + public final static String PURE_TIME_PATTERN = "HHmmss"; + public final static SimpleDateFormat PURE_TIME_FORMAT = new SimpleDateFormat(PURE_TIME_PATTERN); + + /** + * 标准日期格式:yyyyMMddHHmmss + */ + public final static String PURE_DATETIME_PATTERN = "yyyyMMddHHmmss"; + public final static SimpleDateFormat PURE_DATETIME_FORMAT = new SimpleDateFormat(PURE_DATETIME_PATTERN); + + /** + * 标准日期格式:yyyyMMddHHmmssSSS + */ + public final static String PURE_DATETIME_MS_PATTERN = "yyyyMMddHHmmssSSS"; + public final static SimpleDateFormat PURE_DATETIME_MS_FORMAT = new SimpleDateFormat(PURE_DATETIME_MS_PATTERN); + + + /** + * 标准日期格式:yyyy-MM-dd + */ + public final static String NORM_DATE_PATTERN = "yyyy-MM-dd"; + public final static SimpleDateFormat NORM_DATE_FORMAT = new SimpleDateFormat(NORM_DATE_PATTERN); + + /** + * 标准日期时间格式,精确到分:yyyy-MM-dd HH:mm + */ + public final static String NORM_DATETIME_MINUTE_PATTERN = "yyyy-MM-dd HH:mm"; + public final static SimpleDateFormat NORM_DATETIME_MINUTE_FORMAT = new SimpleDateFormat(NORM_DATETIME_MINUTE_PATTERN); + + /** + * 标准日期时间格式,精确到秒:yyyy-MM-dd HH:mm:ss + */ + public final static String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public final static SimpleDateFormat NORM_DATETIME_FORMAT = new SimpleDateFormat(NORM_DATETIME_PATTERN); + + /** + * {@link TemporalAccessor}类型时间转为{@link Date}
+ * 始终根据已有{@link TemporalAccessor} 产生新的{@link Date}对象 + * + * @param temporalAccessor {@link TemporalAccessor} + * @return 时间对象 + */ + public static Date date(TemporalAccessor temporalAccessor) { + Instant instant = toInstant(temporalAccessor); + return new Date(instant.toEpochMilli()); + } + + /** + * Date对象转换为{@link Instant}对象 + * + * @param temporalAccessor Date对象 + * @return {@link Instant}对象 + */ + public static Instant toInstant(TemporalAccessor temporalAccessor) { + if (null == temporalAccessor) { + return null; + } + + Instant result; + if (temporalAccessor instanceof Instant) { + result = (Instant) temporalAccessor; + } else if (temporalAccessor instanceof LocalDateTime) { + result = ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault()).toInstant(); + } else if (temporalAccessor instanceof ZonedDateTime) { + result = ((ZonedDateTime) temporalAccessor).toInstant(); + } else if (temporalAccessor instanceof OffsetDateTime) { + result = ((OffsetDateTime) temporalAccessor).toInstant(); + } else if (temporalAccessor instanceof LocalDate) { + result = ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault()).toInstant(); + } else if (temporalAccessor instanceof LocalTime) { + // 指定本地时间转换 为Instant,取当天日期 + result = ((LocalTime) temporalAccessor).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant(); + } else if (temporalAccessor instanceof OffsetTime) { + // 指定本地时间转换 为Instant,取当天日期 + result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant(); + } else { + result = Instant.from(temporalAccessor); + } + + return result; + } + + /** + * 将日期字符串转换为{@link Date}对象,格式:
+ *

    + *
  1. yyyy-MM-dd HH:mm:ss
  2. + *
  3. yyyy/MM/dd HH:mm:ss
  4. + *
  5. yyyy.MM.dd HH:mm:ss
  6. + *
  7. yyyy年MM月dd日 HH时mm分ss秒
  8. + *
  9. yyyy-MM-dd
  10. + *
  11. yyyy/MM/dd
  12. + *
  13. yyyy.MM.dd
  14. + *
  15. HH:mm:ss
  16. + *
  17. HH时mm分ss秒
  18. + *
  19. yyyy-MM-dd HH:mm
  20. + *
  21. yyyy-MM-dd HH:mm:ss.SSS
  22. + *
  23. yyyyMMddHHmmss
  24. + *
  25. yyyyMMddHHmmssSSS
  26. + *
  27. yyyyMMdd
  28. + *
  29. EEE, dd MMM yyyy HH:mm:ss z
  30. + *
  31. EEE MMM dd HH:mm:ss zzz yyyy
  32. + *
  33. yyyy-MM-dd'T'HH:mm:ss'Z'
  34. + *
  35. yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
  36. + *
  37. yyyy-MM-dd'T'HH:mm:ssZ
  38. + *
  39. yyyy-MM-dd'T'HH:mm:ss.SSSZ
  40. + *
+ * + * @param dateCharSequence 日期字符串 + * @return 日期 + */ + public static Date parse(CharSequence dateCharSequence) { + if (StringUtils.isBlank(dateCharSequence)) { + return null; + } + String dateStr = dateCharSequence.toString(); + // 去掉两边空格并去掉中文日期中的“日”和“秒”,以规范长度 + dateStr = StringUtils.removeAll(dateStr.trim(), '日', '秒'); + int length = dateStr.length(); + + if (NumberUtils.isCreatable(dateStr)) { + // 纯数字形式 + if (length == PURE_DATETIME_PATTERN.length()) { + return parse(dateStr, PURE_DATETIME_FORMAT); + } else if (length == PURE_DATETIME_MS_PATTERN.length()) { + return parse(dateStr, PURE_DATETIME_MS_FORMAT); + } else if (length == PURE_DATE_PATTERN.length()) { + return parse(dateStr, PURE_DATE_FORMAT); + } else if (length == PURE_TIME_PATTERN.length()) { + return parse(dateStr, PURE_TIME_FORMAT); + } + } + if (length == NORM_DATETIME_PATTERN.length()) { + // yyyy-MM-dd HH:mm:ss + return parseDateTime(dateStr); + } else if (length == NORM_DATE_PATTERN.length()) { + // yyyy-MM-dd + return parseDate(dateStr); + } else if (length == NORM_DATETIME_MINUTE_PATTERN.length()) { + // yyyy-MM-dd HH:mm + return parse(dateStr, NORM_DATETIME_MINUTE_FORMAT); + } + + // 没有更多匹配的时间格式 + throw new UtilException("No format fit for date String:" + dateStr); + } + + /** + * 转换字符串为Date + * + * @param dateStr 日期字符串 + * @param dateFormat {@link SimpleDateFormat} + * @return {@link Date} + */ + private static Date parse(CharSequence dateStr, DateFormat dateFormat) { + if (StringUtils.isBlank(dateStr)) { + throw new UtilException("dataStr is blank"); + } + try { + return dateFormat.parse(dateStr.toString()); + } catch (Throwable e) { + String pattern; + if (dateFormat instanceof SimpleDateFormat) { + pattern = ((SimpleDateFormat) dateFormat).toPattern(); + } else { + pattern = dateFormat.toString(); + } + throw new UtilException(StringUtils.format("Parse [{}] with format [{}] error!", dateStr, pattern)); + } + } + + /** + * 将特定格式的日期转换为Date对象 + * + * @param dateStr 特定格式的日期 + * @param format 格式,例如yyyy-MM-dd + * @return 日期对象 + */ + public static Date parse(CharSequence dateStr, String format) { + SimpleDateFormat fmt = new SimpleDateFormat(format); + return parse(dateStr, fmt); + } + + /** + * 解析格式为yyyy-MM-dd的日期,忽略时分秒 + * + * @param dateString 标准形式的日期字符串 + * @return 日期对象 + */ + public static Date parseDate(CharSequence dateString) { + return parse(dateString, NORM_DATE_FORMAT); + } + + /** + * 格式yyyy-MM-dd HH:mm:ss + * + * @param dateString 标准形式的时间字符串 + * @return 日期对象 + */ + public static Date parseDateTime(CharSequence dateString) { + return parse(dateString, NORM_DATETIME_FORMAT); + } + + + /** + * 根据特定格式格式化日期 + * + * @param date 被格式化的日期 + * @param format {@link SimpleDateFormat} + * @return 格式化后的字符串 + */ + public static String format(Date date, DateFormat format) { + if (null == format || null == date) { + return null; + } + return format.format(date); + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/net/NetUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/net/NetUtils.java new file mode 100644 index 0000000..fbf2009 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/net/NetUtils.java @@ -0,0 +1,212 @@ +package cn.zyjblogs.starter.common.utils.net; + + +import cn.zyjblogs.starter.common.exception.UtilException; +import cn.zyjblogs.starter.common.function.Filter; +import cn.zyjblogs.starter.common.utils.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.net.*; +import java.util.*; + +/** + * 网络相关工具 + */ +public class NetUtils { + + private transient static String currentIp; + + public static boolean isLocalhost(String ip) { + return "127.0.0.1".equals(ip) + || "0:0:0:0:0:0:0:1".equals(ip) + || "localhost".equals(ip) + || "::1".equals(ip); + } + + /** + * 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个
+ * 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取。
+ * 此方法不会抛出异常,获取失败将返回null
+ *

+ * 参考:http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java + * + * @return 本机网卡IP地址,获取失败返回null + */ + public static String getInstanceIp() { + InetAddress localhost = getLocalhost(); + if (null != localhost) { + return localhost.getHostAddress(); + } + return null; + } + + public static String getInstanceIpWithCache() { + if (Objects.isNull(currentIp)) { + synchronized (NetUtils.class) { + if (Objects.isNull(currentIp)) { + InetAddress localhost = getLocalhost(); + currentIp = Objects.nonNull(localhost) ? localhost.getHostAddress() : StringUtils.EMPTY; + } + } + } + return currentIp; + } + + /** + * 获取本机网卡IP地址,规则如下: + * + *

+     * 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
+     * 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
+     * 
+ *

+ * 此方法不会抛出异常,获取失败将返回null
+ *

+ * + * @return 本机网卡IP地址,获取失败返回null + */ + public static InetAddress getLocalhost() { + final LinkedHashSet localAddressList = localAddressList(address -> { + // 非loopback地址,指127.*.*.*的地址 + return false == address.isLoopbackAddress() + // 非地区本地地址,指10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255 + && false == address.isSiteLocalAddress() + // 需为IPV4地址 + && address instanceof Inet4Address; + }); + + if (CollectionUtils.isNotEmpty(localAddressList)) { + return CollectionUtils.get(localAddressList, 0); + } + + try { + return InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + // ignore + } + + return null; + } + + /** + * 获得本机的IP地址列表(包括Ipv4和Ipv6)
+ * 返回的IP列表有序,按照系统设备顺序 + * + * @return IP地址列表 {@link LinkedHashSet} + */ + public static LinkedHashSet localIps() { + final LinkedHashSet localAddressList = localAddressList(null); + return toIpList(localAddressList); + } + + /** + * 地址列表转换为IP地址列表 + * + * @param addressList 地址{@link Inet4Address} 列表 + * @return IP地址字符串列表 + */ + public static LinkedHashSet toIpList(Set addressList) { + final LinkedHashSet ipSet = new LinkedHashSet<>(); + for (InetAddress address : addressList) { + ipSet.add(address.getHostAddress()); + } + + return ipSet; + } + + /** + * 获取所有满足过滤条件的本地IP地址对象 + * + * @param addressFilter 过滤器,null表示不过滤,获取所有地址 + * @return 过滤后的地址对象列表 + */ + public static LinkedHashSet localAddressList(Filter addressFilter) { + Enumeration networkInterfaces; + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new UtilException(e.getMessage()); + } + + if (networkInterfaces == null) { + throw new UtilException("Get network interface error!"); + } + + final LinkedHashSet ipSet = new LinkedHashSet<>(); + + while (networkInterfaces.hasMoreElements()) { + final NetworkInterface networkInterface = networkInterfaces.nextElement(); + final Enumeration inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + final InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress != null && (null == addressFilter || addressFilter.accept(inetAddress))) { + ipSet.add(inetAddress); + } + } + } + return ipSet; + } + + /** + * 获取指定名称的网卡信息 + * + * @param name 网络接口名,例如Linux下默认是eth0 + * @return 网卡,未找到返回null + */ + public static NetworkInterface getNetworkInterface(String name) { + Enumeration networkInterfaces; + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + return null; + } + + NetworkInterface netInterface; + while (networkInterfaces.hasMoreElements()) { + netInterface = networkInterfaces.nextElement(); + if (null != netInterface && name.equals(netInterface.getName())) { + return netInterface; + } + } + + return null; + } + + /** + * 获取本机所有网卡 + * + * @return 所有网卡,异常返回null + */ + public static Collection getNetworkInterfaces() { + Enumeration networkInterfaces; + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + return null; + } + + return CollectionUtils.addAll(new ArrayList<>(), networkInterfaces); + } + + public static String getHostName() { + try { + return InetAddress.getLocalHost().getCanonicalHostName(); + } catch (Throwable e) { + return null; + } + } + + public static boolean isPortAvailable(int port) { + Socket socket; + String host = "localhost"; + try { + socket = new Socket(host, port); + return true; + } catch (IOException e) { + return false; + } + + + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/CharUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/CharUtils.java new file mode 100644 index 0000000..436aebf --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/CharUtils.java @@ -0,0 +1,73 @@ +package cn.zyjblogs.starter.common.utils.string; + +/** + * 字符工具类
+ * 部分工具来自于Apache Commons系列 + * + * @author lingyi + */ +public class CharUtils { + public static final char DOT = '.'; + + private static final int ASCII_LENGTH = 128; + private static final String[] CACHE = new String[ASCII_LENGTH]; + + static { + for (char c = 0; c < ASCII_LENGTH; c++) { + CACHE[c] = String.valueOf(c); + } + } + + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @param c 字符 + * @return 是否空白符 + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + */ + public static boolean isBlankChar(char c) { + return isBlankChar((int) c); + } + + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @param c 字符 + * @return 是否空白符 + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + */ + public static boolean isBlankChar(int c) { + return Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a'; + } + + /** + * 给定对象对应的类是否为字符类,字符类包括: + * + *

+     * Character.class
+     * char.class
+     * 
+ * + * @param value 被检查的对象 + * @return true表示为字符类 + */ + public static boolean isChar(Object value) { + //noinspection ConstantConditions + return value instanceof Character || value.getClass() == char.class; + } + + /** + * 字符转为字符串
+ * 如果为ASCII字符,使用缓存 + * + * @param c 字符 + * @return 字符串 + */ + public static String toString(char c) { + return c < ASCII_LENGTH ? CACHE[c] : String.valueOf(c); + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/StringUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/StringUtils.java new file mode 100644 index 0000000..35040e0 --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/string/StringUtils.java @@ -0,0 +1,905 @@ +package cn.zyjblogs.starter.common.utils.string; + + +import cn.zyjblogs.starter.common.exception.UtilException; +import cn.zyjblogs.starter.common.io.UnsafeStringWriter; +import com.alibaba.fastjson2.JSON; +import org.apache.commons.lang3.ArrayUtils; + +import java.io.PrintWriter; + +/** + * StringUtils + * + * @author lingyi + */ +public class StringUtils { + + public static final int INDEX_NOT_FOUND = -1; + + public static final String EMPTY_JSON = "{}"; + public static final char BACKSLASH = '\\'; + public static final char DELIMIT_START = '{'; + public static final String NULL = "null"; + public static final String EMPTY = ""; + public static final String DOT = "."; + public static final String COMMA = ","; + public static final String SEMICOLON = ";"; + /** + * UTF-8 + */ + public static final String UTF_8 = "UTF-8"; + + /** + * is not empty string. + * + * @param str source string. + * @return is not empty. + */ + public static boolean isNotEmpty(CharSequence str) { + return !isEmpty(str); + } + + /** + * is empty string. + * + * @param str source string. + * @return is empty. + */ + public static boolean isEmpty(CharSequence str) { + return str == null || str.length() == 0; + } + + public static boolean isNotBlank(CharSequence cs) { + return !isBlank(cs); + } + + public static boolean isBlank(CharSequence cs) { + int strLen; + if (cs != null && (strLen = cs.length()) != 0) { + for (int i = 0; i < strLen; ++i) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + + return true; + } else { + return true; + } + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + if (isBlank(template) || ArrayUtils.isEmpty(params)) { + return template; + } + final int strPatternLength = template.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + // 记录已经处理到的位置 + int handledPosition = 0; + // 占位符所在位置 + int delimIndex; + for (int argIndex = 0; argIndex < params.length; argIndex++) { + delimIndex = template.indexOf(EMPTY_JSON, handledPosition); + // 剩余部分无占位符 + if (delimIndex == -1) { + // 不带占位符的模板直接返回 + if (handledPosition == 0) { + return template; + } + // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(template, handledPosition, strPatternLength); + return sbuf.toString(); + } + + // 转义符 + if (delimIndex > 0 && template.charAt(delimIndex - 1) == BACKSLASH) { + // 双转义符 + if (delimIndex > 1 && template.charAt(delimIndex - 2) == BACKSLASH) { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(template, handledPosition, delimIndex - 1); + sbuf.append(toJsonStr(params[argIndex])); + handledPosition = delimIndex + 2; + } else { + // 占位符被转义 + argIndex--; + sbuf.append(template, handledPosition, delimIndex - 1); + sbuf.append(DELIMIT_START); + handledPosition = delimIndex + 1; + } + } else {// 正常占位符 + sbuf.append(template, handledPosition, delimIndex); + sbuf.append(toJsonStr(params[argIndex])); + handledPosition = delimIndex + 2; + } + } + + // append the characters following the last {} pair. + // 加入最后一个占位符后所有的字符 + sbuf.append(template, handledPosition, template.length()); + + return sbuf.toString(); + } + + /** + * 将对象转为json字符串
+ * + * @param obj 对象 + * @return 字符串 + */ + private static String toJsonStr(Object obj) { + if (obj instanceof String) { + return (String) obj; + } + return JSON.toJSONString(obj); + } + + /** + * 切割指定位置之前部分的字符串 + * + * @param string 字符串 + * @param toIndex 切割到的位置(不包括) + * @return 切割后的剩余的前半部分字符串 + */ + public static String subPre(CharSequence string, int toIndex) { + return sub(string, 0, toIndex); + } + + /** + * 改进JDK subString
+ * index从0开始计算,最后一个字符为-1
+ * 如果from和to位置一样,返回 ""
+ * 如果from或to为负数,则按照length从后向前数位置,如果绝对值大于字符串长度,则from归到0,to归到length
+ * 如果经过修正的index中from大于to,则互换from和to example:
+ * abcdefgh 2 3 =》 c
+ * abcdefgh 2 -3 =》 cde
+ * + * @param str String + * @param fromIndex 开始的index(包括) + * @param toIndex 结束的index(不包括) + * @return 字串 + */ + public static String sub(CharSequence str, int fromIndex, int toIndex) { + if (isEmpty(str)) { + return str(str); + } + int len = str.length(); + + if (fromIndex < 0) { + fromIndex = len + fromIndex; + if (fromIndex < 0) { + fromIndex = 0; + } + } else if (fromIndex > len) { + fromIndex = len; + } + + if (toIndex < 0) { + toIndex = len + toIndex; + if (toIndex < 0) { + toIndex = len; + } + } else if (toIndex > len) { + toIndex = len; + } + + if (toIndex < fromIndex) { + int tmp = fromIndex; + fromIndex = toIndex; + toIndex = tmp; + } + + if (fromIndex == toIndex) { + return EMPTY; + } + + return str.toString().substring(fromIndex, toIndex); + } + + /** + * {@link CharSequence} 转为字符串,null安全 + * + * @param cs {@link CharSequence} + * @return 字符串 + */ + public static String str(CharSequence cs) { + return null == cs ? null : cs.toString(); + } + + /** + * 比较两个字符串是否相等。 + * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 + * @param ignoreCase 是否忽略大小写 + * @return 如果两个字符串相同,或者都是null,则返回true + */ + public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) { + if (null == str1) { + // 只有两个都为null才判断相等 + return str2 == null; + } + if (null == str2) { + // 字符串2空,字符串1非空,直接false + return false; + } + + if (ignoreCase) { + return str1.toString().equalsIgnoreCase(str2.toString()); + } else { + return str1.equals(str2); + } + } + + /** + * 第一个字符串是否包含第二个字符串 + * + * @param str1 字符串1 + * @param str2 字符串2 + */ + public static boolean contains(CharSequence str1, CharSequence str2) { + if (null == str1 || null == str2) { + return false; + } + return str1.toString().contains(str2); + } + + /** + * @param e + * @return string + */ + public static String toString(Throwable e) { + UnsafeStringWriter w = new UnsafeStringWriter(); + PrintWriter p = new PrintWriter(w); + p.print(e.getClass().getName()); + if (e.getMessage() != null) { + p.print(": " + e.getMessage()); + } + p.println(); + try { + e.printStackTrace(p); + return w.toString(); + } finally { + p.close(); + } + } + + public static String camelToSplitName(String camelName, String split) { + if (isEmpty(camelName)) { + return camelName; + } + StringBuilder buf = null; + for (int i = 0; i < camelName.length(); i++) { + char ch = camelName.charAt(i); + if (ch >= 'A' && ch <= 'Z') { + if (buf == null) { + buf = new StringBuilder(); + if (i > 0) { + buf.append(camelName, 0, i); + } + } + if (i > 0) { + buf.append(split); + } + buf.append(Character.toLowerCase(ch)); + } else if (buf != null) { + buf.append(ch); + } + } + return buf == null ? camelName : buf.toString(); + } + + // ------------------------------------------------------------------------ Trim + + /** + * 除去字符串头尾部的空白,如果字符串是null,依然返回null。 + * + *

+ * 注意,和String.trim不同,此方法使用NumberUtil.isBlankChar 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 + * + *

+     * trim(null)          = null
+     * trim("")            = ""
+     * trim("     ")       = ""
+     * trim("abc")         = "abc"
+     * trim("    abc    ") = "abc"
+     * 
+ * + * @param str 要处理的字符串 + * @return 除去头尾空白的字符串,如果原字串为null,则返回null + */ + public static String trim(CharSequence str) { + return (null == str) ? null : trim(str, 0); + } + + /** + * 除去字符串头尾部的空白符,如果字符串是null,依然返回null。 + * + * @param str 要处理的字符串 + * @param mode -1表示trimStart,0表示trim全部, 1表示trimEnd + * @return 除去指定字符后的的字符串,如果原字串为null,则返回null + */ + public static String trim(CharSequence str, int mode) { + if (str == null) { + return null; + } + + int length = str.length(); + int start = 0; + int end = length; + + // 扫描字符串头部 + if (mode <= 0) { + while ((start < end) && (CharUtils.isBlankChar(str.charAt(start)))) { + start++; + } + } + + // 扫描字符串尾部 + if (mode >= 0) { + while ((start < end) && (CharUtils.isBlankChar(str.charAt(end - 1)))) { + end--; + } + } + + if ((start > 0) || (end < length)) { + return str.toString().substring(start, end); + } + + return str.toString(); + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning null if the String is + * empty ("") after the trim or if it is null. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToNull(String)}.

+ * + *
+     * StringUtils.trimToNull(null)          = null
+     * StringUtils.trimToNull("")            = null
+     * StringUtils.trimToNull("     ")       = null
+     * StringUtils.trimToNull("abc")         = "abc"
+     * StringUtils.trimToNull("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, + * null if only chars <= 32, empty or null String input + * @since 2.0 + */ + public static String trimToNull(String str) { + String ts = trim(str); + return isEmpty(ts) ? null : ts; + } + + /** + *

Removes control characters (char <= 32) from both + * ends of this String returning an empty String ("") if the String + * is empty ("") after the trim or if it is null. + * + *

The String is trimmed using {@link String#trim()}. + * Trim removes start and end characters <= 32. + * To strip whitespace use {@link #stripToEmpty(String)}.

+ * + *
+     * StringUtils.trimToEmpty(null)          = ""
+     * StringUtils.trimToEmpty("")            = ""
+     * StringUtils.trimToEmpty("     ")       = ""
+     * StringUtils.trimToEmpty("abc")         = "abc"
+     * StringUtils.trimToEmpty("    abc    ") = "abc"
+     * 
+ * + * @param str the String to be trimmed, may be null + * @return the trimmed String, or an empty String if null input + * @since 2.0 + */ + public static String trimToEmpty(String str) { + return str == null ? EMPTY : str.trim(); + } + + + // Stripping + //----------------------------------------------------------------------- + + /** + *

Strips whitespace from the start and end of a String.

+ *

+ * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A null input String returns null.

+ * + *
+     * StringUtils.strip(null)     = null
+     * StringUtils.strip("")       = ""
+     * StringUtils.strip("   ")    = ""
+     * StringUtils.strip("abc")    = "abc"
+     * StringUtils.strip("  abc")  = "abc"
+     * StringUtils.strip("abc  ")  = "abc"
+     * StringUtils.strip(" abc ")  = "abc"
+     * StringUtils.strip(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to remove whitespace from, may be null + * @return the stripped String, null if null String input + */ + public static String strip(String str) { + return strip(str, null); + } + + /** + *

Strips whitespace from the start and end of a String returning + * null if the String is empty ("") after the strip.

+ * + *

This is similar to {@link #trimToNull(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToNull(null)     = null
+     * StringUtils.stripToNull("")       = null
+     * StringUtils.stripToNull("   ")    = null
+     * StringUtils.stripToNull("abc")    = "abc"
+     * StringUtils.stripToNull("  abc")  = "abc"
+     * StringUtils.stripToNull("abc  ")  = "abc"
+     * StringUtils.stripToNull(" abc ")  = "abc"
+     * StringUtils.stripToNull(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the stripped String, + * null if whitespace, empty or null String input + * @since 2.0 + */ + public static String stripToNull(String str) { + if (str == null) { + return null; + } + str = strip(str, null); + return str.length() == 0 ? null : str; + } + + /** + *

Strips whitespace from the start and end of a String returning + * an empty String if null input.

+ * + *

This is similar to {@link #trimToEmpty(String)} but removes whitespace. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripToEmpty(null)     = ""
+     * StringUtils.stripToEmpty("")       = ""
+     * StringUtils.stripToEmpty("   ")    = ""
+     * StringUtils.stripToEmpty("abc")    = "abc"
+     * StringUtils.stripToEmpty("  abc")  = "abc"
+     * StringUtils.stripToEmpty("abc  ")  = "abc"
+     * StringUtils.stripToEmpty(" abc ")  = "abc"
+     * StringUtils.stripToEmpty(" ab c ") = "ab c"
+     * 
+ * + * @param str the String to be stripped, may be null + * @return the trimmed String, or an empty String if null input + * @since 2.0 + */ + public static String stripToEmpty(String str) { + return str == null ? EMPTY : strip(str, null); + } + + /** + *

Strips any of a set of characters from the start and end of a String. + * This is similar to {@link String#trim()} but allows the characters + * to be stripped to be controlled.

+ * + *

A null input String returns null. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is null, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}. + * Alternatively use {@link #strip(String)}.

+ * + *
+     * StringUtils.strip(null, *)          = null
+     * StringUtils.strip("", *)            = ""
+     * StringUtils.strip("abc", null)      = "abc"
+     * StringUtils.strip("  abc", null)    = "abc"
+     * StringUtils.strip("abc  ", null)    = "abc"
+     * StringUtils.strip(" abc ", null)    = "abc"
+     * StringUtils.strip("  abcyx", "xyz") = "  abc"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, null if null String input + */ + public static String strip(String str, String stripChars) { + if (isEmpty(str)) { + return str; + } + str = stripStart(str, stripChars); + return stripEnd(str, stripChars); + } + + /** + *

Strips any of a set of characters from the start of a String.

+ * + *

A null input String returns null. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is null, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripStart(null, *)          = null
+     * StringUtils.stripStart("", *)            = ""
+     * StringUtils.stripStart("abc", "")        = "abc"
+     * StringUtils.stripStart("abc", null)      = "abc"
+     * StringUtils.stripStart("  abc", null)    = "abc"
+     * StringUtils.stripStart("abc  ", null)    = "abc  "
+     * StringUtils.stripStart(" abc ", null)    = "abc "
+     * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped String, null if null String input + */ + public static String stripStart(String str, String stripChars) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + int start = 0; + if (stripChars == null) { + while ((start != strLen) && Character.isWhitespace(str.charAt(start))) { + start++; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND)) { + start++; + } + } + return str.substring(start); + } + + /** + *

Strips any of a set of characters from the end of a String.

+ * + *

A null input String returns null. + * An empty string ("") input returns the empty string.

+ * + *

If the stripChars String is null, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripEnd(null, *)          = null
+     * StringUtils.stripEnd("", *)            = ""
+     * StringUtils.stripEnd("abc", "")        = "abc"
+     * StringUtils.stripEnd("abc", null)      = "abc"
+     * StringUtils.stripEnd("  abc", null)    = "  abc"
+     * StringUtils.stripEnd("abc  ", null)    = "abc"
+     * StringUtils.stripEnd(" abc ", null)    = " abc"
+     * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
+     * StringUtils.stripEnd("120.00", ".0")   = "12"
+     * 
+ * + * @param str the String to remove characters from, may be null + * @param stripChars the set of characters to remove, null treated as whitespace + * @return the stripped String, null if null String input + */ + public static String stripEnd(String str, String stripChars) { + int end; + if (str == null || (end = str.length()) == 0) { + return str; + } + + if (stripChars == null) { + while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + } else if (stripChars.length() == 0) { + return str; + } else { + while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND)) { + end--; + } + } + return str.substring(0, end); + } + + // StripAll + //----------------------------------------------------------------------- + + /** + *

Strips whitespace from the start and end of every String in an array. + * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A new array is returned each time, except for length zero. + * A null array will return null. + * An empty array will return itself. + * A null array entry will be ignored.

+ * + *
+     * StringUtils.stripAll(null)             = null
+     * StringUtils.stripAll([])               = []
+     * StringUtils.stripAll(["abc", "  abc"]) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null])  = ["abc", null]
+     * 
+ * + * @param strs the array to remove whitespace from, may be null + * @return the stripped Strings, null if null array input + */ + public static String[] stripAll(String[] strs) { + return stripAll(strs, null); + } + + /** + *

Strips any of a set of characters from the start and end of every + * String in an array.

+ * Whitespace is defined by {@link Character#isWhitespace(char)}.

+ * + *

A new array is returned each time, except for length zero. + * A null array will return null. + * An empty array will return itself. + * A null array entry will be ignored. + * A null stripChars will strip whitespace as defined by + * {@link Character#isWhitespace(char)}.

+ * + *
+     * StringUtils.stripAll(null, *)                = null
+     * StringUtils.stripAll([], *)                  = []
+     * StringUtils.stripAll(["abc", "  abc"], null) = ["abc", "abc"]
+     * StringUtils.stripAll(["abc  ", null], null)  = ["abc", null]
+     * StringUtils.stripAll(["abc  ", null], "yz")  = ["abc  ", null]
+     * StringUtils.stripAll(["yabcz", null], "yz")  = ["abc", null]
+     * 
+ * + * @param strs the array to remove characters from, may be null + * @param stripChars the characters to remove, null treated as whitespace + * @return the stripped Strings, null if null array input + */ + public static String[] stripAll(String[] strs, String stripChars) { + int strsLen; + if (strs == null || (strsLen = strs.length) == 0) { + return strs; + } + String[] newArr = new String[strsLen]; + for (int i = 0; i < strsLen; i++) { + newArr[i] = strip(strs[i], stripChars); + } + return newArr; + } + + + /** + * 截取分隔字符串之前的字符串,不包括分隔字符串
+ * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
+ * 如果分隔字符串未找到,返回原字符串,举例如下: + * + *
+     * StrUtil.subBefore(null, *)      = null
+     * StrUtil.subBefore("", *)        = ""
+     * StrUtil.subBefore("abc", 'a')   = ""
+     * StrUtil.subBefore("abcba", 'b') = "a"
+     * StrUtil.subBefore("abc", 'c')   = "ab"
+     * StrUtil.subBefore("abc", 'd')   = "abc"
+     * 
+ * + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) + * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 + * @return 切割后的字符串 + */ + public static String subBefore(CharSequence string, char separator, boolean isLastSeparator) { + if (isEmpty(string)) { + return null == string ? null : string.toString(); + } + + final String str = string.toString(); + final int pos = isLastSeparator ? str.lastIndexOf(separator) : str.indexOf(separator); + if (INDEX_NOT_FOUND == pos) { + return str; + } + if (0 == pos) { + return EMPTY; + } + return str.substring(0, pos); + } + + public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) { + return endsWith(str, suffix, true); + } + + /** + * 是否以指定字符串结尾
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str 被监测字符串 + * @param suffix 开头字符串 + * @param ignoreCase 是否忽略大小写 + * @return 是否以指定字符串结尾 + */ + private static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) { + if (null == str || null == suffix) { + return null == str && null == suffix; + } + + if (ignoreCase) { + return str.toString().toLowerCase().endsWith(suffix.toString().toLowerCase()); + } else { + return str.toString().endsWith(suffix.toString()); + } + } + + /** + * 是否以指定字符串开头,忽略大小写 + * + * @param str 被监测字符串 + * @param prefix 开头字符串 + * @return 是否以指定字符串开头 + */ + public static boolean startWithIgnoreCase(CharSequence str, CharSequence prefix) { + return startWith(str, prefix, true); + } + + /** + * 是否以指定字符串开头
+ * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false + * + * @param str 被监测字符串 + * @param prefix 开头字符串 + * @param isIgnoreCase 是否忽略大小写 + * @return 是否以指定字符串开头 + */ + public static boolean startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) { + if (null == str || null == prefix) { + return null == str && null == prefix; + } + + if (isIgnoreCase) { + return str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase()); + } else { + return str.toString().startsWith(prefix.toString()); + } + } + + /** + * 去除字符串中指定的多个字符,如有多个则全部去除 + * + * @param str 字符串 + * @param chars 字符列表 + * @return 去除后的字符 + */ + public static String removeAll(CharSequence str, char... chars) { + if (null == str || ArrayUtils.isEmpty(chars)) { + return str(str); + } + final int len = str.length(); + if (0 == len) { + return str(str); + } + final StringBuilder builder = new StringBuilder(len); + char c; + for (int i = 0; i < len; i++) { + c = str.charAt(i); + if (false == ArrayUtils.contains(chars, c)) { + builder.append(c); + } + } + return builder.toString(); + } + + public static String padStart(String string, int minLength, char padChar) { + if (string == null) { + throw new UtilException("string is null"); + } + + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + sb.append(string); + return sb.toString(); + } + + public static String padEnd(String string, int minLength, char padChar) { + if (string == null) { + throw new UtilException("string is null"); + } + + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + sb.append(string); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + return sb.toString(); + } + + public static String padMiddle(int maxLength, String start, String end, char padChar) { + int leftLength = start.length(); + int rightLength = end.length(); + int textLength = leftLength + rightLength; + if (maxLength <= textLength) { + return start + end; + } + int padLength = maxLength - rightLength; + return padEnd(start, padLength, padChar) + end; + } + + public static String lenientFormat(String template, Object... args) { + template = String.valueOf(template); // null -> "null" + + if (args == null) { + args = new Object[]{"(Object[])null"}; + } else { + for (int i = 0; i < args.length; i++) { + args[i] = lenientToString(args[i]); + } + } + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template, templateStart, template.length()); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } + + private static String lenientToString(Object o) { + try { + return String.valueOf(o); + } catch (Throwable e) { + // Default toString() behavior - see Object.toString() + String objectToString = + o.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(o)); + + return "<" + objectToString + " threw " + e.getClass().getName() + ">"; + } + } +} diff --git a/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/type/TypeUtils.java b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/type/TypeUtils.java new file mode 100644 index 0000000..e3d244e --- /dev/null +++ b/stater/zyjblogs-common-spring-boot-starter/src/main/java/cn/zyjblogs/starter/common/utils/type/TypeUtils.java @@ -0,0 +1,296 @@ +package cn.zyjblogs.starter.common.utils.type; + +import org.apache.commons.lang3.ArrayUtils; + +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Type的工具类封装 + * + * @author lingyi + */ +public class TypeUtils { + + public static List getGenericParamType(Class source, Class target) { + Type directTargetInterface = findDirectTargetInterface(source, target); + if (directTargetInterface != null) { + return findActualTypeArguments(directTargetInterface); + } else { + Map parentTypeArgumentMap = findParentTypeArgumentMap(source); + Type targetInterface = findTargetInterfaceRecursive(source, target); + List actualTypeArguments = findActualTypeArguments(targetInterface); + return actualTypeArguments.stream().map(type -> { + if (type instanceof TypeVariable) { + String name = ((TypeVariable) type).getName(); + return parentTypeArgumentMap.get(name); + } + return type; + }).collect(Collectors.toList()); + } + } + + /** + * 获取直接继承目标接口, 此接口是泛型接口 + * + * @param source + * @param target + * @return + */ + public static ParameterizedType findDirectTargetInterface(Class source, Class target) { + // 类实现的接口(直接实现), 返回类型包含了泛型信息 + Type[] genericInterfaces = source.getGenericInterfaces(); + for (Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) genericInterface).getRawType(); + // 是否是目标接口 + if (rawType.equals(target)) { + return (ParameterizedType) genericInterface; + } + } + } + return null; + } + + + public static ParameterizedType findTargetInterfaceRecursive(Class source, Class target) { + while (source != null && !source.equals(Object.class)) { + ParameterizedType directTargetInterface = findDirectTargetInterface(source, target); + if (directTargetInterface != null) { + return directTargetInterface; + } + source = source.getSuperclass(); + } + return null; + } + + /** + *
+     * 获取泛型的实际类型
+     * 如果泛型是 List 这种, 继续找到D
+     * Type 可能是实际类型, 也可能是TypeVariableImpl
+     * 
+ * + * @param type + * @return + */ + public static List findActualTypeArguments(Type type) { + List list = new ArrayList(); + if (type instanceof ParameterizedType) { + Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); + for (Type actualTypeArgument : actualTypeArguments) { + if (actualTypeArgument instanceof ParameterizedType) { + list.add(findActualTypeArgumentIfRawTypeIfList(actualTypeArgument)); + } else { + list.add(actualTypeArgument); + } + } + } + return list; + } + + public static Type findActualTypeArgumentIfRawTypeIfList(Type type) { + // 如果是 ParameterizedType, 说明泛型参数是有嵌套的 + if (type instanceof ParameterizedType) { + Type rawType = ((ParameterizedType) type).getRawType(); + // 获取本层的泛型, 判断是否List, 是则递归 + Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (List.class.isAssignableFrom((Class) rawType)) { + return findActualTypeArgumentIfRawTypeIfList(actualTypeArgument); + } else { + return rawType; + } + } else { + return type; + } + } + + /** + * @param source + * @return {{"T", ParameterizedType}} + */ + public static Map findParentTypeArgumentMap(Class source) { + Map map = new HashMap(1 << 3); + while (source != null && !source.equals(Object.class)) { + // 返回直接父类, 会带上泛型参数 + Type genericSuperclass = source.getGenericSuperclass(); + if (genericSuperclass instanceof ParameterizedType) { + Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); + TypeVariable[] typeParameters = source.getSuperclass().getTypeParameters(); + for (int i = 0; i < typeParameters.length; i++) { + Type actualTypeArgument = actualTypeArguments[i]; + TypeVariable typeParameter = typeParameters[i]; + Type actualTypeArgumentIfRawType = findActualTypeArgumentIfRawTypeIfList(actualTypeArgument); + // 不包含并且类型是Class类 + if (!map.containsKey(typeParameter.getName()) && actualTypeArgumentIfRawType instanceof Class) { + map.put(typeParameter.getName(), actualTypeArgumentIfRawType); + } + } + } + source = source.getSuperclass(); + } + return map; + } + + /** + * 获取所有的字段,包括父类 + * + * @param clazz + * @return + */ + public static List getDeclaredFields(Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("clazz param can not be null"); + } + List classes = new ArrayList<>(); + while (clazz != null && clazz != Object.class) { + classes.add(clazz); + clazz = clazz.getSuperclass(); + } + List fields = new ArrayList<>(); + for (int i = classes.size() - 1; i >= 0; i--) { + final Class aClass = classes.get(i); + fields.addAll(new ArrayList<>(Arrays.asList(aClass.getDeclaredFields()))); + } + return fields; + } + + public static Set> flattenHierarchy(Class concreteClass) { + Set> classes = new HashSet<>(); + classes.add(concreteClass); + while (concreteClass != null && !concreteClass.equals(Object.class)) { + Class[] interfaces = concreteClass.getInterfaces(); + if (interfaces != null) { + for (Class anInterface : interfaces) { + classes.addAll(flattenHierarchy(anInterface)); + } + } + concreteClass = concreteClass.getSuperclass(); + if (concreteClass != null) { + classes.add(concreteClass); + } + } + return classes; + } + // ----------------------------------------------------------------------------------- Type Argument + + /** + * 是否未知类型
+ * type为null或者{@link TypeVariable} 都视为未知类型 + * + * @param type Type类型 + * @return 是否未知类型 + */ + public static boolean isUnknow(Type type) { + return null == type || type instanceof TypeVariable; + } + + /** + * 获得给定类的第一个泛型参数 + * + * @param type 被检查的类型,必须是已经确定泛型类型的类型 + * @return {@link Type},可能为{@code null} + */ + public static Type getTypeArgument(Type type) { + return getTypeArgument(type, 0); + } + + /** + * 获得给定类的泛型参数 + * + * @param type 被检查的类型,必须是已经确定泛型类型的类 + * @param index 泛型类型的索引号,即第几个泛型类型 + * @return {@link Type} + */ + public static Type getTypeArgument(Type type, int index) { + final Type[] typeArguments = getTypeArguments(type); + if (null != typeArguments && typeArguments.length > index) { + return typeArguments[index]; + } + return null; + } + + /** + * 获得指定类型中所有泛型参数类型,例如: + * + *
+     * class A<T>
+     * class B extends A<String>
+     * 
+ *

+ * 通过此方法,传入B.class即可得到String + * + * @param type 指定类型 + * @return 所有泛型参数类型 + */ + public static Type[] getTypeArguments(Type type) { + if (null == type) { + return null; + } + + final ParameterizedType parameterizedType = toParameterizedType(type); + return (null == parameterizedType) ? null : parameterizedType.getActualTypeArguments(); + } + + /** + * 获得Type对应的原始类 + * + * @param type {@link Type} + * @return 原始类,如果无法获取原始类,返回{@code null} + */ + public static Class getClass(Type type) { + if (null != type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return (Class) ((ParameterizedType) type).getRawType(); + } else if (type instanceof TypeVariable) { + return (Class) ((TypeVariable) type).getBounds()[0]; + } else if (type instanceof WildcardType) { + final Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + if (upperBounds.length == 1) { + return getClass(upperBounds[0]); + } + } + } + return null; + } + + + /** + * 将{@link Type} 转换为{@link ParameterizedType}
+ * {@link ParameterizedType}用于获取当前类或父类中泛型参数化后的类型
+ * 一般用于获取泛型参数具体的参数类型,例如: + * + *

+     * class A<T>
+     * class B extends A<String>
+     * 
+ *

+ * 通过此方法,传入B.class即可得到B{@link ParameterizedType},从而获取到String + * + * @param type {@link Type} + * @return {@link ParameterizedType} + */ + public static ParameterizedType toParameterizedType(Type type) { + ParameterizedType result = null; + if (type instanceof ParameterizedType) { + result = (ParameterizedType) type; + } else if (type instanceof Class) { + final Class clazz = (Class) type; + Type genericSuper = clazz.getGenericSuperclass(); + if (null == genericSuper || Object.class.equals(genericSuper)) { + // 如果类没有父类,而是实现一些定义好的泛型接口,则取接口的Type + final Type[] genericInterfaces = clazz.getGenericInterfaces(); + if (ArrayUtils.isNotEmpty(genericInterfaces)) { + // 默认取第一个实现接口的泛型Type + genericSuper = genericInterfaces[0]; + } + } + result = toParameterizedType(genericSuper); + } + return result; + } + +}