This commit is contained in:
Looly 2020-04-02 17:50:07 +08:00
parent 1218a51509
commit 17be56a99c
9 changed files with 498 additions and 205 deletions

View File

@ -14,6 +14,9 @@
* 【script 】 增加createXXXScript区别单例
* 【core 】 修改FileUtil.writeFileToStream等方法返回值为long
* 【core 】 CollUtil.split增加空集合判定issue#814@Github
* 【core 】 NetUtil增加parseCookies方法
* 【core 】 CollUtil增加toMap方法
* 【core 】 CollUtil和IterUtil废弃一些方法
### Bug修复
* 【extra 】 修复SpringUtil使用devtools重启报错问题

View File

@ -9,6 +9,7 @@ import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Editor;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.Matcher;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.hash.Hash32;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
@ -245,7 +246,7 @@ public class CollUtil {
*
* @param coll1 集合1
* @param coll2 集合2
* @param <T> 元素类型
* @param <T> 元素类型
* @return 单差集
*/
public static <T> Collection<T> subtract(Collection<T> coll1, Collection<T> coll2) {
@ -327,10 +328,10 @@ public class CollUtil {
* @param <T> 集合元素类型
* @param collection 集合
* @return {@link Map}
* @see IterUtil#countMap(Iterable)
* @see IterUtil#countMap(Iterator)
*/
public static <T> Map<T, Integer> countMap(Iterable<T> collection) {
return IterUtil.countMap(collection);
return IterUtil.countMap(null == collection ? null : collection.iterator());
}
/**
@ -341,10 +342,13 @@ public class CollUtil {
* @param iterable {@link Iterable}
* @param conjunction 分隔符
* @return 连接后的字符串
* @see IterUtil#join(Iterable, CharSequence)
* @see IterUtil#join(Iterator, CharSequence)
*/
public static <T> String join(Iterable<T> iterable, CharSequence conjunction) {
return IterUtil.join(iterable, conjunction);
if (null == iterable) {
return null;
}
return IterUtil.join(iterable.iterator(), conjunction);
}
/**
@ -355,8 +359,9 @@ public class CollUtil {
* @param iterator 集合
* @param conjunction 分隔符
* @return 连接后的字符串
* @see IterUtil#join(Iterator, CharSequence)
* @deprecated 请使用IterUtil#join(Iterator, CharSequence)
*/
@Deprecated
public static <T> String join(Iterator<T> iterator, CharSequence conjunction) {
return IterUtil.join(iterator, conjunction);
}
@ -643,8 +648,8 @@ public class CollUtil {
* 新建一个List<br>
* 提供的参数为null时返回空{@link ArrayList}
*
* @param <T> 集合元素类型
* @param isLinked 是否新建LinkedList
* @param <T> 集合元素类型
* @param isLinked 是否新建LinkedList
* @param enumeration {@link Enumeration}
* @return ArrayList对象
* @since 3.0.8
@ -707,7 +712,7 @@ public class CollUtil {
* 新建一个ArrayList<br>
* 提供的参数为null时返回空{@link ArrayList}
*
* @param <T> 集合元素类型
* @param <T> 集合元素类型
* @param iterator {@link Iterator}
* @return ArrayList对象
* @since 3.0.8
@ -720,7 +725,7 @@ public class CollUtil {
* 新建一个ArrayList<br>
* 提供的参数为null时返回空{@link ArrayList}
*
* @param <T> 集合元素类型
* @param <T> 集合元素类型
* @param enumeration {@link Enumeration}
* @return ArrayList对象
* @since 3.0.8
@ -1204,6 +1209,36 @@ public class CollUtil {
return Convert.toList(elementType, fieldValues);
}
/**
* 字段值与列表值对应的Map常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况<br>
* 例如车牌号 =
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 对象类型
* @param iterable 对象列表
* @param fieldName 字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 5.0.6
*/
public static <K, V> Map<K, V> fieldValueMap(Iterable<V> iterable, String fieldName) {
return IterUtil.fieldValueMap(null == iterable ? null : iterable.iterator(), fieldName);
}
/**
* 两个字段值组成新的Map
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 值类型不确定使用Object
* @param iterable 对象列表
* @param fieldNameForKey 做为键的字段名会通过反射获取其值
* @param fieldNameForValue 做为值的字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 5.0.6
*/
public static <K, V> Map<K, V> fieldValueAsMap(Iterable<?> iterable, String fieldNameForKey, String fieldNameForValue) {
return IterUtil.fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue);
}
/**
* 查找第一个匹配元素对象
*
@ -1316,13 +1351,13 @@ public class CollUtil {
* 获取匹配规则定义中匹配到元素的所有位置<br>
* 此方法对于某些无序集合的位置信息以转换为数组后的位置为准
*
* @param <T> 元素类型
* @param <T> 元素类型
* @param collection 集合
* @param matcher 匹配器为空则全部匹配
* @param matcher 匹配器为空则全部匹配
* @return 位置数组
* @since 5.2.5
*/
public static <T> int[] indexOfAll(Collection<T> collection, Matcher<T> matcher){
public static <T> int[] indexOfAll(Collection<T> collection, Matcher<T> matcher) {
final List<Integer> indexList = new ArrayList<>();
if (null != collection) {
int index = 0;
@ -1718,6 +1753,38 @@ public class CollUtil {
return MapUtil.toMapList(listMap);
}
/**
* 集合转换为Map转换规则为<br>
* 按照keyFunc函数规则根据元素对象生成Key元素作为值
*
* @param <K> Map键类型
* @param <V> Map值类型
* @param values 数据列表
* @param keyFunc 生成key的函数
* @return 生成的map
* @since 5.2.6
*/
public static <K, V> Map<K, V> toMap(Iterable<V> values, Map<K, V> map, Func1<V, K> keyFunc) {
return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc);
}
/**
* 集合转换为Map转换规则为<br>
* 按照keyFunc函数规则根据元素对象生成Key按照valueFunc函数规则根据元素对象生成value组成新的Map
*
* @param <K> Map键类型
* @param <V> Map值类型
* @param <E> 元素类型
* @param values 数据列表
* @param map Map对象转换后的键值对加入此Map通过传入此对象自定义Map类型
* @param keyFunc 生成key的函数
* @return 生成的map
* @since 5.2.6
*/
public static <K, V, E> Map<K, V> toMap(Iterable<E> values, Map<K, V> map, Func1<E, K> keyFunc, Func1<E, V> valueFunc) {
return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc, valueFunc);
}
/**
* 将指定对象全部加入到集合中<br>
* 提供的对象如果为集合类型会自动转换为目标元素类型<br>

View File

@ -1,17 +1,24 @@
package cn.hutool.core.collection;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.util.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* {@link Iterable} {@link Iterator} 相关工具类
*
*
* @author Looly
* @since 3.1.0
*/
@ -19,7 +26,7 @@ public class IterUtil {
/**
* Iterable是否为空
*
*
* @param iterable Iterable对象
* @return 是否为空
*/
@ -29,7 +36,7 @@ public class IterUtil {
/**
* Iterator是否为空
*
*
* @param Iterator Iterator对象
* @return 是否为空
*/
@ -39,7 +46,7 @@ public class IterUtil {
/**
* Iterable是否为空
*
*
* @param iterable Iterable对象
* @return 是否为空
*/
@ -49,7 +56,7 @@ public class IterUtil {
/**
* Iterator是否为空
*
*
* @param Iterator Iterator对象
* @return 是否为空
*/
@ -59,7 +66,7 @@ public class IterUtil {
/**
* 是否包含{@code null}元素
*
*
* @param iter 被检查的{@link Iterable}对象如果为{@code null} 返回true
* @return 是否包含{@code null}元素
*/
@ -69,7 +76,7 @@ public class IterUtil {
/**
* 是否包含{@code null}元素
*
*
* @param iter 被检查的{@link Iterator}对象如果为{@code null} 返回true
* @return 是否包含{@code null}元素
*/
@ -88,7 +95,7 @@ public class IterUtil {
/**
* 是否全部元素为null
*
*
* @param iter iter 被检查的{@link Iterable}对象如果为{@code null} 返回true
* @return 是否全部元素为null
* @since 3.3.0
@ -99,7 +106,7 @@ public class IterUtil {
/**
* 是否全部元素为null
*
*
* @param iter iter 被检查的{@link Iterator}对象如果为{@code null} 返回true
* @return 是否全部元素为null
* @since 3.3.0
@ -124,11 +131,13 @@ public class IterUtil {
* a: 1<br>
* b: 1<br>
* c: 3<br>
*
* @param <T> 集合元素类型
*
* @param <T> 集合元素类型
* @param iter {@link Iterable}如果为null返回一个空的Map
* @return {@link Map}
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义请使用CollUtil.countMap
*/
@Deprecated
public static <T> Map<T, Integer> countMap(Iterable<T> iter) {
return countMap(null == iter ? null : iter.iterator());
}
@ -140,8 +149,8 @@ public class IterUtil {
* a: 1<br>
* b: 1<br>
* c: 3<br>
*
* @param <T> 集合元素类型
*
* @param <T> 集合元素类型
* @param iter {@link Iterator}如果为null返回一个空的Map
* @return {@link Map}
*/
@ -166,14 +175,16 @@ public class IterUtil {
/**
* 字段值与列表值对应的Map常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况<br>
* 例如车牌号 =
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 对象类型
* @param iter 对象列表
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 对象类型
* @param iter 对象列表
* @param fieldName 字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.0.4
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义请使用CollUtil.fieldValueMap
*/
@Deprecated
public static <K, V> Map<K, V> fieldValueMap(Iterable<V> iter, String fieldName) {
return fieldValueMap(null == iter ? null : iter.iterator(), fieldName);
}
@ -181,71 +192,60 @@ public class IterUtil {
/**
* 字段值与列表值对应的Map常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况<br>
* 例如车牌号 =
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 对象类型
* @param iter 对象列表
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 对象类型
* @param iter 对象列表
* @param fieldName 字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.0.4
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> fieldValueMap(Iterator<V> iter, String fieldName) {
final Map<K, V> result = new HashMap<>();
if (null != iter) {
V value;
while (iter.hasNext()) {
value = iter.next();
result.put((K) ReflectUtil.getFieldValue(value, fieldName), value);
}
}
return result;
return toMap(iter, new HashMap<>(), (value)->(K)ReflectUtil.getFieldValue(value, fieldName));
}
/**
* 两个字段值组成新的Map
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 值类型不确定使用Object
* @param iterable 对象列表
* @param fieldNameForKey 做为键的字段名会通过反射获取其值
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 值类型不确定使用Object
* @param iterable 对象列表
* @param fieldNameForKey 做为键的字段名会通过反射获取其值
* @param fieldNameForValue 做为值的字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.6.2
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义请使用CollUtil.fieldValueMap
*/
@Deprecated
public static <K, V> Map<K, V> fieldValueAsMap(Iterable<?> iterable, String fieldNameForKey, String fieldNameForValue) {
return fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue);
}
/**
* 两个字段值组成新的Map
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 值类型不确定使用Object
* @param iter 对象列表
* @param fieldNameForKey 做为键的字段名会通过反射获取其值
*
* @param <K> 字段名对应值得类型不确定请使用Object
* @param <V> 值类型不确定使用Object
* @param iter 对象列表
* @param fieldNameForKey 做为键的字段名会通过反射获取其值
* @param fieldNameForValue 做为值的字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.0.10
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> fieldValueAsMap(Iterator<?> iter, String fieldNameForKey, String fieldNameForValue) {
final Map<K, V> result = new HashMap<>();
if (null != iter) {
Object value;
while (iter.hasNext()) {
value = iter.next();
result.put((K) ReflectUtil.getFieldValue(value, fieldNameForKey), (V) ReflectUtil.getFieldValue(value, fieldNameForValue));
}
}
return result;
return toMap(iter, new HashMap<>(),
(value)->(K)ReflectUtil.getFieldValue(value, fieldNameForKey),
(value)->(V)ReflectUtil.getFieldValue(value, fieldNameForValue)
);
}
/**
* 获取指定Bean列表中某个字段生成新的列表
*
* @param <V> 对象类型
* @param iterable 对象列表
*
* @param <V> 对象类型
* @param iterable 对象列表
* @param fieldName 字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.6.2
@ -256,9 +256,9 @@ public class IterUtil {
/**
* 获取指定Bean列表中某个字段生成新的列表
*
* @param <V> 对象类型
* @param iter 对象列表
*
* @param <V> 对象类型
* @param iter 对象列表
* @param fieldName 字段名会通过反射获取其值
* @return 某个字段值与对象对应Map
* @since 4.0.10
@ -277,27 +277,29 @@ public class IterUtil {
/**
* conjunction 为分隔符将集合转换为字符串
*
* @param <T> 集合元素类型
* @param iterable {@link Iterable}
*
* @param <T> 集合元素类型
* @param iterable {@link Iterable}
* @param conjunction 分隔符
* @return 连接后的字符串
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义请使用CollUtil.join
*/
@Deprecated
public static <T> String join(Iterable<T> iterable, CharSequence conjunction) {
if (null == iterable) {
return null;
}
return join(iterable.iterator(), conjunction);
}
/**
* conjunction 为分隔符将集合转换为字符串
*
* @param <T> 集合元素类型
* @param iterable {@link Iterable}
*
* @param <T> 集合元素类型
* @param iterable {@link Iterable}
* @param conjunction 分隔符
* @param prefix 每个元素添加的前缀null表示不添加
* @param suffix 每个元素添加的后缀null表示不添加
* @param prefix 每个元素添加的前缀null表示不添加
* @param suffix 每个元素添加的后缀null表示不添加
* @return 连接后的字符串
* @since 4.0.10
*/
@ -307,13 +309,13 @@ public class IterUtil {
}
return join(iterable.iterator(), conjunction, prefix, suffix);
}
/**
* conjunction 为分隔符将集合转换为字符串<br>
* 如果集合元素为数组{@link Iterable}{@link Iterator}则递归组合其为字符串
*
* @param <T> 集合元素类型
* @param iterator 集合
*
* @param <T> 集合元素类型
* @param iterator 集合
* @param conjunction 分隔符
* @return 连接后的字符串
*/
@ -324,12 +326,12 @@ public class IterUtil {
/**
* conjunction 为分隔符将集合转换为字符串<br>
* 如果集合元素为数组{@link Iterable}{@link Iterator}则递归组合其为字符串
*
* @param <T> 集合元素类型
* @param iterator 集合
*
* @param <T> 集合元素类型
* @param iterator 集合
* @param conjunction 分隔符
* @param prefix 每个元素添加的前缀null表示不添加
* @param suffix 每个元素添加的后缀null表示不添加
* @param prefix 每个元素添加的前缀null表示不添加
* @param suffix 每个元素添加的后缀null表示不添加
* @return 连接后的字符串
* @since 4.0.10
*/
@ -364,9 +366,9 @@ public class IterUtil {
/**
* 将Entry集合转换为HashMap
*
* @param <K> 键类型
* @param <V> 值类型
*
* @param <K> 键类型
* @param <V> 值类型
* @param entryIter entry集合
* @return Map
*/
@ -379,15 +381,15 @@ public class IterUtil {
}
return map;
}
/**
* 将键列表和值列表转换为Map<br>
* 以键为准值与键位置需对应如果键元素数多于值元素多余部分值用null代替<br>
* 如果值多于键忽略多余的值
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
* @return 标题内容Map
* @since 3.1.0
@ -400,11 +402,11 @@ public class IterUtil {
* 将键列表和值列表转换为Map<br>
* 以键为准值与键位置需对应如果键元素数多于值元素多余部分值用null代替<br>
* 如果值多于键忽略多余的值
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
* @param isOrder 是否有序
* @return 标题内容Map
* @since 4.1.12
@ -412,15 +414,15 @@ public class IterUtil {
public static <K, V> Map<K, V> toMap(Iterable<K> keys, Iterable<V> values, boolean isOrder) {
return toMap(null == keys ? null : keys.iterator(), null == values ? null : values.iterator(), isOrder);
}
/**
* 将键列表和值列表转换为Map<br>
* 以键为准值与键位置需对应如果键元素数多于值元素多余部分值用null代替<br>
* 如果值多于键忽略多余的值
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
* @return 标题内容Map
* @since 3.1.0
@ -433,11 +435,11 @@ public class IterUtil {
* 将键列表和值列表转换为Map<br>
* 以键为准值与键位置需对应如果键元素数多于值元素多余部分值用null代替<br>
* 如果值多于键忽略多余的值
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
*
* @param <K> 键类型
* @param <V> 值类型
* @param keys 键列表
* @param values 值列表
* @param isOrder 是否有序
* @return 标题内容Map
* @since 4.1.12
@ -455,14 +457,14 @@ public class IterUtil {
/**
* Iterator转List<br>
* 不判断直接生成新的List
*
* @param <E> 元素类型
*
* @param <E> 元素类型
* @param iter {@link Iterator}
* @return List
* @since 4.0.6
*/
public static <E> List<E> toList(Iterable<E> iter) {
if(null == iter) {
if (null == iter) {
return null;
}
return toList(iter.iterator());
@ -471,8 +473,8 @@ public class IterUtil {
/**
* Iterator转List<br>
* 不判断直接生成新的List
*
* @param <E> 元素类型
*
* @param <E> 元素类型
* @param iter {@link Iterator}
* @return List
* @since 4.0.6
@ -489,9 +491,9 @@ public class IterUtil {
* Enumeration转换为Iterator
* <p>
* Adapt the specified <code>Enumeration</code> to the <code>Iterator</code> interface
*
*
* @param <E> 集合元素类型
* @param e {@link Enumeration}
* @param e {@link Enumeration}
* @return {@link Iterator}
*/
public static <E> Iterator<E> asIterator(Enumeration<E> e) {
@ -500,8 +502,8 @@ public class IterUtil {
/**
* {@link Iterator} 转为 {@link Iterable}
*
* @param <E> 元素类型
*
* @param <E> 元素类型
* @param iter {@link Iterator}
* @return {@link Iterable}
*/
@ -511,8 +513,8 @@ public class IterUtil {
/**
* 获取集合的第一个元素
*
* @param <T> 集合元素类型
*
* @param <T> 集合元素类型
* @param iterable {@link Iterable}
* @return 第一个元素
*/
@ -525,8 +527,8 @@ public class IterUtil {
/**
* 获取集合的第一个元素
*
* @param <T> 集合元素类型
*
* @param <T> 集合元素类型
* @param iterator {@link Iterator}
* @return 第一个元素
*/
@ -540,7 +542,7 @@ public class IterUtil {
/**
* 获得{@link Iterable}对象的元素类型通过第一个非空元素判断<br>
* 注意此方法至少会调用多次next方法
*
*
* @param iterable {@link Iterable}
* @return 元素类型当列表为空或元素全部为null时返回null
*/
@ -555,7 +557,7 @@ public class IterUtil {
/**
* 获得{@link Iterator}对象的元素类型通过第一个非空元素判断<br>
* 注意此方法至少会调用多次next方法
*
*
* @param iterator {@link Iterator}
* @return 元素类型当列表为空或元素全部为null时返回null
*/
@ -569,42 +571,42 @@ public class IterUtil {
}
return null;
}
/**
* 过滤集合此方法在原集合上直接修改<br>
* 通过实现Filter接口完成元素的过滤这个Filter实现可以实现以下功能
*
*
* <pre>
* 1过滤出需要的对象{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
* </pre>
*
* @param <T> 集合类型
* @param <E> 集合元素类型
* @param iter 集合
*
* @param <T> 集合类型
* @param <E> 集合元素类型
* @param iter 集合
* @param filter 过滤器接口
* @return 编辑后的集合
* @since 4.6.5
*/
public static <T extends Iterable<E>, E> T filter(T iter, Filter<E> filter) {
if(null == iter) {
if (null == iter) {
return null;
}
filter(iter.iterator(), filter);
return iter;
}
/**
* 过滤集合此方法在原集合上直接修改<br>
* 通过实现Filter接口完成元素的过滤这个Filter实现可以实现以下功能
*
*
* <pre>
* 1过滤出需要的对象{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
* </pre>
*
* @param <E> 集合元素类型
* @param iter 集合
*
* @param <E> 集合元素类型
* @param iter 集合
* @param filter 过滤器接口
* @return 编辑后的集合
* @since 4.6.5
@ -614,11 +616,61 @@ public class IterUtil {
return iter;
}
while(iter.hasNext()) {
if(false == filter.accept(iter.next())) {
while (iter.hasNext()) {
if (false == filter.accept(iter.next())) {
iter.remove();
}
}
return iter;
}
/**
* Iterator转换为Map转换规则为<br>
* 按照keyFunc函数规则根据元素对象生成Key元素作为值
*
* @param <K> Map键类型
* @param <V> Map值类型
* @param iterator 数据列表
* @param map Map对象转换后的键值对加入此Map通过传入此对象自定义Map类型
* @param keyFunc 生成key的函数
* @return 生成的map
* @since 5.2.6
*/
public static <K, V> Map<K, V> toMap(Iterator<V> iterator, Map<K, V> map, Func1<V, K> keyFunc) {
return toMap(iterator, map, keyFunc, (value) -> value);
}
/**
* 集合转换为Map转换规则为<br>
* 按照keyFunc函数规则根据元素对象生成Key按照valueFunc函数规则根据元素对象生成value组成新的Map
*
* @param <K> Map键类型
* @param <V> Map值类型
* @param <E> 元素类型
* @param iterator 数据列表
* @param map Map对象转换后的键值对加入此Map通过传入此对象自定义Map类型
* @param keyFunc 生成key的函数
* @return 生成的map
* @since 5.2.6
*/
public static <K, V, E> Map<K, V> toMap(Iterator<E> iterator, Map<K, V> map, Func1<E, K> keyFunc, Func1<E, V> valueFunc) {
if (null == iterator) {
return map;
}
if (null == map) {
map = MapUtil.newHashMap(true);
}
E element;
while (iterator.hasNext()) {
element = iterator.next();
try {
map.put(keyFunc.call(element), valueFunc.call(element));
} catch (Exception e) {
throw new UtilException(e);
}
}
return map;
}
}

View File

@ -12,6 +12,7 @@ import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.HttpCookie;
import java.net.IDN;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -29,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@ -647,7 +649,7 @@ public class NetUtil {
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (false == isUnknow(subIp)) {
if (false == isUnknown(subIp)) {
ip = subIp;
break;
}
@ -662,8 +664,20 @@ public class NetUtil {
* @param checkString 被检测的字符串
* @return 是否未知
* @since 4.4.1
* @deprecated 拼写错误请使用{@link #isUnknown(String)}
*/
public static boolean isUnknow(String checkString) {
return isUnknown(checkString);
}
/**
* 检测给定字符串是否为未知多用于检测HTTP请求相关<br>
*
* @param checkString 被检测的字符串
* @return 是否未知
* @since 5.2.6
*/
public static boolean isUnknown(String checkString) {
return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
@ -692,6 +706,19 @@ public class NetUtil {
}
}
/**
* 解析Cookie信息
*
* @param cookieStr Cookie字符串
* @return cookie字符串
* @since 5.2.6
*/
public static List<HttpCookie> parseCookies(String cookieStr){
if(StrUtil.isBlank(cookieStr)){
return CollUtil.newArrayList();
}
return HttpCookie.parse(cookieStr);
}
// ----------------------------------------------------------------------------------------- Private method start
/**

View File

@ -277,6 +277,32 @@ public class CollUtilTest {
Assert.assertEquals("张三", list.get(2).getName());
}
@Test
public void fieldValueMapTest() {
List<TestBean> list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), //
new TestBean("李四", 13, DateUtil.parse("2018-03-01")), //
new TestBean("王五", 12, DateUtil.parse("2018-04-01"))//
);
final Map<String, TestBean> map = CollUtil.fieldValueMap(list, "name");
Assert.assertEquals("李四", map.get("李四").getName());
Assert.assertEquals("王五", map.get("王五").getName());
Assert.assertEquals("张三", map.get("张三").getName());
}
@Test
public void fieldValueAsMapTest() {
List<TestBean> list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), //
new TestBean("李四", 13, DateUtil.parse("2018-03-01")), //
new TestBean("王五", 14, DateUtil.parse("2018-04-01"))//
);
final Map<String, Integer> map = CollUtil.fieldValueAsMap(list, "name", "age");
Assert.assertEquals(new Integer(12), map.get("张三"));
Assert.assertEquals(new Integer(13), map.get("李四"));
Assert.assertEquals(new Integer(14), map.get("王五"));
}
public static class TestBean {
private String name;
private int age;
@ -600,4 +626,25 @@ public class CollUtilTest {
Assert.assertEquals(3, map.get("c").intValue());
Assert.assertEquals(4, map.get("d").intValue());
}
@Test
public void toMapTest(){
Collection<String> keys = CollUtil.newArrayList("a", "b", "c", "d");
final Map<String, String> map = CollUtil.toMap(keys, new HashMap<>(), (value)->"key" + value);
Assert.assertEquals("a", map.get("keya"));
Assert.assertEquals("b", map.get("keyb"));
Assert.assertEquals("c", map.get("keyc"));
Assert.assertEquals("d", map.get("keyd"));
}
@Test
public void countMapTest() {
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d");
Map<String, Integer> countMap = CollUtil.countMap(list);
Assert.assertEquals(Integer.valueOf(2), countMap.get("a"));
Assert.assertEquals(Integer.valueOf(2), countMap.get("b"));
Assert.assertEquals(Integer.valueOf(2), countMap.get("c"));
Assert.assertEquals(Integer.valueOf(1), countMap.get("d"));
}
}

View File

@ -1,11 +1,11 @@
package cn.hutool.core.collection;
import java.util.ArrayList;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Map;
/**
* {@link IterUtil} 单元测试
* @author looly
@ -13,17 +13,6 @@ import org.junit.Test;
*/
public class IterUtilTest {
@Test
public void countMapTest() {
ArrayList<String> list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d");
Map<String, Integer> countMap = IterUtil.countMap(list);
Assert.assertEquals(Integer.valueOf(2), countMap.get("a"));
Assert.assertEquals(Integer.valueOf(2), countMap.get("b"));
Assert.assertEquals(Integer.valueOf(2), countMap.get("c"));
Assert.assertEquals(Integer.valueOf(1), countMap.get("d"));
}
@Test
public void fieldValueMapTest() {
ArrayList<Car> carList = CollUtil.newArrayList(new Car("123", "大众"), new Car("345", "奔驰"), new Car("567", "路虎"));

View File

@ -6,7 +6,9 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.net.HttpCookie;
import java.net.InetAddress;
import java.util.List;
/**
* NetUtil单元测试
@ -57,4 +59,18 @@ public class NetUtilTest {
public void isUsableLocalPortTest(){
Assert.assertTrue(NetUtil.isUsableLocalPort(80));
}
@Test
public void parseCookiesTest(){
String cookieStr = "cookieName=\"cookieValue\";Path=\"/\";Domain=\"cookiedomain.com\"";
final List<HttpCookie> httpCookies = NetUtil.parseCookies(cookieStr);
Assert.assertEquals(1, httpCookies.size());
final HttpCookie httpCookie = httpCookies.get(0);
Assert.assertEquals(0, httpCookie.getVersion());
Assert.assertEquals("cookieName", httpCookie.getName());
Assert.assertEquals("cookieValue", httpCookie.getValue());
Assert.assertEquals("/", httpCookie.getPath());
Assert.assertEquals("cookiedomain.com", httpCookie.getDomain());
}
}

View File

@ -3,11 +3,14 @@ package cn.hutool.extra.servlet;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
@ -227,13 +230,13 @@ public class ServletUtil {
String ip;
for (String header : headerNames) {
ip = request.getHeader(header);
if (false == isUnknow(ip)) {
return getMultistageReverseProxyIp(ip);
if (false == NetUtil.isUnknown(ip)) {
return NetUtil.getMultistageReverseProxyIp(ip);
}
}
ip = request.getRemoteAddr();
return getMultistageReverseProxyIp(ip);
return NetUtil.getMultistageReverseProxyIp(ip);
}
/**
@ -415,14 +418,10 @@ public class ServletUtil {
* @return Cookie map
*/
public static Map<String, Cookie> readCookieMap(HttpServletRequest httpServletRequest) {
final Map<String, Cookie> cookieMap = new CaseInsensitiveMap<>();
final Cookie[] cookies = httpServletRequest.getCookies();
if (null != cookies) {
for (Cookie cookie : cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
return IterUtil.toMap(
new ArrayIter<>(httpServletRequest.getCookies()),
new CaseInsensitiveMap<>(),
Cookie::getName);
}
/**
@ -614,37 +613,4 @@ public class ServletUtil {
}
}
// --------------------------------------------------------- Response end
// --------------------------------------------------------- Private methd start
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
private static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (false == isUnknow(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知多用于检测HTTP请求相关<br>
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
private static boolean isUnknow(String checkString) {
return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
// --------------------------------------------------------- Private methd end
}

View File

@ -1,6 +1,10 @@
package cn.hutool.http.server;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
@ -12,8 +16,11 @@ import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
/**
* Http请求对象{@link HttpExchange}封装
@ -25,6 +32,8 @@ public class HttpRequest {
private final HttpExchange httpExchange;
private Map<String, HttpCookie> cookieCache;
/**
* 构造
*
@ -97,6 +106,16 @@ public class HttpRequest {
return this.httpExchange.getRequestHeaders();
}
/**
* 获得请求header中的信息
*
* @param headerKey 头信息的KEY
* @return header值
*/
public String getHeader(Header headerKey) {
return getHeader(headerKey.toString());
}
/**
* 获得请求header中的信息
*
@ -122,13 +141,22 @@ public class HttpRequest {
return null;
}
/**
* 获取Content-Type头信息
*
* @return Content-Type头信息
*/
public String getContentType() {
return getHeader(Header.USER_AGENT);
}
/**
* 获得User-Agent
*
* @return User-Agent字符串
*/
public String getUserAgentStr() {
return getHeader("User-Agent");
return getHeader(Header.USER_AGENT);
}
/**
@ -140,6 +168,49 @@ public class HttpRequest {
return UserAgentUtil.parse(getUserAgentStr());
}
/**
* 获得Cookie信息字符串
*
* @return cookie字符串
*/
public String getCookiesStr() {
return getHeader(Header.COOKIE);
}
/**
* 获得Cookie信息列表
*
* @return Cookie信息列表
*/
public Collection<HttpCookie> getCookies() {
return getCookieMap().values();
}
/**
* 获得Cookie信息Map键为Cookie名值为HttpCookie对象
*
* @return Cookie信息Map
*/
public Map<String, HttpCookie> getCookieMap() {
if (null == this.cookieCache) {
cookieCache = CollUtil.toMap(
NetUtil.parseCookies(getCookiesStr()),
new CaseInsensitiveMap<>(),
HttpCookie::getName);
}
return cookieCache;
}
/**
* 获得指定Cookie名对应的HttpCookie对象
*
* @param cookieName Cookie名
* @return HttpCookie对象
*/
public HttpCookie getCookie(String cookieName) {
return getCookieMap().get(cookieName);
}
/**
* 获取请求体的流流中可以读取请求内容包括请求表单数据或文件上传数据
*
@ -156,7 +227,7 @@ public class HttpRequest {
* @return 请求
*/
public String getBody() {
final String contentType = getHeader(Header.CONTENT_TYPE.toString());
final String contentType = getContentType();
final String charsetStr = HttpUtil.getCharset(contentType);
final Charset charset = CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8);
@ -189,11 +260,66 @@ public class HttpRequest {
return false;
}
final String contentType = getHeader(Header.CONTENT_TYPE.toString());
final String contentType = getContentType();
if (StrUtil.isBlank(contentType)) {
return false;
}
return contentType.toLowerCase().startsWith("multipart/");
}
/**
* 获取客户端IP
*
* <p>
* 默认检测的Header:
*
* <pre>
* 1X-Forwarded-For
* 2X-Real-IP
* 3Proxy-Client-IP
* 4WL-Proxy-Client-IP
* </pre>
*
* <p>
* otherHeaderNames参数用于自定义检测的Header<br>
* 需要注意的是使用此方法获取的客户IP地址必须在Http服务器例如Nginx中配置头信息否则容易造成IP伪造
* </p>
*
* @param otherHeaderNames 其他自定义头文件通常在Http服务器例如Nginx中配置
* @return IP地址
*/
public String getClientIP(String... otherHeaderNames) {
String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
headers = ArrayUtil.addAll(headers, otherHeaderNames);
}
return getClientIPByHeader(headers);
}
/**
* 获取客户端IP
*
* <p>
* headerNames参数用于自定义检测的Header<br>
* 需要注意的是使用此方法获取的客户IP地址必须在Http服务器例如Nginx中配置头信息否则容易造成IP伪造
* </p>
*
* @param headerNames 自定义头通常在Http服务器例如Nginx中配置
* @return IP地址
* @since 4.4.1
*/
public String getClientIPByHeader(String... headerNames) {
String ip;
for (String header : headerNames) {
ip = getHeader(header);
if (false == NetUtil.isUnknown(ip)) {
return NetUtil.getMultistageReverseProxyIp(ip);
}
}
ip = this.httpExchange.getRemoteAddress().getHostName();
return NetUtil.getMultistageReverseProxyIp(ip);
}
}