mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-12-06 17:18:54 +08:00
优化EscapeUtil,兼容不规范的转义(pr#4150@Github)
This commit is contained in:
parent
13a04feab7
commit
7bd0585a39
@ -1790,12 +1790,13 @@ public class CollUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取集合中指定多个下标的元素值,下标可以为负数,例如-1表示最后一个元素
|
* 获取集合中指定多个下标的元素值,下标可以为负数,例如-1表示最后一个元素<br>
|
||||||
|
* 如果下标越界(转换负数索引后仍然越界),则抛出异常
|
||||||
*
|
*
|
||||||
* @param <T> 元素类型
|
* @param <T> 元素类型
|
||||||
* @param collection 集合
|
* @param collection 集合
|
||||||
* @param indexes 下标,支持负数
|
* @param indexes 下标列表,支持负数(负数表示从末尾倒数,-1为最后一个元素,-2为倒数第二个,以此类推)
|
||||||
* @return 元素值列表
|
* @return 元素值列表,返回 {@link ArrayList},只包含有效下标对应的元素
|
||||||
* @since 4.0.6
|
* @since 4.0.6
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@ -243,7 +243,7 @@ public class IterUtil {
|
|||||||
* @param <V> 对象类型
|
* @param <V> 对象类型
|
||||||
* @param iterable 对象列表
|
* @param iterable 对象列表
|
||||||
* @param fieldName 字段名(会通过反射获取其值)
|
* @param fieldName 字段名(会通过反射获取其值)
|
||||||
* @return 某个字段值与对象对应Map
|
* @return 字段值列表
|
||||||
* @since 4.6.2
|
* @since 4.6.2
|
||||||
*/
|
*/
|
||||||
public static <V, R> List<R> fieldValueList(final Iterable<V> iterable, final String fieldName) {
|
public static <V, R> List<R> fieldValueList(final Iterable<V> iterable, final String fieldName) {
|
||||||
@ -257,7 +257,7 @@ public class IterUtil {
|
|||||||
* @param <V> 对象类型
|
* @param <V> 对象类型
|
||||||
* @param iter 对象列表
|
* @param iter 对象列表
|
||||||
* @param fieldName 字段名(会通过反射获取其值)
|
* @param fieldName 字段名(会通过反射获取其值)
|
||||||
* @return 某个字段值与对象对应Map
|
* @return 字段值列表
|
||||||
* @since 4.0.10
|
* @since 4.0.10
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@ -512,10 +512,11 @@ public class CalendarUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定日期字段的最小值,例如分钟的最小值是0
|
* 获取指定日期字段的最小值,例如分钟的最小值是0<br>
|
||||||
|
* 对于 {@link DateField#DAY_OF_WEEK} 字段,会返回一周的第一天,详见 {@link #getBeginValue(Calendar, int)}
|
||||||
*
|
*
|
||||||
* @param calendar {@link Calendar}
|
* @param calendar {@link Calendar}
|
||||||
* @param dateField {@link DateField}
|
* @param dateField {@link DateField} 日期字段
|
||||||
* @return 字段最小值
|
* @return 字段最小值
|
||||||
* @see Calendar#getActualMinimum(int)
|
* @see Calendar#getActualMinimum(int)
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
@ -525,7 +526,10 @@ public class CalendarUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定日期字段的最小值,例如分钟的最小值是0
|
* 获取指定日期字段的最小值,例如分钟的最小值是0<br>
|
||||||
|
* 对于 {@link Calendar#DAY_OF_WEEK} 字段,需要特殊处理:<br>
|
||||||
|
* 由于 DAY_OF_WEEK 的值范围是 1-7,取决于一周的第一天设置(周日或周一),<br>
|
||||||
|
* 本方法会直接返回 {@link Calendar#getFirstDayOfWeek()} 的值作为一周的第一天。
|
||||||
*
|
*
|
||||||
* @param calendar {@link Calendar}
|
* @param calendar {@link Calendar}
|
||||||
* @param dateField {@link DateField}
|
* @param dateField {@link DateField}
|
||||||
@ -535,18 +539,21 @@ public class CalendarUtil {
|
|||||||
*/
|
*/
|
||||||
public static int getBeginValue(final Calendar calendar, final int dateField) {
|
public static int getBeginValue(final Calendar calendar, final int dateField) {
|
||||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||||
|
// DAY_OF_WEEK 的值范围是 1-7,直接返回一周的第一天
|
||||||
return calendar.getFirstDayOfWeek();
|
return calendar.getFirstDayOfWeek();
|
||||||
}
|
}
|
||||||
return calendar.getActualMinimum(dateField);
|
return calendar.getActualMinimum(dateField);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定日期字段的最大值,例如分钟的最大值是59
|
* 获取指定日期字段的最大值,例如分钟的最大值是59<br>
|
||||||
|
* 对于 {@link DateField#DAY_OF_WEEK} 字段,会根据一周的第一天设置计算最后一天,详见 {@link #getEndValue(Calendar, int)}
|
||||||
*
|
*
|
||||||
* @param calendar {@link Calendar}
|
* @param calendar {@link Calendar}
|
||||||
* @param dateField {@link DateField}
|
* @param dateField {@link DateField} 日期字段
|
||||||
* @return 字段最大值
|
* @return 字段最大值
|
||||||
* @see Calendar#getActualMaximum(int)
|
* @see Calendar#getActualMaximum(int)
|
||||||
|
* @see #getEndValue(Calendar, int)
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
*/
|
*/
|
||||||
public static int getEndValue(final Calendar calendar, final DateField dateField) {
|
public static int getEndValue(final Calendar calendar, final DateField dateField) {
|
||||||
@ -554,16 +561,22 @@ public class CalendarUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定日期字段的最大值,例如分钟的最大值是59
|
* 获取指定日期字段的最大值,例如分钟的最大值是59<br>
|
||||||
|
* 对于 {@link Calendar#DAY_OF_WEEK} 字段,需要特殊处理:<br>
|
||||||
|
* 由于 DAY_OF_WEEK 的值范围是 1-7,取决于一周的第一天设置(周日或周一),<br>
|
||||||
|
* 本方法会根据 {@link Calendar#getFirstDayOfWeek()} 计算出一周的最后一天。<br>
|
||||||
|
* 计算逻辑:最后一天 = 第一天 + 6,如果结果 > 7,则减去 7 以保持在 1-7 范围内。<br>
|
||||||
|
* 例如:如果第一天是周一(2),则最后一天是周日(1);如果第一天是周日(1),则最后一天是周六(7)。
|
||||||
*
|
*
|
||||||
* @param calendar {@link Calendar}
|
* @param calendar {@link Calendar}
|
||||||
* @param dateField {@link DateField}
|
* @param dateField {@link DateField} 日期字段,使用 {@link Calendar} 中的字段常量,如 {@link Calendar#MINUTE}、{@link Calendar#DAY_OF_WEEK} 等
|
||||||
* @return 字段最大值
|
* @return 字段最大值
|
||||||
* @see Calendar#getActualMaximum(int)
|
* @see Calendar#getActualMaximum(int)
|
||||||
* @since 4.5.7
|
* @since 4.5.7
|
||||||
*/
|
*/
|
||||||
public static int getEndValue(final Calendar calendar, final int dateField) {
|
public static int getEndValue(final Calendar calendar, final int dateField) {
|
||||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||||
|
// DAY_OF_WEEK 的值范围是 1-7,根据一周的第一天计算最后一天
|
||||||
return (calendar.getFirstDayOfWeek() + 6) % 7;
|
return (calendar.getFirstDayOfWeek() + 6) % 7;
|
||||||
}
|
}
|
||||||
return calendar.getActualMaximum(dateField);
|
return calendar.getActualMaximum(dateField);
|
||||||
@ -584,10 +597,10 @@ public class CalendarUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calendar{@link Instant}对象
|
* 将 {@link Calendar} 转换为 {@link Instant} 对象
|
||||||
*
|
*
|
||||||
* @param calendar Date对象
|
* @param calendar {@link Calendar} 对象
|
||||||
* @return {@link Instant}对象
|
* @return {@link Instant} 对象,如果 calendar 为 {@code null} 则返回 {@code null}
|
||||||
* @since 5.0.5
|
* @since 5.0.5
|
||||||
*/
|
*/
|
||||||
public static Instant toInstant(final Calendar calendar) {
|
public static Instant toInstant(final Calendar calendar) {
|
||||||
|
|||||||
@ -165,7 +165,7 @@ public class StopWatch {
|
|||||||
/**
|
/**
|
||||||
* 开始默认的新任务
|
* 开始默认的新任务
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException 前一个任务没有结束
|
* @throws IllegalStateException 当前已有任务正在运行时抛出此异常,必须先调用{@link #stop()}停止当前任务
|
||||||
*/
|
*/
|
||||||
public void start() throws IllegalStateException {
|
public void start() throws IllegalStateException {
|
||||||
start(StrUtil.EMPTY);
|
start(StrUtil.EMPTY);
|
||||||
@ -175,7 +175,7 @@ public class StopWatch {
|
|||||||
* 开始指定名称的新任务
|
* 开始指定名称的新任务
|
||||||
*
|
*
|
||||||
* @param taskName 新开始的任务名称
|
* @param taskName 新开始的任务名称
|
||||||
* @throws IllegalStateException 前一个任务没有结束
|
* @throws IllegalStateException 当前已有任务正在运行时抛出此异常,必须先调用{@link #stop()}停止当前任务
|
||||||
*/
|
*/
|
||||||
public void start(final String taskName) throws IllegalStateException {
|
public void start(final String taskName) throws IllegalStateException {
|
||||||
if (null != this.currentTaskName) {
|
if (null != this.currentTaskName) {
|
||||||
@ -188,7 +188,7 @@ public class StopWatch {
|
|||||||
/**
|
/**
|
||||||
* 停止当前任务
|
* 停止当前任务
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException 任务没有开始
|
* @throws IllegalStateException 当前没有正在运行的任务时抛出此异常,必须先调用{@link #start()}或{@link #start(String)}开始任务
|
||||||
*/
|
*/
|
||||||
public void stop() throws IllegalStateException {
|
public void stop() throws IllegalStateException {
|
||||||
if (null == this.currentTaskName) {
|
if (null == this.currentTaskName) {
|
||||||
|
|||||||
@ -229,7 +229,7 @@ public class IoUtil extends NioUtil {
|
|||||||
/**
|
/**
|
||||||
* 获得一个Writer,默认编码UTF-8
|
* 获得一个Writer,默认编码UTF-8
|
||||||
*
|
*
|
||||||
* @param out 输入流
|
* @param out 输出流
|
||||||
* @return OutputStreamWriter对象
|
* @return OutputStreamWriter对象
|
||||||
* @since 5.1.6
|
* @since 5.1.6
|
||||||
*/
|
*/
|
||||||
@ -240,7 +240,7 @@ public class IoUtil extends NioUtil {
|
|||||||
/**
|
/**
|
||||||
* 获得一个Writer
|
* 获得一个Writer
|
||||||
*
|
*
|
||||||
* @param out 输入流
|
* @param out 输出流
|
||||||
* @param charset 字符集
|
* @param charset 字符集
|
||||||
* @return OutputStreamWriter对象
|
* @return OutputStreamWriter对象
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -49,6 +49,15 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
super(raw);
|
super(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将指定的键值对放入Map中,同时同步更新反向Map
|
||||||
|
* <p>
|
||||||
|
* 如果key已存在但value不同,会在反向Map中移除旧的value到key的映射,并建立新的value到key的映射
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return 与key关联的旧值,如果key不存在则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V put(final K key, final V value) {
|
public V put(final K key, final V value) {
|
||||||
final V oldValue = super.put(key, value);
|
final V oldValue = super.put(key, value);
|
||||||
@ -63,6 +72,11 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return oldValue;
|
return oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将指定Map中的所有映射关系复制到此Map中,同时同步更新反向Map
|
||||||
|
*
|
||||||
|
* @param m 要存储在此Map中的映射关系
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void putAll(final Map<? extends K, ? extends V> m) {
|
public void putAll(final Map<? extends K, ? extends V> m) {
|
||||||
super.putAll(m);
|
super.putAll(m);
|
||||||
@ -71,6 +85,12 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Map中移除指定键的映射关系,同时同步从反向Map中移除对应的反向映射
|
||||||
|
*
|
||||||
|
* @param key 要从Map中移除其映射关系的键
|
||||||
|
* @return 与key关联的旧值,如果key不存在则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V remove(final Object key) {
|
public V remove(final Object key) {
|
||||||
final V v = super.remove(key);
|
final V v = super.remove(key);
|
||||||
@ -80,11 +100,21 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅当指定键当前映射到指定值时才移除该键的映射关系,同时同步从反向Map中移除对应的反向映射
|
||||||
|
*
|
||||||
|
* @param key 与指定值关联的键
|
||||||
|
* @param value 与指定键关联的值
|
||||||
|
* @return 如果删除成功则返回true
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean remove(final Object key, final Object value) {
|
public boolean remove(final Object key, final Object value) {
|
||||||
return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key);
|
return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Map中移除所有映射关系,同时清空反向Map
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
super.clear();
|
super.clear();
|
||||||
@ -113,14 +143,34 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return getInverse().get(value);
|
return getInverse().get(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果指定的键尚未与值关联(或映射到null),则将其与给定值关联,同时同步更新反向Map
|
||||||
|
* <p>
|
||||||
|
* 只有当键成功添加到主Map时(即键之前不存在),才会同步更新反向Map,确保双向映射的一致性
|
||||||
|
*
|
||||||
|
* @param key 与指定值关联的键
|
||||||
|
* @param value 与指定键关联的值
|
||||||
|
* @return 与指定键关联的当前值,如果没有映射则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V putIfAbsent(final K key, final V value) {
|
public V putIfAbsent(final K key, final V value) {
|
||||||
if (null != this.inverse) {
|
final V oldValue = super.putIfAbsent(key, value);
|
||||||
this.inverse.putIfAbsent(value, key);
|
// 只有当oldValue为null时(即key之前不存在),才更新反向Map
|
||||||
|
if (null == oldValue && null != this.inverse) {
|
||||||
|
this.inverse.put(value, key);
|
||||||
}
|
}
|
||||||
return super.putIfAbsent(key, value);
|
return oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果指定的键尚未与值关联(或映射到null),则使用给定的映射函数计算其值并放入此Map中
|
||||||
|
* <p>
|
||||||
|
* 由于此操作可能会修改Map的映射关系,因此在操作完成后会重置反向Map,下次访问时会重新构建
|
||||||
|
*
|
||||||
|
* @param key 与计算值关联的键
|
||||||
|
* @param mappingFunction 用于计算值的映射函数
|
||||||
|
* @return 与指定键关联的当前(现有的或计算的)值,如果计算的值为null则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
|
public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
|
||||||
final V result = super.computeIfAbsent(key, mappingFunction);
|
final V result = super.computeIfAbsent(key, mappingFunction);
|
||||||
@ -128,6 +178,16 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果指定键的值存在且非null,则使用给定的重新映射函数计算新值
|
||||||
|
* <p>
|
||||||
|
* 如果重新映射函数返回null,则移除该映射关系。由于此操作会修改Map的映射关系,
|
||||||
|
* 因此在操作完成后会重置反向Map,下次访问时会重新构建
|
||||||
|
*
|
||||||
|
* @param key 与指定值关联的键
|
||||||
|
* @param remappingFunction 用于计算值的重新映射函数
|
||||||
|
* @return 与指定键关联的新值,如果不存在则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V computeIfPresent(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
public V computeIfPresent(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||||
final V result = super.computeIfPresent(key, remappingFunction);
|
final V result = super.computeIfPresent(key, remappingFunction);
|
||||||
@ -135,6 +195,16 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试计算指定键及其当前映射值的映射关系(如果当前映射不存在,则为null)
|
||||||
|
* <p>
|
||||||
|
* 此方法用于为指定的键计算新的值。如果重新映射函数返回null,则移除该映射关系(如果存在)。
|
||||||
|
* 由于此操作会修改Map的映射关系,因此在操作完成后会重置反向Map,下次访问时会重新构建
|
||||||
|
*
|
||||||
|
* @param key 与指定值关联的键
|
||||||
|
* @param remappingFunction 用于计算值的重新映射函数
|
||||||
|
* @return 与指定键关联的新值,如果不存在则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V compute(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
public V compute(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||||
final V result = super.compute(key, remappingFunction);
|
final V result = super.compute(key, remappingFunction);
|
||||||
@ -142,6 +212,17 @@ public class BiMap<K, V> extends MapWrapper<K, V> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果指定的键尚未与值关联或关联的值为null,则将其与给定的非null值关联
|
||||||
|
* <p>
|
||||||
|
* 否则,使用给定的重新映射函数的结果替换关联的值,如果结果为null则移除该映射关系。
|
||||||
|
* 由于此操作会修改Map的映射关系,因此在操作完成后会重置反向Map,下次访问时会重新构建
|
||||||
|
*
|
||||||
|
* @param key 与指定值关联的键
|
||||||
|
* @param value 要与键关联的非null值
|
||||||
|
* @param remappingFunction 用于重新计算值的重新映射函数
|
||||||
|
* @return 与指定键关联的新值,如果不存在则返回null
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public V merge(final K key, final V value, final BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
public V merge(final K key, final V value, final BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||||
final V result = super.merge(key, value, remappingFunction);
|
final V result = super.merge(key, value, remappingFunction);
|
||||||
|
|||||||
@ -139,6 +139,24 @@ public class MapProxy implements Map<Object, Object>, TypeGetter<Object>, Invoca
|
|||||||
return map.entrySet();
|
return map.entrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现InvocationHandler接口的调用处理方法
|
||||||
|
* <p>
|
||||||
|
* 此方法用于动态代理,支持以下功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>将getXXX方法调用转换为从Map中获取对应key的值</li>
|
||||||
|
* <li>将isXXX方法调用(针对boolean类型)转换为从Map中获取对应key的值</li>
|
||||||
|
* <li>将setXXX方法调用转换为向Map中设置对应key的值</li>
|
||||||
|
* <li>支持驼峰命名和下划线命名的自动转换</li>
|
||||||
|
* <li>自动进行类型转换,将Map中的值转换为方法返回类型</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param proxy 代理实例
|
||||||
|
* @param method 被调用的方法
|
||||||
|
* @param args 方法参数
|
||||||
|
* @return 方法调用的结果
|
||||||
|
* @throws UnsupportedOperationException 如果方法不支持代理
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(final Object proxy, final Method method, final Object[] args) {
|
public Object invoke(final Object proxy, final Method method, final Object[] args) {
|
||||||
final Class<?>[] parameterTypes = method.getParameterTypes();
|
final Class<?>[] parameterTypes = method.getParameterTypes();
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public interface Table<R, C, V> extends Iterable<Table.Cell<R, C, V>> {
|
|||||||
/**
|
/**
|
||||||
* 行是否存在
|
* 行是否存在
|
||||||
*
|
*
|
||||||
* @param rowKey 行键
|
* @param rowKey 行键set
|
||||||
* @return 行是否存在
|
* @return 行是否存在
|
||||||
*/
|
*/
|
||||||
default boolean containsRow(final R rowKey) {
|
default boolean containsRow(final R rowKey) {
|
||||||
@ -124,7 +124,7 @@ public interface Table<R, C, V> extends Iterable<Table.Cell<R, C, V>> {
|
|||||||
/**
|
/**
|
||||||
* 返回所有列的key,列的key如果实现Map是可重复key,则返回对应不去重的List。
|
* 返回所有列的key,列的key如果实现Map是可重复key,则返回对应不去重的List。
|
||||||
*
|
*
|
||||||
* @return 列set
|
* @return 列List
|
||||||
* @since 5.8.0
|
* @since 5.8.0
|
||||||
*/
|
*/
|
||||||
default List<C> columnKeys() {
|
default List<C> columnKeys() {
|
||||||
|
|||||||
@ -77,7 +77,7 @@ public class ConstructorUtil {
|
|||||||
* @param <T> 构造的对象类型
|
* @param <T> 构造的对象类型
|
||||||
* @param beanClass 类,非{@code null}
|
* @param beanClass 类,非{@code null}
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> Constructor<T>[] getConstructors(final Class<T> beanClass) throws SecurityException {
|
public static <T> Constructor<T>[] getConstructors(final Class<T> beanClass) throws SecurityException {
|
||||||
@ -90,7 +90,7 @@ public class ConstructorUtil {
|
|||||||
*
|
*
|
||||||
* @param beanClass 类
|
* @param beanClass 类
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Constructor<?>[] getConstructorsDirectly(final Class<?> beanClass) throws SecurityException {
|
public static Constructor<?>[] getConstructorsDirectly(final Class<?> beanClass) throws SecurityException {
|
||||||
return beanClass.getDeclaredConstructors();
|
return beanClass.getDeclaredConstructors();
|
||||||
|
|||||||
@ -76,7 +76,7 @@ public class FieldReflect {
|
|||||||
*
|
*
|
||||||
* @param predicate 过滤器
|
* @param predicate 过滤器
|
||||||
* @return 字段数组
|
* @return 字段数组
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public Field[] getDeclaredFields(final Predicate<Field> predicate) {
|
public Field[] getDeclaredFields(final Predicate<Field> predicate) {
|
||||||
if (null == declaredFields) {
|
if (null == declaredFields) {
|
||||||
@ -94,7 +94,7 @@ public class FieldReflect {
|
|||||||
*
|
*
|
||||||
* @param predicate 过滤器
|
* @param predicate 过滤器
|
||||||
* @return 字段数组
|
* @return 字段数组
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public Field[] getAllFields(final Predicate<Field> predicate) {
|
public Field[] getAllFields(final Predicate<Field> predicate) {
|
||||||
if (null == allFields) {
|
if (null == allFields) {
|
||||||
@ -113,7 +113,7 @@ public class FieldReflect {
|
|||||||
*
|
*
|
||||||
* @param withSuperClassFields 是否包括父类的字段列表
|
* @param withSuperClassFields 是否包括父类的字段列表
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public Field[] getFieldsDirectly(final boolean withSuperClassFields) throws SecurityException {
|
public Field[] getFieldsDirectly(final boolean withSuperClassFields) throws SecurityException {
|
||||||
Field[] allFields = null;
|
Field[] allFields = null;
|
||||||
|
|||||||
@ -158,7 +158,7 @@ public class FieldUtil {
|
|||||||
*
|
*
|
||||||
* @param beanClass 类
|
* @param beanClass 类
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Field[] getFields(final Class<?> beanClass) throws SecurityException {
|
public static Field[] getFields(final Class<?> beanClass) throws SecurityException {
|
||||||
return getFields(beanClass, null);
|
return getFields(beanClass, null);
|
||||||
@ -172,7 +172,7 @@ public class FieldUtil {
|
|||||||
* @param beanClass 类
|
* @param beanClass 类
|
||||||
* @param fieldPredicate field过滤器,过滤掉不需要的field,{@link Predicate#test(Object)}为{@code true}保留,null表示全部保留
|
* @param fieldPredicate field过滤器,过滤掉不需要的field,{@link Predicate#test(Object)}为{@code true}保留,null表示全部保留
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
* @since 5.7.14
|
* @since 5.7.14
|
||||||
*/
|
*/
|
||||||
public static Field[] getFields(final Class<?> beanClass, final Predicate<Field> fieldPredicate) throws SecurityException {
|
public static Field[] getFields(final Class<?> beanClass, final Predicate<Field> fieldPredicate) throws SecurityException {
|
||||||
@ -186,7 +186,7 @@ public class FieldUtil {
|
|||||||
* @param beanClass 类
|
* @param beanClass 类
|
||||||
* @param fieldPredicate field过滤器,过滤掉不需要的field,{@link Predicate#test(Object)}为{@code true}保留,null表示全部保留
|
* @param fieldPredicate field过滤器,过滤掉不需要的field,{@link Predicate#test(Object)}为{@code true}保留,null表示全部保留
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public static Field[] getDeclaredFields(final Class<?> beanClass, final Predicate<Field> fieldPredicate) throws SecurityException {
|
public static Field[] getDeclaredFields(final Class<?> beanClass, final Predicate<Field> fieldPredicate) throws SecurityException {
|
||||||
@ -201,7 +201,7 @@ public class FieldUtil {
|
|||||||
* @param beanClass 类
|
* @param beanClass 类
|
||||||
* @param withSuperClassFields 是否包括父类的字段列表
|
* @param withSuperClassFields 是否包括父类的字段列表
|
||||||
* @return 字段列表
|
* @return 字段列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Field[] getFieldsDirectly(final Class<?> beanClass, final boolean withSuperClassFields) throws SecurityException {
|
public static Field[] getFieldsDirectly(final Class<?> beanClass, final boolean withSuperClassFields) throws SecurityException {
|
||||||
return FieldReflect.of(beanClass).getFieldsDirectly(withSuperClassFields);
|
return FieldReflect.of(beanClass).getFieldsDirectly(withSuperClassFields);
|
||||||
|
|||||||
@ -149,7 +149,7 @@ public class MethodReflect {
|
|||||||
* @param withSupers 是否包括父类或接口的方法列表
|
* @param withSupers 是否包括父类或接口的方法列表
|
||||||
* @param withMethodFromObject 是否包括Object中的方法
|
* @param withMethodFromObject 是否包括Object中的方法
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public Method[] getMethodsDirectly(final boolean withSupers, final boolean withMethodFromObject) throws SecurityException {
|
public Method[] getMethodsDirectly(final boolean withSupers, final boolean withMethodFromObject) throws SecurityException {
|
||||||
final Class<?> clazz = this.clazz;
|
final Class<?> clazz = this.clazz;
|
||||||
|
|||||||
@ -286,7 +286,7 @@ public class MethodUtil {
|
|||||||
*
|
*
|
||||||
* @param clazz 类,非{@code null}
|
* @param clazz 类,非{@code null}
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Method[] getMethods(final Class<?> clazz) throws SecurityException {
|
public static Method[] getMethods(final Class<?> clazz) throws SecurityException {
|
||||||
return getMethods(clazz, null);
|
return getMethods(clazz, null);
|
||||||
@ -298,7 +298,7 @@ public class MethodUtil {
|
|||||||
* @param clazz 类,非{@code null}
|
* @param clazz 类,非{@code null}
|
||||||
* @param predicate 方法过滤器,{@code null}表示无过滤
|
* @param predicate 方法过滤器,{@code null}表示无过滤
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Method[] getMethods(final Class<?> clazz, final Predicate<Method> predicate) throws SecurityException {
|
public static Method[] getMethods(final Class<?> clazz, final Predicate<Method> predicate) throws SecurityException {
|
||||||
return METHODS_CACHE.computeIfAbsent(Assert.notNull(clazz), MethodReflect::of).getAllMethods(predicate);
|
return METHODS_CACHE.computeIfAbsent(Assert.notNull(clazz), MethodReflect::of).getAllMethods(predicate);
|
||||||
@ -330,7 +330,7 @@ public class MethodUtil {
|
|||||||
*
|
*
|
||||||
* @param clazz 类,非{@code null}
|
* @param clazz 类,非{@code null}
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Method[] getDeclaredMethods(final Class<?> clazz) throws SecurityException {
|
public static Method[] getDeclaredMethods(final Class<?> clazz) throws SecurityException {
|
||||||
return getDeclaredMethods(clazz, null);
|
return getDeclaredMethods(clazz, null);
|
||||||
@ -342,7 +342,7 @@ public class MethodUtil {
|
|||||||
* @param clazz 类,非{@code null}
|
* @param clazz 类,非{@code null}
|
||||||
* @param predicate 方法过滤器,{@code null}表示无过滤
|
* @param predicate 方法过滤器,{@code null}表示无过滤
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Method[] getDeclaredMethods(final Class<?> clazz, final Predicate<Method> predicate) throws SecurityException {
|
public static Method[] getDeclaredMethods(final Class<?> clazz, final Predicate<Method> predicate) throws SecurityException {
|
||||||
return METHODS_CACHE.computeIfAbsent(Assert.notNull(clazz), MethodReflect::of).getDeclaredMethods(predicate);
|
return METHODS_CACHE.computeIfAbsent(Assert.notNull(clazz), MethodReflect::of).getDeclaredMethods(predicate);
|
||||||
@ -363,7 +363,7 @@ public class MethodUtil {
|
|||||||
* @param withSupers 是否包括父类或接口的方法列表
|
* @param withSupers 是否包括父类或接口的方法列表
|
||||||
* @param withMethodFromObject 是否包括Object中的方法
|
* @param withMethodFromObject 是否包括Object中的方法
|
||||||
* @return 方法列表
|
* @return 方法列表
|
||||||
* @throws SecurityException 安全检查异常
|
* @throws SecurityException 当安全管理器存在且拒绝访问时抛出
|
||||||
*/
|
*/
|
||||||
public static Method[] getMethodsDirectly(final Class<?> beanClass, final boolean withSupers, final boolean withMethodFromObject) throws SecurityException {
|
public static Method[] getMethodsDirectly(final Class<?> beanClass, final boolean withSupers, final boolean withMethodFromObject) throws SecurityException {
|
||||||
return MethodReflect.of(Assert.notNull(beanClass)).getMethodsDirectly(withSupers, withMethodFromObject);
|
return MethodReflect.of(Assert.notNull(beanClass)).getMethodsDirectly(withSupers, withMethodFromObject);
|
||||||
|
|||||||
@ -34,12 +34,23 @@ public class TextSimilarity {
|
|||||||
* <li>只比较两个字符串字母、数字、汉字部分,其他符号去除</li>
|
* <li>只比较两个字符串字母、数字、汉字部分,其他符号去除</li>
|
||||||
* <li>计算出两个字符串最大子串,除以最长的字符串,结果即为相似度</li>
|
* <li>计算出两个字符串最大子串,除以最长的字符串,结果即为相似度</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* 空值处理:
|
||||||
|
* <ul>
|
||||||
|
* <li>如果两个字符串都为{@code null},返回1(视为完全相同)</li>
|
||||||
|
* <li>如果其中一个字符串为{@code null},返回0(视为完全不同)</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param strA 字符串1
|
* @param strA 字符串1
|
||||||
* @param strB 字符串2
|
* @param strB 字符串2
|
||||||
* @return 相似度
|
* @return 相似度,取值范围为[0, 1],其中0表示完全不同,1表示完全相同
|
||||||
*/
|
*/
|
||||||
public static double similar(final String strA, final String strB) {
|
public static double similar(final String strA, final String strB) {
|
||||||
|
if (null == strA && null == strB) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (null == strA || null == strB) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
final String newStrA;
|
final String newStrA;
|
||||||
final String newStrB;
|
final String newStrB;
|
||||||
if (strA.length() < strB.length()) {
|
if (strA.length() < strB.length()) {
|
||||||
@ -52,7 +63,7 @@ public class TextSimilarity {
|
|||||||
|
|
||||||
// 用较大的字符串长度作为分母,相似子串作为分子计算出字串相似度
|
// 用较大的字符串长度作为分母,相似子串作为分子计算出字串相似度
|
||||||
final int temp = Math.max(newStrA.length(), newStrB.length());
|
final int temp = Math.max(newStrA.length(), newStrB.length());
|
||||||
if(0 == temp) {
|
if (0 == temp) {
|
||||||
// 两个都是空串相似度为1,被认为是相同的串
|
// 两个都是空串相似度为1,被认为是相同的串
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -64,8 +75,8 @@ public class TextSimilarity {
|
|||||||
/**
|
/**
|
||||||
* 利用莱文斯坦距离(Levenshtein distance)算法计算相似度百分比
|
* 利用莱文斯坦距离(Levenshtein distance)算法计算相似度百分比
|
||||||
*
|
*
|
||||||
* @param strA 字符串1
|
* @param strA 字符串1
|
||||||
* @param strB 字符串2
|
* @param strB 字符串2
|
||||||
* @param scale 保留小数
|
* @param scale 保留小数
|
||||||
* @return 百分比
|
* @return 百分比
|
||||||
*/
|
*/
|
||||||
@ -79,9 +90,12 @@ public class TextSimilarity {
|
|||||||
*
|
*
|
||||||
* @param strA 字符串1
|
* @param strA 字符串1
|
||||||
* @param strB 字符串2
|
* @param strB 字符串2
|
||||||
* @return 最长公共子串
|
* @return 最长公共子串,如果任一参数为{@code null}则返回{@code null},如果没有公共子串则返回空字符串""
|
||||||
*/
|
*/
|
||||||
public static String longestCommonSubstring(final String strA, final String strB) {
|
public static String longestCommonSubstring(final String strA, final String strB) {
|
||||||
|
if (null == strA || null == strB) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// 初始化矩阵数据,matrix[0][0]的值为0, 如果字符数组chars_strA和chars_strB的对应位相同,则matrix[i][j]的值为左上角的值加1,
|
// 初始化矩阵数据,matrix[0][0]的值为0, 如果字符数组chars_strA和chars_strB的对应位相同,则matrix[i][j]的值为左上角的值加1,
|
||||||
// 否则,matrix[i][j]的值等于左上方最近两个位置的较大值, 矩阵中其余各点的值为0.
|
// 否则,matrix[i][j]的值等于左上方最近两个位置的较大值, 矩阵中其余各点的值为0.
|
||||||
final int[][] matrix = generateMatrix(strA, strB);
|
final int[][] matrix = generateMatrix(strA, strB);
|
||||||
@ -108,6 +122,7 @@ public class TextSimilarity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------- Private method start
|
// --------------------------------------------------------------------------------------------------- Private method start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将字符串的所有数据依次写成一行,去除无意义字符串
|
* 将字符串的所有数据依次写成一行,去除无意义字符串
|
||||||
*
|
*
|
||||||
@ -121,7 +136,7 @@ public class TextSimilarity {
|
|||||||
char c;
|
char c;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
c = str.charAt(i);
|
c = str.charAt(i);
|
||||||
if(isValidChar(c)) {
|
if (isValidChar(c)) {
|
||||||
sb.append(c);
|
sb.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,9 +152,9 @@ public class TextSimilarity {
|
|||||||
*/
|
*/
|
||||||
private static boolean isValidChar(final char charValue) {
|
private static boolean isValidChar(final char charValue) {
|
||||||
return (charValue >= 0x4E00 && charValue <= 0X9FFF) || //
|
return (charValue >= 0x4E00 && charValue <= 0X9FFF) || //
|
||||||
(charValue >= 'a' && charValue <= 'z') || //
|
(charValue >= 'a' && charValue <= 'z') || //
|
||||||
(charValue >= 'A' && charValue <= 'Z') || //
|
(charValue >= 'A' && charValue <= 'Z') || //
|
||||||
(charValue >= '0' && charValue <= '9');
|
(charValue >= '0' && charValue <= '9');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,9 +177,9 @@ public class TextSimilarity {
|
|||||||
for (int i = 1; i <= m; i++) {
|
for (int i = 1; i <= m; i++) {
|
||||||
for (int j = 1; j <= n; j++) {
|
for (int j = 1; j <= n; j++) {
|
||||||
if (strA.charAt(i - 1) == strB.charAt(j - 1)) {
|
if (strA.charAt(i - 1) == strB.charAt(j - 1)) {
|
||||||
currLine[j] = lastLine[j-1] + 1;
|
currLine[j] = lastLine[j - 1] + 1;
|
||||||
} else {
|
} else {
|
||||||
currLine[j] = Math.max(currLine[j-1], lastLine[j]);
|
currLine[j] = Math.max(currLine[j - 1], lastLine[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
temp = lastLine;
|
temp = lastLine;
|
||||||
|
|||||||
@ -131,9 +131,10 @@ public class EscapeUtil {
|
|||||||
return StrUtil.toStringOrNull(content);
|
return StrUtil.toStringOrNull(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
final StringBuilder tmp = new StringBuilder(content.length() * 6);
|
final int len = content.length();
|
||||||
|
final StringBuilder tmp = new StringBuilder(len * 6);
|
||||||
char c;
|
char c;
|
||||||
for (int i = 0; i < content.length(); i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
c = content.charAt(i);
|
c = content.charAt(i);
|
||||||
if (!filter.test(c)) {
|
if (!filter.test(c)) {
|
||||||
tmp.append(c);
|
tmp.append(c);
|
||||||
@ -156,7 +157,26 @@ public class EscapeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape解码
|
* Escape解码支持两种转义格式的解码:
|
||||||
|
* <ul>
|
||||||
|
* <li>%XX - 两位十六进制数字,用于表示ASCII字符(0-255)</li>
|
||||||
|
* <li>%uXXXX - 四位十六进制数字,用于表示Unicode字符</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 对于不完整的转义序列,本方法会将其原样保留而不抛出异常:
|
||||||
|
* <ul>
|
||||||
|
* <li>字符串末尾的单独"%"字符会被原样保留</li>
|
||||||
|
* <li>"%u"后面不足4位十六进制数字时,整个不完整序列会被原样保留</li>
|
||||||
|
* <li>"%"后面不足2位十六进制数字时(非%u格式),整个不完整序列会被原样保留</li>
|
||||||
|
* </ul>
|
||||||
|
* 例如:
|
||||||
|
* <pre>
|
||||||
|
* unescape("test%") = "test%" // 末尾的%被保留
|
||||||
|
* unescape("test%u12") = "test%u12" // 不足4位,原样保留
|
||||||
|
* unescape("test%2") = "test%2" // 不足2位,原样保留
|
||||||
|
* unescape("test%20") = "test " // 正常解码空格
|
||||||
|
* unescape("test%u4E2D") = "test中" // 正常解码中文字符
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param content 被转义的内容
|
* @param content 被转义的内容
|
||||||
* @return 解码后的字符串
|
* @return 解码后的字符串
|
||||||
@ -166,26 +186,40 @@ public class EscapeUtil {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
final StringBuilder tmp = new StringBuilder(content.length());
|
final int len = content.length();
|
||||||
|
final StringBuilder tmp = new StringBuilder(len);
|
||||||
int lastPos = 0;
|
int lastPos = 0;
|
||||||
int pos;
|
int pos;
|
||||||
char ch;
|
char ch;
|
||||||
while (lastPos < content.length()) {
|
while (lastPos < len) {
|
||||||
pos = content.indexOf("%", lastPos);
|
pos = content.indexOf("%", lastPos);
|
||||||
if (pos == lastPos) {
|
if (pos == lastPos) {
|
||||||
if (content.charAt(pos + 1) == 'u') {
|
if (pos + 1 < len && content.charAt(pos + 1) == 'u') {
|
||||||
ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
|
if (pos + 6 <= len) {
|
||||||
tmp.append(ch);
|
ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16);
|
||||||
lastPos = pos + 6;
|
tmp.append(ch);
|
||||||
|
lastPos = pos + 6;
|
||||||
|
} else {
|
||||||
|
// Not enough characters, append as-is
|
||||||
|
tmp.append(content.substring(pos));
|
||||||
|
lastPos = len;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
|
// Check if there's enough characters for hex escape (%XX)
|
||||||
tmp.append(ch);
|
if (pos + 3 <= len) {
|
||||||
lastPos = pos + 3;
|
ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16);
|
||||||
|
tmp.append(ch);
|
||||||
|
lastPos = pos + 3;
|
||||||
|
} else {
|
||||||
|
// Not enough characters, append as-is
|
||||||
|
tmp.append(content.substring(pos));
|
||||||
|
lastPos = len;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (pos == -1) {
|
if (pos == -1) {
|
||||||
tmp.append(content.substring(lastPos));
|
tmp.append(content.substring(lastPos));
|
||||||
lastPos = content.length();
|
lastPos = len;
|
||||||
} else {
|
} else {
|
||||||
tmp.append(content, lastPos, pos);
|
tmp.append(content, lastPos, pos);
|
||||||
lastPos = pos;
|
lastPos = pos;
|
||||||
|
|||||||
@ -16,31 +16,33 @@
|
|||||||
|
|
||||||
package cn.hutool.v7.core.text.escape;
|
package cn.hutool.v7.core.text.escape;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
public class EscapeUtilTest {
|
public class EscapeUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void escapeHtml4Test() {
|
public void escapeHtml4Test() {
|
||||||
final String escapeHtml4 = EscapeUtil.escapeHtml4("<a>你好</a>");
|
final String escapeHtml4 = EscapeUtil.escapeHtml4("<a>你好</a>");
|
||||||
Assertions.assertEquals("<a>你好</a>", escapeHtml4);
|
assertEquals("<a>你好</a>", escapeHtml4);
|
||||||
|
|
||||||
final String result = EscapeUtil.unescapeHtml4("振荡器类型");
|
final String result = EscapeUtil.unescapeHtml4("振荡器类型");
|
||||||
Assertions.assertEquals("振荡器类型", result);
|
assertEquals("振荡器类型", result);
|
||||||
|
|
||||||
final String escape = EscapeUtil.escapeHtml4("*@-_+./(123你好)");
|
final String escape = EscapeUtil.escapeHtml4("*@-_+./(123你好)");
|
||||||
Assertions.assertEquals("*@-_+./(123你好)", escape);
|
assertEquals("*@-_+./(123你好)", escape);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void escapeTest(){
|
public void escapeTest(){
|
||||||
final String str = "*@-_+./(123你好)ABCabc";
|
final String str = "*@-_+./(123你好)ABCabc";
|
||||||
final String escape = EscapeUtil.escape(str);
|
final String escape = EscapeUtil.escape(str);
|
||||||
Assertions.assertEquals("*@-_+./%28123%u4f60%u597d%29ABCabc", escape);
|
assertEquals("*@-_+./%28123%u4f60%u597d%29ABCabc", escape);
|
||||||
|
|
||||||
final String unescape = EscapeUtil.unescape(escape);
|
final String unescape = EscapeUtil.unescape(escape);
|
||||||
Assertions.assertEquals(str, unescape);
|
assertEquals(str, unescape);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -48,24 +50,24 @@ public class EscapeUtilTest {
|
|||||||
final String str = "*@-_+./(123你好)ABCabc";
|
final String str = "*@-_+./(123你好)ABCabc";
|
||||||
|
|
||||||
final String escape = EscapeUtil.escapeAll(str);
|
final String escape = EscapeUtil.escapeAll(str);
|
||||||
Assertions.assertEquals("%2a%40%2d%5f%2b%2e%2f%28%31%32%33%u4f60%u597d%29%41%42%43%61%62%63", escape);
|
assertEquals("%2a%40%2d%5f%2b%2e%2f%28%31%32%33%u4f60%u597d%29%41%42%43%61%62%63", escape);
|
||||||
|
|
||||||
final String unescape = EscapeUtil.unescape(escape);
|
final String unescape = EscapeUtil.unescape(escape);
|
||||||
Assertions.assertEquals(str, unescape);
|
assertEquals(str, unescape);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://gitee.com/chinabugotech/hutool/issues/I49JU8
|
* <a href="https://gitee.com/chinabugotech/hutool/issues/I49JU8">issue#I49JU8</a>
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void escapeAllTest2(){
|
public void escapeAllTest2(){
|
||||||
final String str = "٩";
|
final String str = "٩";
|
||||||
|
|
||||||
final String escape = EscapeUtil.escapeAll(str);
|
final String escape = EscapeUtil.escapeAll(str);
|
||||||
Assertions.assertEquals("%u0669", escape);
|
assertEquals("%u0669", escape);
|
||||||
|
|
||||||
final String unescape = EscapeUtil.unescape(escape);
|
final String unescape = EscapeUtil.unescape(escape);
|
||||||
Assertions.assertEquals(str, unescape);
|
assertEquals(str, unescape);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -73,21 +75,135 @@ public class EscapeUtilTest {
|
|||||||
// 单引号不做转义
|
// 单引号不做转义
|
||||||
final String str = "'some text with single quotes'";
|
final String str = "'some text with single quotes'";
|
||||||
final String s = EscapeUtil.escapeHtml4(str);
|
final String s = EscapeUtil.escapeHtml4(str);
|
||||||
Assertions.assertEquals("'some text with single quotes'", s);
|
assertEquals("'some text with single quotes'", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void unescapeSingleQuotesTest(){
|
public void unescapeSingleQuotesTest(){
|
||||||
final String str = "'some text with single quotes'";
|
final String str = "'some text with single quotes'";
|
||||||
final String s = EscapeUtil.unescapeHtml4(str);
|
final String s = EscapeUtil.unescapeHtml4(str);
|
||||||
Assertions.assertEquals("'some text with single quotes'", s);
|
assertEquals("'some text with single quotes'", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void escapeXmlTest(){
|
public void escapeXmlTest(){
|
||||||
final String a = "<>";
|
final String a = "<>";
|
||||||
final String escape = EscapeUtil.escapeXml(a);
|
final String escape = EscapeUtil.escapeXml(a);
|
||||||
Assertions.assertEquals("<>", escape);
|
assertEquals("<>", escape);
|
||||||
Assertions.assertEquals("中文“双引号”", EscapeUtil.escapeXml("中文“双引号”"));
|
assertEquals("中文“双引号”", EscapeUtil.escapeXml("中文“双引号”"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeNull() {
|
||||||
|
assertNull(EscapeUtil.unescape(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeEmpty() {
|
||||||
|
assertEquals("", EscapeUtil.unescape(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeBlank() {
|
||||||
|
assertEquals(" ", EscapeUtil.unescape(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeAsciiCharacters() {
|
||||||
|
// 测试ASCII字符转义
|
||||||
|
assertEquals("hello", EscapeUtil.unescape("hello"));
|
||||||
|
assertEquals("test space", EscapeUtil.unescape("test%20space"));
|
||||||
|
assertEquals("A", EscapeUtil.unescape("%41"));
|
||||||
|
assertEquals("a", EscapeUtil.unescape("%61"));
|
||||||
|
assertEquals("0", EscapeUtil.unescape("%30"));
|
||||||
|
assertEquals("!", EscapeUtil.unescape("%21"));
|
||||||
|
assertEquals("@", EscapeUtil.unescape("%40"));
|
||||||
|
assertEquals("#", EscapeUtil.unescape("%23"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeUnicodeCharacters() {
|
||||||
|
// 测试Unicode字符转义
|
||||||
|
assertEquals("中", EscapeUtil.unescape("%u4E2D"));
|
||||||
|
assertEquals("文", EscapeUtil.unescape("%u6587"));
|
||||||
|
assertEquals("测", EscapeUtil.unescape("%u6D4B"));
|
||||||
|
assertEquals("试", EscapeUtil.unescape("%u8BD5"));
|
||||||
|
assertEquals("😊", EscapeUtil.unescape("%uD83D%uDE0A")); // 笑脸表情
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeMixedContent() {
|
||||||
|
// 测试混合内容
|
||||||
|
assertEquals("Hello 世界!", EscapeUtil.unescape("Hello%20%u4E16%u754C%21"));
|
||||||
|
assertEquals("测试: 100%", EscapeUtil.unescape("%u6D4B%u8BD5%3A%20100%25"));
|
||||||
|
assertEquals("a+b=c", EscapeUtil.unescape("a%2Bb%3Dc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeIncompleteEscapeSequences() {
|
||||||
|
// 测试不完整的转义序列
|
||||||
|
assertEquals("test%", EscapeUtil.unescape("test%"));
|
||||||
|
assertEquals("test%u", EscapeUtil.unescape("test%u"));
|
||||||
|
assertEquals("test%u1", EscapeUtil.unescape("test%u1"));
|
||||||
|
assertEquals("test%u12", EscapeUtil.unescape("test%u12"));
|
||||||
|
assertEquals("test%u123", EscapeUtil.unescape("test%u123"));
|
||||||
|
assertEquals("test%1", EscapeUtil.unescape("test%1"));
|
||||||
|
assertEquals("test%2", EscapeUtil.unescape("test%2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeEdgeCases() {
|
||||||
|
// 测试边界情况
|
||||||
|
assertEquals("%", EscapeUtil.unescape("%"));
|
||||||
|
assertEquals("%u", EscapeUtil.unescape("%u"));
|
||||||
|
assertEquals("%%", EscapeUtil.unescape("%%"));
|
||||||
|
assertEquals("%u%", EscapeUtil.unescape("%u%"));
|
||||||
|
assertEquals("100% complete", EscapeUtil.unescape("100%25%20complete"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeMultipleEscapeSequences() {
|
||||||
|
// 测试多个连续的转义序列
|
||||||
|
assertEquals("ABC", EscapeUtil.unescape("%41%42%43"));
|
||||||
|
assertEquals("中文测试", EscapeUtil.unescape("%u4E2D%u6587%u6D4B%u8BD5"));
|
||||||
|
assertEquals("A 中 B", EscapeUtil.unescape("%41%20%u4E2D%20%42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeSpecialCharacters() {
|
||||||
|
// 测试特殊字符
|
||||||
|
assertEquals("\n", EscapeUtil.unescape("%0A"));
|
||||||
|
assertEquals("\r", EscapeUtil.unescape("%0D"));
|
||||||
|
assertEquals("\t", EscapeUtil.unescape("%09"));
|
||||||
|
assertEquals(" ", EscapeUtil.unescape("%20"));
|
||||||
|
assertEquals("<", EscapeUtil.unescape("%3C"));
|
||||||
|
assertEquals(">", EscapeUtil.unescape("%3E"));
|
||||||
|
assertEquals("&", EscapeUtil.unescape("%26"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeComplexScenario() {
|
||||||
|
// 测试复杂场景
|
||||||
|
final String original = "Hello 世界! 这是测试。Email: test@example.com";
|
||||||
|
final String escaped = "Hello%20%u4E16%u754C%21%20%u8FD9%u662F%u6D4B%u8BD5%u3002Email%3A%20test%40example.com";
|
||||||
|
assertEquals(original, EscapeUtil.unescape(escaped));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeWithIncompleteAtEnd() {
|
||||||
|
// 测试末尾有不完整转义序列
|
||||||
|
assertEquals("normal%", EscapeUtil.unescape("normal%"));
|
||||||
|
assertEquals("normal%u", EscapeUtil.unescape("normal%u"));
|
||||||
|
assertEquals("normal%u1", EscapeUtil.unescape("normal%u1"));
|
||||||
|
assertEquals("normal%1", EscapeUtil.unescape("normal%1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnescapeUppercaseHex() {
|
||||||
|
// 测试大写十六进制
|
||||||
|
assertEquals("A", EscapeUtil.unescape("%41"));
|
||||||
|
assertEquals("A", EscapeUtil.unescape("%41"));
|
||||||
|
assertEquals("中", EscapeUtil.unescape("%u4E2D"));
|
||||||
|
assertEquals("中", EscapeUtil.unescape("%u4E2D"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user