cn.hutool.core.net.url.UrlQuery Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hutool-all Show documentation
Show all versions of hutool-all Show documentation
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
package cn.hutool.core.net.url;
import cn.hutool.core.codec.PercentCodec;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.TableMap;
import cn.hutool.core.net.FormUrlencoded;
import cn.hutool.core.net.RFC3986;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.StrUtil;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
/**
* URL中查询字符串部分的封装,类似于:
*
* key1=v1&key2=&key3=v3
*
* 查询封装分为解析查询字符串和构建查询字符串,解析可通过charset为null来自定义是否decode编码后的内容,
* 构建则通过charset是否为null是否encode参数键值对
*
* @author looly
* @since 5.3.1
*/
public class UrlQuery {
private final TableMap query;
/**
* 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
*/
private final boolean isFormUrlEncoded;
/**
* 是否严格模式,严格模式下,query的name和value中均不允许有分隔符。
*/
private boolean isStrict;
/**
* 构建UrlQuery
*
* @param queryMap 初始化的查询键值对
* @return UrlQuery
*/
public static UrlQuery of(Map extends CharSequence, ?> queryMap) {
return new UrlQuery(queryMap);
}
/**
* 构建UrlQuery
*
* @param queryMap 初始化的查询键值对
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
* @return UrlQuery
*/
public static UrlQuery of(Map extends CharSequence, ?> queryMap, boolean isFormUrlEncoded) {
return new UrlQuery(queryMap, isFormUrlEncoded);
}
/**
* 构建UrlQuery
*
* @param queryStr 初始化的查询字符串
* @param charset decode用的编码,null表示不做decode
* @return UrlQuery
*/
public static UrlQuery of(String queryStr, Charset charset) {
return of(queryStr, charset, true);
}
/**
* 构建UrlQuery
*
* @param queryStr 初始化的查询字符串
* @param charset decode用的编码,null表示不做decode
* @param autoRemovePath 是否自动去除path部分,{@code true}则自动去除第一个?前的内容
* @return UrlQuery
* @since 5.5.8
*/
public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath) {
return of(queryStr, charset, autoRemovePath, false);
}
/**
* 构建UrlQuery
*
* @param queryStr 初始化的查询字符串
* @param charset decode用的编码,null表示不做decode
* @param autoRemovePath 是否自动去除path部分,{@code true}则自动去除第一个?前的内容
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
* @return UrlQuery
* @since 5.7.16
*/
public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath, boolean isFormUrlEncoded) {
return new UrlQuery(isFormUrlEncoded).parse(queryStr, charset, autoRemovePath);
}
/**
* 构造
*/
public UrlQuery() {
this(null);
}
/**
* 构造
*
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
* @since 5.7.16
*/
public UrlQuery(boolean isFormUrlEncoded) {
this(null, isFormUrlEncoded);
}
/**
* 构造
*
* @param queryMap 初始化的查询键值对
*/
public UrlQuery(Map extends CharSequence, ?> queryMap) {
this(queryMap, false);
}
/**
* 构造
*
* @param queryMap 初始化的查询键值对
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
* @since 5.7.16
*/
public UrlQuery(Map extends CharSequence, ?> queryMap, boolean isFormUrlEncoded) {
if (MapUtil.isNotEmpty(queryMap)) {
query = new TableMap<>(queryMap.size());
addAll(queryMap);
} else {
query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
}
this.isFormUrlEncoded = isFormUrlEncoded;
}
/**
* 设置是否严格模式
* @param strict 是否严格模式
* @return this
* @since 5.8.20
*/
public UrlQuery setStrict(final boolean strict) {
isStrict = strict;
return this;
}
/**
* 增加键值对
*
* @param key 键
* @param value 值,集合和数组转换为逗号分隔形式
* @return this
*/
public UrlQuery add(CharSequence key, Object value) {
this.query.put(key, toStr(value));
return this;
}
/**
* 批量增加键值对
*
* @param queryMap query中的键值对
* @return this
*/
public UrlQuery addAll(Map extends CharSequence, ?> queryMap) {
if (MapUtil.isNotEmpty(queryMap)) {
queryMap.forEach(this::add);
}
return this;
}
/**
* 解析URL中的查询字符串
*
* @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3
* @param charset decode编码,null表示不做decode
* @return this
*/
public UrlQuery parse(String queryStr, Charset charset) {
return parse(queryStr, charset, true);
}
/**
* 解析URL中的查询字符串
*
* @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3
* @param charset decode编码,null表示不做decode
* @param autoRemovePath 是否自动去除path部分,{@code true}则自动去除第一个?前的内容
* @return this
* @since 5.5.8
*/
public UrlQuery parse(String queryStr, Charset charset, boolean autoRemovePath) {
if (StrUtil.isBlank(queryStr)) {
return this;
}
if (autoRemovePath) {
// 去掉Path部分
int pathEndPos = queryStr.indexOf('?');
if (pathEndPos > -1) {
queryStr = StrUtil.subSuf(queryStr, pathEndPos + 1);
if (StrUtil.isBlank(queryStr)) {
return this;
}
}
}
return doParse(queryStr, charset);
}
/**
* 获得查询的Map
*
* @return 查询的Map,只读
*/
public Map getQueryMap() {
return MapUtil.unmodifiable(this.query);
}
/**
* 获取查询值
*
* @param key 键
* @return 值
*/
public CharSequence get(CharSequence key) {
if (MapUtil.isEmpty(this.query)) {
return null;
}
return this.query.get(key);
}
/**
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
* 对于{@code null}处理规则如下:
*
* - 如果key为{@code null},则这个键值对忽略
* - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
*
*
* @param charset encode编码,null表示不做encode编码
* @return URL查询字符串
*/
public String build(Charset charset) {
return build(charset, true);
}
/**
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
* 对于{@code null}处理规则如下:
*
* - 如果key为{@code null},则这个键值对忽略
* - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
*
*
* @param charset encode编码,null表示不做encode编码
* @param encodePercent 是否编码`%`
* @return URL查询字符串
*/
public String build(Charset charset, boolean encodePercent) {
if (isFormUrlEncoded) {
return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset, encodePercent);
}
if (isStrict) {
return build(RFC3986.QUERY_PARAM_NAME_STRICT, RFC3986.QUERY_PARAM_VALUE_STRICT, charset, encodePercent);
}
return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset, encodePercent);
}
/**
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
* 对于{@code null}处理规则如下:
*
* - 如果key为{@code null},则这个键值对忽略
* - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
*
*
* @param keyCoder 键值对中键的编码器
* @param valueCoder 键值对中值的编码器
* @param charset encode编码,null表示不做encode编码
* @return URL查询字符串
* @since 5.7.16
*/
public String build(PercentCodec keyCoder, PercentCodec valueCoder, Charset charset) {
return build(keyCoder, valueCoder, charset, true);
}
/**
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
* 对于{@code null}处理规则如下:
*
* - 如果key为{@code null},则这个键值对忽略
* - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
*
*
* @param keyCoder 键值对中键的编码器
* @param valueCoder 键值对中值的编码器
* @param charset encode编码,null表示不做encode编码
* @param encodePercent 是否编码`%`
* @return URL查询字符串
* @since 5.8.0
*/
public String build(PercentCodec keyCoder, PercentCodec valueCoder, Charset charset, boolean encodePercent) {
if (MapUtil.isEmpty(this.query)) {
return StrUtil.EMPTY;
}
final char[] safeChars = encodePercent ? null : new char[]{'%'};
final StringBuilder sb = new StringBuilder();
CharSequence name;
CharSequence value;
for (Map.Entry entry : this.query) {
name = entry.getKey();
if (null != name) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(keyCoder.encode(name, charset, safeChars));
value = entry.getValue();
if (null != value) {
sb.append("=").append(valueCoder.encode(value, charset, safeChars));
}
}
}
return sb.toString();
}
/**
* 生成查询字符串,类似于aaa=111&bbb=222
* 此方法不对任何特殊字符编码,仅用于输出显示
*
* @return 查询字符串
*/
@Override
public String toString() {
return build(null);
}
/**
* 解析URL中的查询字符串
* 规则见:https://url.spec.whatwg.org/#urlencoded-parsing
*
* @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3
* @param charset decode编码,null表示不做decode
* @return this
* @since 5.5.8
*/
private UrlQuery doParse(String queryStr, Charset charset) {
final int len = queryStr.length();
String name = null;
int pos = 0; // 未处理字符开始位置
int i; // 未处理字符结束位置
char c; // 当前字符
for (i = 0; i < len; i++) {
c = queryStr.charAt(i);
switch (c) {
case '='://键和值的分界符
if (null == name) {
// name可以是""
name = queryStr.substring(pos, i);
// 开始位置从分节符后开始
pos = i + 1;
}
// 当=不作为分界符时,按照普通字符对待
break;
case '&'://键值对之间的分界符
addParam(name, queryStr.substring(pos, i), charset);
name = null;
if (i + 4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) {
// issue#850@Github,"&"转义为"&"
i += 4;
}
// 开始位置从分节符后开始
pos = i + 1;
break;
}
}
// 处理结尾
addParam(name, queryStr.substring(pos, i), charset);
return this;
}
/**
* 对象转换为字符串,用于URL的Query中
*
* @param value 值
* @return 字符串
*/
private static String toStr(Object value) {
String result;
if (value instanceof Iterable) {
result = CollUtil.join((Iterable>) value, ",");
} else if (value instanceof Iterator) {
result = IterUtil.join((Iterator>) value, ",");
} else {
result = Convert.toStr(value);
}
return result;
}
/**
* 将键值对加入到值为List类型的Map中,,情况如下:
*
* 1、key和value都不为null,类似于 "a=1"或者"=1",直接put
* 2、key不为null,value为null,类似于 "a=",值传""
* 3、key为null,value不为null,类似于 "1"
* 4、key和value都为null,忽略之,比如&&
*
*
* @param key key,为null则value作为key
* @param value value,为null且key不为null时传入""
* @param charset 编码
*/
private void addParam(String key, String value, Charset charset) {
if (null != key) {
final String actualKey = URLDecoder.decode(key, charset, isFormUrlEncoded);
this.query.put(actualKey, StrUtil.nullToEmpty(URLDecoder.decode(value, charset, isFormUrlEncoded)));
} else if (null != value) {
// name为空,value作为name,value赋值null
this.query.put(URLDecoder.decode(value, charset, isFormUrlEncoded), null);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy