diff --git a/bin/build.bat b/bin/build.bat index 4239e02..0c4fca2 100644 --- a/bin/build.bat +++ b/bin/build.bat @@ -1,22 +1,22 @@ @echo off setlocal -rem 获取当前 Java 版本信息并搜索是否包含 "17." -java -version 2>&1 | find "17." >nul +rem 获取当前 Java 版本信息并搜索是否包含 "11." +java -version 2>&1 | find "11." >nul rem 如果找不到 JDK 17.x,则下载并安装 if errorlevel 1 ( - echo JDK 17.x not found. Downloading and installing... + echo JDK 11.x not found. Downloading and installing... REM 这里添加下载和安装 JDK 的代码 rem 验证安装 java -version - echo JDK 17.x installation complete. + echo JDK 11.x installation complete. ) else ( - echo JDK 17.x is already installed. + echo JDK 11.x is already installed. ) endlocal -pause \ No newline at end of file +pause diff --git a/bin/netdisk-fast-download.service b/bin/netdisk-fast-download.service index 50ae18f..ee22801 100644 --- a/bin/netdisk-fast-download.service +++ b/bin/netdisk-fast-download.service @@ -7,7 +7,7 @@ Wants=network-online.target [Service] Type=simple # User=USER -# 需要JDK17及以上版本 注意修改为自己的路径 +# 需要JDK11及以上版本 注意修改为自己的路径 ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download.jar ExecStop=/bin/kill -s QUIT $MAINPID Restart=always diff --git a/bin/run.bat b/bin/run.bat index 615ca8a..fde60be 100644 --- a/bin/run.bat +++ b/bin/run.bat @@ -1,4 +1,6 @@ @echo off && @chcp 65001 > nul + +:: 需要JDK11及以上版本和Windows环境变量已配置jdk的路径 pushd %~dp0 set LIB_DIR=%~dp0 for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download.jar') do ( diff --git a/core-database/pom.xml b/core-database/pom.xml index 9c15186..51deaaf 100644 --- a/core-database/pom.xml +++ b/core-database/pom.xml @@ -12,7 +12,7 @@ core-database - 17 + 11 UTF-8 2.0.5 3.12.0 diff --git a/core/src/main/java/cn/qaiu/vx/core/Deploy.java b/core/src/main/java/cn/qaiu/vx/core/Deploy.java index c25e5c3..676d236 100644 --- a/core/src/main/java/cn/qaiu/vx/core/Deploy.java +++ b/core/src/main/java/cn/qaiu/vx/core/Deploy.java @@ -87,19 +87,15 @@ public final class Deploy { var calendar = Calendar.getInstance(); calendar.setTime(new Date()); var year = calendar.get(Calendar.YEAR); - var logoTemplate = """ - - Web Server powered by:\s - ____ ____ _ _ _ \s - |_^^_| |_^^_| / |_ | | | | \s - \\ \\ / /.---. _ .--.`| |-' _ __ | |__| |_ \s - \\ \\ / // /__\\\\[ `/'`\\]| | [ \\ [ ]|____ _|\s - \\ V / | \\__., | | | |, _ > ' < _| |_ \s - \\_/ '.__.'[___] \\__/(_)[__]`\\_] |_____|\s - Version: %s; Framework version: %s; %s©%d. - - """; + String logoTemplate = "Web Server powered by: \n" + + " ____ ____ _ _ _ \n" + + "|_^^_| |_^^_| / |_ | | | | \n" + + " \\ \\ / /.---. _ .--.`| |-' _ __ | |__| |_ \n" + + " \\ \\ / // /__\\\\[ `/'`\\]| | [ \\ [ ]|____ _|\n" + + " \\ V / | \\__., | | | |, _ > ' < _| |_ \n" + + " \\_/ '.__.'[___] \\__/(_)[__]`\\_] |_____|\n" + + " Version: %s; Framework version: %s; JDK11; %s©%d.\n\n"; System.out.printf(logoTemplate, conf.getString("version_app"), VersionCommand.getVersion(), @@ -132,9 +128,10 @@ public final class Deploy { localMap.put(GLOBAL_CONFIG, globalConfig); localMap.put(CUSTOM_CONFIG, customConfig); localMap.put(SERVER, globalConfig.getJsonObject(SERVER)); - var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> { + var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(() -> { handle.handle(globalConfig); - bch.complete("other handle complete"); + LOGGER.info("other handle complete"); + return null; }); // 部署 路由、异步service、反向代理 服务 @@ -142,7 +139,7 @@ public final class Deploy { var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service")); var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy")); - CompositeFuture.all(future1, future2, future3, future0) + Future.all(future1, future2, future3, future0) .onSuccess(this::deployWorkVerticalSuccess) .onFailure(this::deployVerticalFailed); } @@ -181,7 +178,7 @@ public final class Deploy { private DeploymentOptions getWorkDeploymentOptions(String name, int ins) { return new DeploymentOptions() .setWorkerPoolName(name) - .setWorker(true) + .setThreadingModel(ThreadingModel.WORKER) .setInstances(ins); } diff --git a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java index 1ae027c..4185452 100644 --- a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java +++ b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java @@ -112,7 +112,7 @@ public class RouterHandlerFactory implements BaseHttpApi { return Integer.compare(routeHandler2.order(), routeHandler1.order()); }; // 获取处理器类列表 - List> sortedHandlers = handlers.stream().sorted(comparator).toList(); + List> sortedHandlers = handlers.stream().sorted(comparator).collect(Collectors.toList()); for (Class handler : sortedHandlers) { try { // 注册请求处理方法 @@ -153,7 +153,7 @@ public class RouterHandlerFactory implements BaseHttpApi { methodList.addAll(Stream.of(methods).filter( method -> method.isAnnotationPresent(SockRouteMapper.class) - ).toList()); + ).collect(Collectors.toList())); // 依次注册处理方法 for (Method method : methodList) { diff --git a/core/src/main/java/cn/qaiu/vx/core/model/JsonResult.java b/core/src/main/java/cn/qaiu/vx/core/model/JsonResult.java index 183c47d..fcefce6 100644 --- a/core/src/main/java/cn/qaiu/vx/core/model/JsonResult.java +++ b/core/src/main/java/cn/qaiu/vx/core/model/JsonResult.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.vertx.core.json.JsonObject; import org.apache.commons.lang3.StringUtils; -import java.io.Serial; import java.io.Serializable; /** @@ -17,7 +16,6 @@ import java.io.Serializable; */ public class JsonResult implements Serializable { - @Serial private static final long serialVersionUID = 1L; private static final int SUCCESS_CODE = 200; diff --git a/note.txt b/note.txt index 93c71d6..3f0b945 100644 --- a/note.txt +++ b/note.txt @@ -22,3 +22,9 @@ Cloudreve自建网盘 (ce) {origin}/s/{shareKey} https://f.ws59.cn/f/e3peohu6192 + + +短链接设计 + + + diff --git a/parser/src/main/java/cn/qaiu/parser/IPanTool.java b/parser/src/main/java/cn/qaiu/parser/IPanTool.java index d01a57e..88a2fb6 100644 --- a/parser/src/main/java/cn/qaiu/parser/IPanTool.java +++ b/parser/src/main/java/cn/qaiu/parser/IPanTool.java @@ -7,24 +7,22 @@ public interface IPanTool { Future parse(); static IPanTool typeMatching(String type, String key, String pwd) { - return switch (type) { - case "lz" -> new LzTool(key, pwd); - case "cow" -> new CowTool(key, pwd); - case "ec" -> new EcTool(key, pwd); - case "fc" -> new FcTool(key, pwd); - case "uc" -> new UcTool(key, pwd); - case "ye" -> new YeTool(key, pwd); - case "fj" -> new FjTool(key, pwd); - case "qk" -> new QkTool(key, pwd); - case "le" -> new LeTool(key, pwd); - case "ws" -> new WsTool(key, pwd); - case "qq" -> new QQTool(key, pwd); - case "iz" -> new IzTool(key, pwd); - case "ce" -> new CeTool(key, pwd); - default -> { - throw new UnsupportedOperationException("未知分享类型"); - } - }; + switch (type) { + case "lz": return new LzTool(key, pwd); + case "cow": return new CowTool(key, pwd); + case "ec": return new EcTool(key, pwd); + case "fc": return new FcTool(key, pwd); + case "uc": return new UcTool(key, pwd); + case "ye": return new YeTool(key, pwd); + case "fj": return new FjTool(key, pwd); + case "qk": return new QkTool(key, pwd); + case "le": return new LeTool(key, pwd); + case "ws": return new WsTool(key, pwd); + case "qq": return new QQTool(key, pwd); + case "iz": return new IzTool(key, pwd); + case "ce": return new CeTool(key, pwd); + default: throw new UnsupportedOperationException("未知分享类型"); + } } static IPanTool shareURLPrefixMatching(String url, String pwd) { diff --git a/parser/src/main/java/cn/qaiu/util/AESUtils.java b/parser/src/main/java/cn/qaiu/util/AESUtils.java index 767ce10..5aca4d5 100644 --- a/parser/src/main/java/cn/qaiu/util/AESUtils.java +++ b/parser/src/main/java/cn/qaiu/util/AESUtils.java @@ -1,5 +1,6 @@ package cn.qaiu.util; +import cn.qaiu.util.jdk17halper.HexFormat; import org.apache.commons.lang3.StringUtils; import javax.crypto.*; @@ -12,7 +13,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Date; -import java.util.HexFormat; import java.util.Random; /** diff --git a/parser/src/main/java/cn/qaiu/util/JsContent.java b/parser/src/main/java/cn/qaiu/util/JsContent.java index ed80ee7..3f68704 100644 --- a/parser/src/main/java/cn/qaiu/util/JsContent.java +++ b/parser/src/main/java/cn/qaiu/util/JsContent.java @@ -1,152 +1,160 @@ package cn.qaiu.util; public interface JsContent { - String ye123 = """ - /* - https://statics.123pan.com/share-static/dist/umi.fb72555e.js - eaefamemdead - eaefameidldy - _0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥 - - _0x1e2592 1690439821 时间戳 - _0x48562f 5790548 随机码 - _0x1e37d5 /b/api/share/download/info - _0x4e2d74 web - _0x56f040 3 - _0x43bdc6 1946841013 加密时间HASH戳 - - >>>> - _0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e) - 加密时间HASH戳 = 时间戳-随机码-秘钥 - */ - - function _0x1b5d95(_0x278d1a) { - var _0x839b57, - _0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : 0x8; - if (0x0 === arguments['length']) - return null; - 'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)), - _0x839b57 = new Date(_0x278d1a)); - var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']() - , _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc; - return _0x839b57 = new Date(_0x3732dc), - { - 'y': _0x839b57['getFullYear'](), - 'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : _0x839b57['getMonth']() + 0x1, - 'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : _0x839b57['getDate'](), - 'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : _0x839b57['getHours'](), - 'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : _0x839b57['getMinutes']() - }; - } - - - function _0x4f141a(_0x4075b1) { - - for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? arguments[0x1] : 0xa, - _0x2fc680 = function() { - for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) { - _0x515c63 = _0x4cbdba; - for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++) - _0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1; - _0x361314[_0x4cbdba] = _0x515c63; - } - return _0x361314; - }, - _0x4aed86 = _0x2fc680(), - _0x5880f0 = _0x4075b1, - _0x492393 = -0x1, _0x25d82c = 0x0; - _0x25d82c < _0x5880f0['length']; - _0x25d82c++) - - _0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt(_0x25d82c))]; - return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0, - _0x492393.toString(_0x4eddcb); - } - - - function getSign(_0x1e37d5) { - var _0x4e2d74 = 'web'; - var _0x56f040 = 3; - var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() * 0x3e8 + 28800000) / 0x3e8).toString(); - var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z'; - var _0x48562f = Math['round'](0x989680 * Math['random']()); - - var _0x2f7dfc; - var _0x35a889; - var _0x36f983; - var _0x3b043d; - var _0x5bc73b; - var _0x4b30b2; - var _0x32399e; - var _0x25d94e; - var _0x373490; - for (var _0x1c540f in (_0x2f7dfc = key.split(','), - _0x35a889 = _0x1b5d95(_0x1e2592), - _0x36f983 = _0x35a889['y'], - _0x3b043d = _0x35a889['m'], - _0x5bc73b = _0x35a889['d'], - _0x4b30b2 = _0x35a889['h'], - _0x32399e = _0x35a889['f'], - _0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''), - _0x373490 = [], - _0x25d94e)) - _0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]); - var _0x43bdc6; - var _0x406c4e; - return _0x43bdc6 = _0x4f141a(_0x373490['join']('')), - _0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat'](_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)), - [_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)]; - } - - """; - String lz = """ - /** - * 蓝奏云解析器js签名获取工具 - */ - - var signObj; - - - var $, jQuery; - - $ = jQuery = function () { - return new jQuery.fn.init(); - } - - jQuery.fn = jQuery.prototype = { - init: function () { - return { - focus: function (a) { - - }, - keyup: function(a) { - - }, - ajax: function (obj) { - signObj = obj - } - - } - }, - - } - - jQuery.fn.init.prototype = jQuery.fn; - - - // 伪装jquery.ajax函数获取关键数据 - $.ajax = function (obj) { - signObj = obj - } - - var document = { - getElementById: function (v) { - return { - value: 'v' - } - }, - } - - """; + String ye123 = "/*\n" + + " https://statics.123pan.com/share-static/dist/umi.fb72555e.js\n" + + " eaefamemdead\n" + + " eaefameidldy\n" + + " _0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥\n" + + " \n" + + " _0x1e2592 1690439821 时间戳\n" + + " _0x48562f 5790548 随机码\n" + + " _0x1e37d5 /b/api/share/download/info\n" + + " _0x4e2d74 web\n" + + " _0x56f040 3\n" + + " _0x43bdc6 1946841013 加密时间HASH戳\n" + + " \n" + + " >>>>\n" + + " _0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)\n" + + " 加密时间HASH戳 = 时间戳-随机码-秘钥\n" + + " */\n" + + " \n" + + " function _0x1b5d95(_0x278d1a) {\n" + + " var _0x839b57,\n" + + " _0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : " + + "0x8;\n" + + " if (0x0 === arguments['length'])\n" + + " return null;\n" + + " 'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)" + + "['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)),\n" + + " _0x839b57 = new Date(_0x278d1a));\n" + + " var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']()\n" + + " , _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc;\n" + + " return _0x839b57 = new Date(_0x3732dc),\n" + + " {\n" + + " 'y': _0x839b57['getFullYear'](),\n" + + " 'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : " + + "_0x839b57['getMonth']() + 0x1,\n" + + " 'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : " + + "_0x839b57['getDate'](),\n" + + " 'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : " + + "_0x839b57['getHours'](),\n" + + " 'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : " + + "_0x839b57['getMinutes']()\n" + + " };\n" + + " }\n" + + " \n" + + " \n" + + " function _0x4f141a(_0x4075b1) {\n" + + " \n" + + " for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? " + + "arguments[0x1] : 0xa,\n" + + " _0x2fc680 = function() {\n" + + " for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) " + + "{\n" + + " _0x515c63 = _0x4cbdba;\n" + + " for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++)\n" + + " _0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1;" + + "\n" + + " _0x361314[_0x4cbdba] = _0x515c63;\n" + + " }\n" + + " return _0x361314;\n" + + " },\n" + + " _0x4aed86 = _0x2fc680(),\n" + + " _0x5880f0 = _0x4075b1,\n" + + " _0x492393 = -0x1, _0x25d82c = 0x0;\n" + + " _0x25d82c < _0x5880f0['length'];\n" + + " _0x25d82c++)\n" + + " \n" + + " _0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt" + + "(_0x25d82c))];\n" + + " return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0,\n" + + " _0x492393.toString(_0x4eddcb);\n" + + " }\n" + + " \n" + + " \n" + + " function getSign(_0x1e37d5) {\n" + + " var _0x4e2d74 = 'web';\n" + + " var _0x56f040 = 3;\n" + + " var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() *" + + " 0x3e8 + 28800000) / 0x3e8).toString();\n" + + " var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z';\n" + + " var _0x48562f = Math['round'](0x989680 * Math['random']());\n" + + " \n" + + " var _0x2f7dfc;\n" + + " var _0x35a889;\n" + + " var _0x36f983;\n" + + " var _0x3b043d;\n" + + " var _0x5bc73b;\n" + + " var _0x4b30b2;\n" + + " var _0x32399e;\n" + + " var _0x25d94e;\n" + + " var _0x373490;\n" + + " for (var _0x1c540f in (_0x2f7dfc = key.split(','),\n" + + " _0x35a889 = _0x1b5d95(_0x1e2592),\n" + + " _0x36f983 = _0x35a889['y'],\n" + + " _0x3b043d = _0x35a889['m'],\n" + + " _0x5bc73b = _0x35a889['d'],\n" + + " _0x4b30b2 = _0x35a889['h'],\n" + + " _0x32399e = _0x35a889['f'],\n" + + " _0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''),\n" + + " _0x373490 = [],\n" + + " _0x25d94e))\n" + + " _0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]);\n" + + " var _0x43bdc6;\n" + + " var _0x406c4e;\n" + + " return _0x43bdc6 = _0x4f141a(_0x373490['join']('')),\n" + + " _0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat']" + + "(_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)),\n" + + " [_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)" + + "];\n" + + " }\n" + + " "; + String lz = "/**\n" + + " * 蓝奏云解析器js签名获取工具\n" + + " */\n" + + " \n" + + " var signObj;\n" + + " \n" + + " \n" + + " var $, jQuery;\n" + + " \n" + + " $ = jQuery = function () {\n" + + " return new jQuery.fn.init();\n" + + " }\n" + + " \n" + + " jQuery.fn = jQuery.prototype = {\n" + + " init: function () {\n" + + " return {\n" + + " focus: function (a) {\n" + + " \n" + + " },\n" + + " keyup: function(a) {\n" + + " \n" + + " },\n" + + " ajax: function (obj) {\n" + + " signObj = obj\n" + + " }\n" + + " \n" + + " }\n" + + " },\n" + + " \n" + + " }\n" + + " \n" + + " jQuery.fn.init.prototype = jQuery.fn;\n" + + " \n" + + " \n" + + " // 伪装jquery.ajax函数获取关键数据\n" + + " $.ajax = function (obj) {\n" + + " signObj = obj\n" + + " }\n" + + " \n" + + " var document = {\n" + + " getElementById: function (v) {\n" + + " return {\n" + + " value: 'v'\n" + + " }\n" + + " },\n" + + " }"; } diff --git a/parser/src/main/java/cn/qaiu/util/jdk17halper/HexFormat.java b/parser/src/main/java/cn/qaiu/util/jdk17halper/HexFormat.java new file mode 100644 index 0000000..e27cdd5 --- /dev/null +++ b/parser/src/main/java/cn/qaiu/util/jdk17halper/HexFormat.java @@ -0,0 +1,1081 @@ +/* + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package cn.qaiu.util.jdk17halper; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; + +/** + * {@code HexFormat} converts between bytes and chars and hex-encoded strings which may include + * additional formatting markup such as prefixes, suffixes, and delimiters. + *

+ * There are two factories of {@code HexFormat} with preset parameters {@link #of()} and + * {@link #ofDelimiter(String) ofDelimiter(delimiter)}. For other parameter combinations + * the {@code withXXX} methods return copies of {@code HexFormat} modified + * {@link #withPrefix(String)}, {@link #withSuffix(String)}, {@link #withDelimiter(String)} + * or choice of {@link #withUpperCase()} or {@link #withLowerCase()} parameters. + *

+ * For primitive to hexadecimal string conversions the {@code toHexDigits} + * methods include {@link #toHexDigits(byte)}, {@link #toHexDigits(int)}, and + * {@link #toHexDigits(long)}, etc. The default is to use lowercase characters {@code "0-9","a-f"}. + * For conversions producing uppercase hexadecimal the characters are {@code "0-9","A-F"}. + * Only the {@link HexFormat#isUpperCase() HexFormat.isUpperCase()} parameter is + * considered; the delimiter, prefix and suffix are not used. + * + *

+ * For hexadecimal string to primitive conversions the {@code fromHexDigits} + * methods include {@link #fromHexDigits(CharSequence) fromHexDigits(string)}, + * {@link #fromHexDigitsToLong(CharSequence) fromHexDigitsToLong(string)}, and + * {@link #fromHexDigit(int) fromHexDigit(int)} converts a single character or codepoint. + * For conversions from hexadecimal characters the digits and uppercase and lowercase + * characters in {@code "0-9", "a-f", and "A-F"} are converted to corresponding values + * {@code 0-15}. The delimiter, prefix, suffix, and uppercase parameters are not used. + * + *

+ * For byte array to formatted hexadecimal string conversions + * the {@code formatHex} methods include {@link #formatHex(byte[]) formatHex(byte[])} + * and {@link #formatHex(Appendable, byte[]) formatHex(Appendable, byte[])}. + * The formatted output is a string or is appended to an {@link Appendable} such as + * {@link StringBuilder} or {@link java.io.PrintStream}. + * Each byte value is formatted as the prefix, two hexadecimal characters from the + * uppercase or lowercase digits, and the suffix. + * A delimiter follows each formatted value, except the last. + * For conversions producing uppercase hexadecimal strings use {@link #withUpperCase()}. + * + *

+ * For formatted hexadecimal string to byte array conversions the + * {@code parseHex} methods include {@link #parseHex(CharSequence) parseHex(CharSequence)} and + * {@link #parseHex(char[], int, int) parseHex(char[], offset, length)}. + * Each byte value is parsed from the prefix, two case insensitive hexadecimal characters, + * and the suffix. A delimiter follows each formatted value, except the last. + * + * @apiNote + * For example, an individual byte is converted to a string of hexadecimal digits using + * {@link HexFormat#toHexDigits(int) toHexDigits(int)} and converted from a string to a + * primitive value using {@link HexFormat#fromHexDigits(CharSequence) fromHexDigits(string)}. + *

{@code
+ *     HexFormat hex = HexFormat.of();
+ *     byte b = 127;
+ *     String byteStr = hex.toHexDigits(b);
+ *
+ *     byte byteVal = (byte)hex.fromHexDigits(byteStr);
+ *     assert(byteStr.equals("7f"));
+ *     assert(b == byteVal);
+ *
+ *     // The hexadecimal digits are: "7f"
+ * }
+ *

+ * For a comma ({@code ", "}) separated format with a prefix ({@code "#"}) + * using lowercase hex digits the {@code HexFormat} is: + *

{@code
+ *     HexFormat commaFormat = HexFormat.ofDelimiter(", ").withPrefix("#");
+ *     byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127};
+ *     String str = commaFormat.formatHex(bytes);
+ *
+ *     byte[] parsed = commaFormat.parseHex(str);
+ *     assert(Arrays.equals(bytes, parsed));
+ *
+ *     // The formatted string is: "#00, #01, #02, #03, #7c, #7d, #7e, #7f"
+ * }
+ *

+ * For a fingerprint of byte values that uses the delimiter colon ({@code ":"}) + * and uppercase characters the {@code HexFormat} is: + *

{@code
+ *     HexFormat formatFingerprint = HexFormat.ofDelimiter(":").withUpperCase();
+ *     byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127};
+ *     String str = formatFingerprint.formatHex(bytes);
+ *     byte[] parsed = formatFingerprint.parseHex(str);
+ *     assert(Arrays.equals(bytes, parsed));
+ *
+ *     // The formatted string is: "00:01:02:03:7C:7D:7E:7F"
+ * }
+ * + *

+ * This is a value-based + * class; use of identity-sensitive operations (including reference equality + * ({@code ==}), identity hash code, or synchronization) on instances of + * {@code HexFormat} may have unpredictable results and should be avoided. + * The {@code equals} method should be used for comparisons. + *

+ * This class is immutable and thread-safe. + *

+ * Unless otherwise noted, passing a null argument to any method will cause a + * {@link java.lang.NullPointerException NullPointerException} to be thrown. + * + * @since 17 + */ + + +public final class HexFormat { + + private static final byte[] UPPERCASE_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + }; + private static final byte[] LOWERCASE_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', + }; + // Analysis has shown that generating the whole array allows the JIT to generate + // better code compared to a slimmed down array, such as one cutting off after 'f' + private static final byte[] DIGITS = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Format each byte of an array as a pair of hexadecimal digits. + * The hexadecimal characters are from lowercase alpha digits. + */ + private static final HexFormat HEX_FORMAT = + new HexFormat("", "", "", LOWERCASE_DIGITS); + + private static final byte[] EMPTY_BYTES = {}; + + private final String delimiter; + private final String prefix; + private final String suffix; + private final byte[] digits; + + /** + * Returns a HexFormat with a delimiter, prefix, suffix, and array of digits. + * + * @param delimiter a delimiter, non-null + * @param prefix a prefix, non-null + * @param suffix a suffix, non-null + * @param digits byte array of digits indexed by low nibble, non-null + * @throws NullPointerException if any argument is null + */ + private HexFormat(String delimiter, String prefix, String suffix, byte[] digits) { + this.delimiter = Objects.requireNonNull(delimiter, "delimiter"); + this.prefix = Objects.requireNonNull(prefix, "prefix"); + this.suffix = Objects.requireNonNull(suffix, "suffix"); + this.digits = digits; + } + + /** + * Returns a hexadecimal formatter with no delimiter and lowercase characters. + * The delimiter, prefix, and suffix are empty. + * The methods {@link #withDelimiter(String) withDelimiter}, + * {@link #withUpperCase() withUpperCase}, {@link #withLowerCase() withLowerCase}, + * {@link #withPrefix(String) withPrefix}, and {@link #withSuffix(String) withSuffix} + * return copies of formatters with new parameters. + * + * @return a hexadecimal formatter with no delimiter and lowercase characters + */ + public static HexFormat of() { + return HEX_FORMAT; + } + + /** + * Returns a hexadecimal formatter with the delimiter and lowercase characters. + * The prefix and suffix are empty. + * The methods {@link #withDelimiter(String) withDelimiter}, + * {@link #withUpperCase() withUpperCase}, {@link #withLowerCase() withLowerCase}, + * {@link #withPrefix(String) withPrefix}, and {@link #withSuffix(String) withSuffix} + * return copies of formatters with new parameters. + * + * @param delimiter a delimiter, non-null, may be empty + * @return a {@link HexFormat} with the delimiter and lowercase characters + */ + public static HexFormat ofDelimiter(String delimiter) { + return new HexFormat(delimiter, "", "", LOWERCASE_DIGITS); + } + + /** + * Returns a copy of this {@code HexFormat} with the delimiter. + * @param delimiter the delimiter, non-null, may be empty + * @return a copy of this {@code HexFormat} with the delimiter + */ + public HexFormat withDelimiter(String delimiter) { + return new HexFormat(delimiter, this.prefix, this.suffix, this.digits); + } + + /** + * Returns a copy of this {@code HexFormat} with the prefix. + * + * @param prefix a prefix, non-null, may be empty + * @return a copy of this {@code HexFormat} with the prefix + */ + public HexFormat withPrefix(String prefix) { + return new HexFormat(this.delimiter, prefix, this.suffix, this.digits); + } + + /** + * Returns a copy of this {@code HexFormat} with the suffix. + * + * @param suffix a suffix, non-null, may be empty + * @return a copy of this {@code HexFormat} with the suffix + */ + public HexFormat withSuffix(String suffix) { + return new HexFormat(this.delimiter, this.prefix, suffix, this.digits); + } + + /** + * Returns a copy of this {@code HexFormat} to use uppercase hexadecimal characters. + * The uppercase hexadecimal characters are {@code "0-9", "A-F"}. + * + * @return a copy of this {@code HexFormat} with uppercase hexadecimal characters + */ + public HexFormat withUpperCase() { + return new HexFormat(this.delimiter, this.prefix, this.suffix, UPPERCASE_DIGITS); + } + + /** + * Returns a copy of this {@code HexFormat} to use lowercase hexadecimal characters. + * The lowercase hexadecimal characters are {@code "0-9", "a-f"}. + * + * @return a copy of this {@code HexFormat} with lowercase hexadecimal characters + */ + public HexFormat withLowerCase() { + return new HexFormat(this.delimiter, this.prefix, this.suffix, LOWERCASE_DIGITS); + } + + /** + * Returns the delimiter between hexadecimal values in formatted hexadecimal strings. + * + * @return the delimiter, non-null, may be empty {@code ""} + */ + public String delimiter() { + return delimiter; + } + + /** + * Returns the prefix used for each hexadecimal value in formatted hexadecimal strings. + * + * @return the prefix, non-null, may be empty {@code ""} + */ + public String prefix() { + return prefix; + } + + /** + * Returns the suffix used for each hexadecimal value in formatted hexadecimal strings. + * + * @return the suffix, non-null, may be empty {@code ""} + */ + public String suffix() { + return suffix; + } + + /** + * Returns {@code true} if the hexadecimal digits are uppercase, + * otherwise {@code false}. + * + * @return {@code true} if the hexadecimal digits are uppercase, + * otherwise {@code false} + */ + public boolean isUpperCase() { + return Arrays.equals(digits, UPPERCASE_DIGITS); + } + + /** + * Returns a hexadecimal string formatted from a byte array. + * Each byte value is formatted as the prefix, two hexadecimal characters + * {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. + * A delimiter follows each formatted value, except the last. + * + * The behavior is equivalent to + * {@link #formatHex(byte[], int, int) formatHex(bytes, 0, bytes.length))}. + * + * @param bytes a non-null array of bytes + * @return a string hexadecimal formatting of the byte array + */ + public String formatHex(byte[] bytes) { + return formatHex(bytes, 0, bytes.length); + } + + /** + * Returns a hexadecimal string formatted from a byte array range. + * Each byte value is formatted as the prefix, two hexadecimal characters + * {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. + * A delimiter follows each formatted value, except the last. + * + * @param bytes a non-null array of bytes + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive + * @return a string hexadecimal formatting each byte of the array range + * @throws IndexOutOfBoundsException if the array range is out of bounds + */ + public String formatHex(byte[] bytes, int fromIndex, int toIndex) { + Objects.requireNonNull(bytes,"bytes"); + Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); + if (toIndex - fromIndex == 0) { + return ""; + } + // Format efficiently if possible + String s = formatOptDelimiter(bytes, fromIndex, toIndex); + if (s == null) { + long stride = prefix.length() + 2L + suffix.length() + delimiter.length(); + int capacity = checkMaxArraySize((toIndex - fromIndex) * stride - delimiter.length()); + StringBuilder sb = new StringBuilder(capacity); + formatHex(sb, bytes, fromIndex, toIndex); + s = sb.toString(); + } + return s; + } + + /** + * Appends formatted hexadecimal strings from a byte array to the {@link Appendable}. + * Each byte value is formatted as the prefix, two hexadecimal characters + * {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. + * A delimiter follows each formatted value, except the last. + * The formatted hexadecimal strings are appended in zero or more calls to the {@link Appendable} methods. + * + * @param The type of {@code Appendable} + * @param out an {@code Appendable}, non-null + * @param bytes a byte array + * @return the {@code Appendable} + * @throws UncheckedIOException if an I/O exception occurs appending to the output + */ + public A formatHex(A out, byte[] bytes) { + return formatHex(out, bytes, 0, bytes.length); + } + + /** + * Appends formatted hexadecimal strings from a byte array range to the {@link Appendable}. + * Each byte value is formatted as the prefix, two hexadecimal characters + * {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. + * A delimiter follows each formatted value, except the last. + * The formatted hexadecimal strings are appended in zero or more calls to the {@link Appendable} methods. + * + * @param The type of {@code Appendable} + * @param out an {@code Appendable}, non-null + * @param bytes a byte array, non-null + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return the {@code Appendable} + * @throws IndexOutOfBoundsException if the array range is out of bounds + * @throws UncheckedIOException if an I/O exception occurs appending to the output + */ + public A formatHex(A out, byte[] bytes, int fromIndex, int toIndex) { + Objects.requireNonNull(out, "out"); + Objects.requireNonNull(bytes, "bytes"); + Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); + + int length = toIndex - fromIndex; + if (length > 0) { + try { + String between = suffix + delimiter + prefix; + out.append(prefix); + toHexDigits(out, bytes[fromIndex]); + if (between.isEmpty()) { + for (int i = 1; i < length; i++) { + toHexDigits(out, bytes[fromIndex + i]); + } + } else { + for (int i = 1; i < length; i++) { + out.append(between); + toHexDigits(out, bytes[fromIndex + i]); + } + } + out.append(suffix); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe.getMessage(), ioe); + } + } + return out; + } + + /** + * Returns a string formatting of the range of bytes optimized + * for a single allocation. + * Prefix and suffix must be empty and the delimiter + * must be empty or a single byte character, otherwise null is returned. + * + * @param bytes the bytes, non-null + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return a String formatted or null for non-single byte delimiter + * or non-empty prefix or suffix + */ + private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) { + byte[] rep; + if (!prefix.isEmpty() || !suffix.isEmpty()) { + return null; + } + int length = toIndex - fromIndex; + if (delimiter.isEmpty()) { + // Allocate the byte array and fill in the hex pairs for each byte + rep = new byte[checkMaxArraySize(length * 2L)]; + for (int i = 0; i < length; i++) { + rep[i * 2] = (byte)toHighHexDigit(bytes[fromIndex + i]); + rep[i * 2 + 1] = (byte)toLowHexDigit(bytes[fromIndex + i]); + } + } else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) { + // Allocate the byte array and fill in the characters for the first byte + // Then insert the delimiter and hexadecimal characters for each of the remaining bytes + char sep = delimiter.charAt(0); + rep = new byte[checkMaxArraySize(length * 3L - 1L)]; + rep[0] = (byte) toHighHexDigit(bytes[fromIndex]); + rep[1] = (byte) toLowHexDigit(bytes[fromIndex]); + for (int i = 1; i < length; i++) { + rep[i * 3 - 1] = (byte) sep; + rep[i * 3 ] = (byte) toHighHexDigit(bytes[fromIndex + i]); + rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); + } + } else { + // Delimiter formatting not to a single byte + return null; + } + try { + // Return a new string using the bytes without making a copy + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Checked that the requested size for the result string is + * less than or equal to the max array size. + * + * @param length the requested size of a byte array. + * @return the length + * @throws OutOfMemoryError if the size is larger than Integer.MAX_VALUE + */ + private static int checkMaxArraySize(long length) { + if (length > Integer.MAX_VALUE) + throw new OutOfMemoryError("String size " + length + + " exceeds maximum " + Integer.MAX_VALUE); + return (int)length; + } + + /** + * Returns a byte array containing hexadecimal values parsed from the string. + * + * Each byte value is parsed from the prefix, two case insensitive hexadecimal characters, + * and the suffix. A delimiter follows each formatted value, except the last. + * The delimiters, prefixes, and suffixes strings must be present; they may be empty strings. + * A valid string consists only of the above format. + * + * @param string a string containing the byte values with prefix, hexadecimal digits, suffix, + * and delimiters + * @return a byte array with the values parsed from the string + * @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, + * the byte values are not hexadecimal characters, or if the delimiter is not present + * after all but the last byte value + */ + public byte[] parseHex(CharSequence string) { + return parseHex(string, 0, string.length()); + } + + /** + * Returns a byte array containing hexadecimal values parsed from a range of the string. + * + * Each byte value is parsed from the prefix, two case insensitive hexadecimal characters, + * and the suffix. A delimiter follows each formatted value, except the last. + * The delimiters, prefixes, and suffixes strings must be present; they may be empty strings. + * A valid string consists only of the above format. + * + * @param string a string range containing hexadecimal digits, + * delimiters, prefix, and suffix. + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return a byte array with the values parsed from the string range + * @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, + * the byte values are not hexadecimal characters, or if the delimiter is not present + * after all but the last byte value + * @throws IndexOutOfBoundsException if the string range is out of bounds + */ + public byte[] parseHex(CharSequence string, int fromIndex, int toIndex) { + Objects.requireNonNull(string, "string"); + Objects.checkFromToIndex(fromIndex, toIndex, string.length()); + + if (fromIndex != 0 || toIndex != string.length()) { + string = string.subSequence(fromIndex, toIndex); + } + + if (string.length() == 0) + return EMPTY_BYTES; + if (delimiter.isEmpty() && prefix.isEmpty() && suffix.isEmpty()) + return parseNoDelimiter(string); + + // avoid overflow for max length prefix or suffix + long valueChars = prefix.length() + 2L + suffix.length(); + long stride = valueChars + delimiter.length(); + if ((string.length() - valueChars) % stride != 0) + throw new IllegalArgumentException("extra or missing delimiters " + + "or values consisting of prefix, two hexadecimal digits, and suffix"); + + checkLiteral(string, 0, prefix); + checkLiteral(string, string.length() - suffix.length(), suffix); + String between = suffix + delimiter + prefix; + final int len = (int)((string.length() - valueChars) / stride + 1L); + byte[] bytes = new byte[len]; + int i, offset; + for (i = 0, offset = prefix.length(); i < len - 1; i++, offset += 2 + between.length()) { + bytes[i] = (byte) fromHexDigits(string, offset); + checkLiteral(string, offset + 2, between); + } + bytes[i] = (byte) fromHexDigits(string, offset); + + return bytes; + } + + /** + * Returns a byte array containing hexadecimal values parsed from + * a range of the character array. + * + * Each byte value is parsed from the prefix, two case insensitive hexadecimal characters, + * and the suffix. A delimiter follows each formatted value, except the last. + * The delimiters, prefixes, and suffixes strings must be present; they may be empty strings. + * A valid character array range consists only of the above format. + * + * @param chars a character array range containing an even number of hexadecimal digits, + * delimiters, prefix, and suffix. + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return a byte array with the values parsed from the character array range + * @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, + * the byte values are not hexadecimal characters, or if the delimiter is not present + * after all but the last byte value + * @throws IndexOutOfBoundsException if the character array range is out of bounds + */ + public byte[] parseHex(char[] chars, int fromIndex, int toIndex) { + Objects.requireNonNull(chars, "chars"); + Objects.checkFromToIndex(fromIndex, toIndex, chars.length); + CharBuffer cb = CharBuffer.wrap(chars, fromIndex, toIndex - fromIndex); + return parseHex(cb); + } + + /** + * Compare the literal and throw an exception if it does not match. + * Pre-condition: {@code index + literal.length() <= string.length()}. + * + * @param string a CharSequence + * @param index the index of the literal in the CharSequence + * @param literal the expected literal + * @throws IllegalArgumentException if the literal is not present + */ + private static void checkLiteral(CharSequence string, int index, String literal) { + assert index <= string.length() - literal.length() : "pre-checked invariant error"; + if (literal.isEmpty() || + (literal.length() == 1 && literal.charAt(0) == string.charAt(index))) { + return; + } + for (int i = 0; i < literal.length(); i++) { + if (string.charAt(index + i) != literal.charAt(i)) { + throw new IllegalArgumentException(escapeNL("found: \"" + + string.subSequence(index, index + literal.length()) + + "\", expected: \"" + literal + "\", index: " + index + + " ch: " + (int)string.charAt(index + i))); + } + } + } + + /** + * Expands new line characters to escaped newlines for display. + * + * @param string a string + * @return a string with newline characters escaped + */ + private static String escapeNL(String string) { + return string.replace("\n", "\\n") + .replace("\r", "\\r"); + } + + /** + * Returns the hexadecimal character for the low 4 bits of the value considering it to be a byte. + * If the parameter {@link #isUpperCase()} is {@code true} the + * character returned for values {@code 10-15} is uppercase {@code "A-F"}, + * otherwise the character returned is lowercase {@code "a-f"}. + * The values in the range {@code 0-9} are returned as {@code "0-9"}. + * + * @param value a value, only the low 4 bits {@code 0-3} of the value are used + * @return the hexadecimal character for the low 4 bits {@code 0-3} of the value + */ + public char toLowHexDigit(int value) { + return (char)digits[value & 0xf]; + } + + /** + * Returns the hexadecimal character for the high 4 bits of the value considering it to be a byte. + * If the parameter {@link #isUpperCase()} is {@code true} the + * character returned for values {@code 10-15} is uppercase {@code "A-F"}, + * otherwise the character returned is lowercase {@code "a-f"}. + * The values in the range {@code 0-9} are returned as {@code "0-9"}. + * + * @param value a value, only bits {@code 4-7} of the value are used + * @return the hexadecimal character for the bits {@code 4-7} of the value + */ + public char toHighHexDigit(int value) { + return (char)digits[(value >> 4) & 0xf]; + } + + /** + * Appends two hexadecimal characters for the byte value to the {@link Appendable}. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The hexadecimal characters are appended in one or more calls to the + * {@link Appendable} methods. The delimiter, prefix and suffix are not used. + * + * @param The type of {@code Appendable} + * @param out an {@code Appendable}, non-null + * @param value a byte value + * @return the {@code Appendable} + * @throws UncheckedIOException if an I/O exception occurs appending to the output + */ + public A toHexDigits(A out, byte value) { + Objects.requireNonNull(out, "out"); + try { + out.append(toHighHexDigit(value)); + out.append(toLowHexDigit(value)); + return out; + } catch (IOException ioe) { + throw new UncheckedIOException(ioe.getMessage(), ioe); + } + } + + /** + * Returns the two hexadecimal characters for the {@code byte} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value a byte value + * @return the two hexadecimal characters for the byte value + */ + public String toHexDigits(byte value) { + byte[] rep = new byte[2]; + rep[0] = (byte)toHighHexDigit(value); + rep[1] = (byte)toLowHexDigit(value); + try { + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Returns the four hexadecimal characters for the {@code char} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value a {@code char} value + * @return the four hexadecimal characters for the {@code char} value + */ + public String toHexDigits(char value) { + return toHexDigits((short)value); + } + + /** + * Returns the four hexadecimal characters for the {@code short} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value a {@code short} value + * @return the four hexadecimal characters for the {@code short} value + */ + public String toHexDigits(short value) { + byte[] rep = new byte[4]; + rep[0] = (byte)toHighHexDigit((byte)(value >> 8)); + rep[1] = (byte)toLowHexDigit((byte)(value >> 8)); + rep[2] = (byte)toHighHexDigit((byte)value); + rep[3] = (byte)toLowHexDigit((byte)value); + + try { + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Returns the eight hexadecimal characters for the {@code int} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value an {@code int} value + * @return the eight hexadecimal characters for the {@code int} value + * @see Integer#toHexString + */ + public String toHexDigits(int value) { + byte[] rep = new byte[8]; + rep[0] = (byte)toHighHexDigit((byte)(value >> 24)); + rep[1] = (byte)toLowHexDigit((byte)(value >> 24)); + rep[2] = (byte)toHighHexDigit((byte)(value >> 16)); + rep[3] = (byte)toLowHexDigit((byte)(value >> 16)); + rep[4] = (byte)toHighHexDigit((byte)(value >> 8)); + rep[5] = (byte)toLowHexDigit((byte)(value >> 8)); + rep[6] = (byte)toHighHexDigit((byte)value); + rep[7] = (byte)toLowHexDigit((byte)value); + + try { + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Returns the sixteen hexadecimal characters for the {@code long} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value a {@code long} value + * @return the sixteen hexadecimal characters for the {@code long} value + * @see Long#toHexString + */ + public String toHexDigits(long value) { + byte[] rep = new byte[16]; + rep[0] = (byte)toHighHexDigit((byte)(value >>> 56)); + rep[1] = (byte)toLowHexDigit((byte)(value >>> 56)); + rep[2] = (byte)toHighHexDigit((byte)(value >>> 48)); + rep[3] = (byte)toLowHexDigit((byte)(value >>> 48)); + rep[4] = (byte)toHighHexDigit((byte)(value >>> 40)); + rep[5] = (byte)toLowHexDigit((byte)(value >>> 40)); + rep[6] = (byte)toHighHexDigit((byte)(value >>> 32)); + rep[7] = (byte)toLowHexDigit((byte)(value >>> 32)); + rep[8] = (byte)toHighHexDigit((byte)(value >>> 24)); + rep[9] = (byte)toLowHexDigit((byte)(value >>> 24)); + rep[10] = (byte)toHighHexDigit((byte)(value >>> 16)); + rep[11] = (byte)toLowHexDigit((byte)(value >>> 16)); + rep[12] = (byte)toHighHexDigit((byte)(value >>> 8)); + rep[13] = (byte)toLowHexDigit((byte)(value >>> 8)); + rep[14] = (byte)toHighHexDigit((byte)value); + rep[15] = (byte)toLowHexDigit((byte)value); + + try { + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Returns up to sixteen hexadecimal characters for the {@code long} value. + * Each nibble (4 bits) from most significant to least significant of the value + * is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. + * The delimiter, prefix and suffix are not used. + * + * @param value a {@code long} value + * @param digits the number of hexadecimal digits to return, 0 to 16 + * @return the hexadecimal characters for the {@code long} value + * @throws IllegalArgumentException if {@code digits} is negative or greater than 16 + */ + public String toHexDigits(long value, int digits) { + if (digits < 0 || digits > 16) + throw new IllegalArgumentException("number of digits: " + digits); + if (digits == 0) + return ""; + byte[] rep = new byte[digits]; + for (int i = rep.length - 1; i >= 0; i--) { + rep[i] = (byte)toLowHexDigit((byte)(value)); + value = value >>> 4; + } + try { + return new String(rep, StandardCharsets.ISO_8859_1); + } catch (Exception cce) { + throw new AssertionError(cce); + } + } + + /** + * Returns a byte array containing the parsed hex digits. + * A valid string consists only of an even number of hex digits. + * + * @param string a string containing an even number of only hex digits + * @return a byte array + * @throws IllegalArgumentException if the string length is not valid or + * the string contains non-hexadecimal characters + */ + private static byte[] parseNoDelimiter(CharSequence string) { + if ((string.length() & 1) != 0) + throw new IllegalArgumentException("string length not even: " + + string.length()); + + byte[] bytes = new byte[string.length() / 2]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) fromHexDigits(string, i * 2); + } + + return bytes; + } + + /** + * Check the number of requested digits against a limit. + * + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @param limit the maximum allowed + * @return the length of the range + */ + private static int checkDigitCount(int fromIndex, int toIndex, int limit) { + int length = toIndex - fromIndex; + if (length > limit) + throw new IllegalArgumentException("string length greater than " + + limit + ": " + length); + return length; + } + + /** + * Returns {@code true} if the character is a valid hexadecimal character or codepoint. + * The valid hexadecimal characters are: + *

+ * @param ch a codepoint + * @return {@code true} if the character is valid a hexadecimal character, + * otherwise {@code false} + */ + public static boolean isHexDigit(int ch) { + return ((ch >>> 8) == 0 && DIGITS[ch] >= 0); + } + + /** + * Returns the value for the hexadecimal character or codepoint. + * The value is: + *
    + *
  • {@code (ch - '0')} for {@code '0'} through {@code '9'} inclusive, + *
  • {@code (ch - 'A' + 10)} for {@code 'A'} through {@code 'F'} inclusive, and + *
  • {@code (ch - 'a' + 10)} for {@code 'a'} through {@code 'f'} inclusive. + *
+ * + * @param ch a character or codepoint + * @return the value {@code 0-15} + * @throws NumberFormatException if the codepoint is not a hexadecimal character + */ + public static int fromHexDigit(int ch) { + int value; + if ((ch >>> 8) == 0 && (value = DIGITS[ch]) >= 0) { + return value; + } + throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch); + } + + /** + * Returns a value parsed from two hexadecimal characters in a string. + * The characters in the range from {@code index} to {@code index + 1}, + * inclusive, must be valid hex digits according to {@link #fromHexDigit(int)}. + * + * @param string a CharSequence containing the characters + * @param index the index of the first character of the range + * @return the value parsed from the string range + * @throws NumberFormatException if any of the characters in the range + * is not a hexadecimal character + * @throws IndexOutOfBoundsException if the range is out of bounds + * for the {@code CharSequence} + */ + private static int fromHexDigits(CharSequence string, int index) { + int high = fromHexDigit(string.charAt(index)); + int low = fromHexDigit(string.charAt(index + 1)); + return (high << 4) | low; + } + + /** + * Returns the {@code int} value parsed from a string of up to eight hexadecimal characters. + * The hexadecimal characters are parsed from most significant to least significant + * using {@link #fromHexDigit(int)} to form an unsigned value. + * The value is zero extended to 32 bits and is returned as an {@code int}. + * + * @apiNote + * {@link Integer#parseInt(String, int) Integer.parseInt(s, 16)} and + * {@link Integer#parseUnsignedInt(String, int) Integer.parseUnsignedInt(s, 16)} + * are similar but allow all Unicode hexadecimal digits defined by + * {@link Character#digit(char, int) Character.digit(ch, 16)}. + * {@code HexFormat} uses only hexadecimal characters + * {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. + * Signed hexadecimal strings can be parsed with {@link Integer#parseInt(String, int)}. + * + * @param string a CharSequence containing up to eight hexadecimal characters + * @return the value parsed from the string + * @throws IllegalArgumentException if the string length is greater than eight (8) or + * if any of the characters is not a hexadecimal character + */ + public static int fromHexDigits(CharSequence string) { + return fromHexDigits(string, 0, string.length()); + } + + /** + * Returns the {@code int} value parsed from a string range of up to eight hexadecimal + * characters. + * The characters in the range {@code fromIndex} to {@code toIndex}, exclusive, + * are parsed from most significant to least significant + * using {@link #fromHexDigit(int)} to form an unsigned value. + * The value is zero extended to 32 bits and is returned as an {@code int}. + * + * @apiNote + * {@link Integer#parseInt(String, int) Integer.parseInt(s, 16)} and + * {@link Integer#parseUnsignedInt(String, int) Integer.parseUnsignedInt(s, 16)} + * are similar but allow all Unicode hexadecimal digits defined by + * {@link Character#digit(char, int) Character.digit(ch, 16)}. + * {@code HexFormat} uses only hexadecimal characters + * {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. + * Signed hexadecimal strings can be parsed with {@link Integer#parseInt(String, int)}. + * + * @param string a CharSequence containing the characters + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return the value parsed from the string range + * @throws IndexOutOfBoundsException if the range is out of bounds + * for the {@code CharSequence} + * @throws IllegalArgumentException if length of the range is greater than eight (8) or + * if any of the characters is not a hexadecimal character + */ + public static int fromHexDigits(CharSequence string, int fromIndex, int toIndex) { + Objects.requireNonNull(string, "string"); + Objects.checkFromToIndex(fromIndex, toIndex, string.length()); + int length = checkDigitCount(fromIndex, toIndex, 8); + int value = 0; + for (int i = 0; i < length; i++) { + value = (value << 4) + fromHexDigit(string.charAt(fromIndex + i)); + } + return value; + } + + /** + * Returns the long value parsed from a string of up to sixteen hexadecimal characters. + * The hexadecimal characters are parsed from most significant to least significant + * using {@link #fromHexDigit(int)} to form an unsigned value. + * The value is zero extended to 64 bits and is returned as a {@code long}. + * + * @apiNote + * {@link Long#parseLong(String, int) Long.parseLong(s, 16)} and + * {@link Long#parseUnsignedLong(String, int) Long.parseUnsignedLong(s, 16)} + * are similar but allow all Unicode hexadecimal digits defined by + * {@link Character#digit(char, int) Character.digit(ch, 16)}. + * {@code HexFormat} uses only hexadecimal characters + * {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. + * Signed hexadecimal strings can be parsed with {@link Long#parseLong(String, int)}. + * + * @param string a CharSequence containing up to sixteen hexadecimal characters + * @return the value parsed from the string + * @throws IllegalArgumentException if the string length is greater than sixteen (16) or + * if any of the characters is not a hexadecimal character + */ + public static long fromHexDigitsToLong(CharSequence string) { + return fromHexDigitsToLong(string, 0, string.length()); + } + + /** + * Returns the long value parsed from a string range of up to sixteen hexadecimal + * characters. + * The characters in the range {@code fromIndex} to {@code toIndex}, exclusive, + * are parsed from most significant to least significant + * using {@link #fromHexDigit(int)} to form an unsigned value. + * The value is zero extended to 64 bits and is returned as a {@code long}. + * + * @apiNote + * {@link Long#parseLong(String, int) Long.parseLong(s, 16)} and + * {@link Long#parseUnsignedLong(String, int) Long.parseUnsignedLong(s, 16)} + * are similar but allow all Unicode hexadecimal digits defined by + * {@link Character#digit(char, int) Character.digit(ch, 16)}. + * {@code HexFormat} uses only hexadecimal characters + * {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. + * Signed hexadecimal strings can be parsed with {@link Long#parseLong(String, int)}. + * + * @param string a CharSequence containing the characters + * @param fromIndex the initial index of the range, inclusive + * @param toIndex the final index of the range, exclusive. + * @return the value parsed from the string range + * @throws IndexOutOfBoundsException if the range is out of bounds + * for the {@code CharSequence} + * @throws IllegalArgumentException if the length of the range is greater than sixteen (16) or + * if any of the characters is not a hexadecimal character + */ + public static long fromHexDigitsToLong(CharSequence string, int fromIndex, int toIndex) { + Objects.requireNonNull(string, "string"); + Objects.checkFromToIndex(fromIndex, toIndex, string.length()); + int length = checkDigitCount(fromIndex, toIndex, 16); + long value = 0L; + for (int i = 0; i < length; i++) { + value = (value << 4) + fromHexDigit(string.charAt(fromIndex + i)); + } + return value; + } + + /** + * Returns {@code true} if the other object is a {@code HexFormat} + * with the same parameters. + * + * @param o an object, may be null + * @return {@code true} if the other object is a {@code HexFormat} and the parameters + * uppercase, delimiter, prefix, and suffix are equal; + * otherwise {@code false} + */ + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + HexFormat otherHex = (HexFormat) o; + return Arrays.equals(digits, otherHex.digits) && + delimiter.equals(otherHex.delimiter) && + prefix.equals(otherHex.prefix) && + suffix.equals(otherHex.suffix); + } + + /** + * Returns a hashcode for this {@code HexFormat}. + * + * @return a hashcode for this {@code HexFormat} + */ + @Override + public int hashCode() { + int result = Objects.hash(delimiter, prefix, suffix); + result = 31 * result + Boolean.hashCode(Arrays.equals(digits, UPPERCASE_DIGITS)); + return result; + } + + /** + * Returns a description of the formatter parameters for uppercase, + * delimiter, prefix, and suffix. + * + * @return a description of this {@code HexFormat} + */ + @Override + public String toString() { + return escapeNL("uppercase: " + Arrays.equals(digits, UPPERCASE_DIGITS) + + ", delimiter: \"" + delimiter + + "\", prefix: \"" + prefix + + "\", suffix: \"" + suffix + "\""); + } +} diff --git a/parser/src/test/java/qaiu/web/test/TestAESUtil.java b/parser/src/test/java/qaiu/web/test/TestAESUtil.java index 67a4a48..971c61e 100644 --- a/parser/src/test/java/qaiu/web/test/TestAESUtil.java +++ b/parser/src/test/java/qaiu/web/test/TestAESUtil.java @@ -1,6 +1,7 @@ package qaiu.web.test; import cn.qaiu.util.AESUtils; +import cn.qaiu.util.jdk17halper.HexFormat; import org.junit.Assert; import org.junit.Test; @@ -9,7 +10,6 @@ import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; public class TestAESUtil { diff --git a/parser/src/test/java/qaiu/web/test/TestRegex.java b/parser/src/test/java/qaiu/web/test/TestRegex.java index f6f66bf..0118833 100644 --- a/parser/src/test/java/qaiu/web/test/TestRegex.java +++ b/parser/src/test/java/qaiu/web/test/TestRegex.java @@ -9,10 +9,10 @@ public class TestRegex { @Test public void regexYFC() { - String html = """ - - - """; + String html = "\n" + + " \n" + + " "; Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\""); Matcher matcher = compile.matcher(html); diff --git a/parser/src/test/java/qaiu/web/test/TestWebClient2.java b/parser/src/test/java/qaiu/web/test/TestWebClient2.java index f8fcb05..1f28625 100644 --- a/parser/src/test/java/qaiu/web/test/TestWebClient2.java +++ b/parser/src/test/java/qaiu/web/test/TestWebClient2.java @@ -15,9 +15,11 @@ public class TestWebClient2 { public void matcherHtml() { Pattern compile = Pattern.compile("class=\"ifr2\" name=.+src=\"(/fn\\?[a-zA-Z0-9_+/=]{16,})\""); - var text = """ -
-"""; + var text = "
\n" + + ""; System.out.println(text); Matcher matcher = compile.matcher(text); if (matcher.find()) { diff --git a/pom.xml b/pom.xml index 34484b4..d394921 100644 --- a/pom.xml +++ b/pom.xml @@ -18,9 +18,9 @@ 0.1.7 - 17 - 17 - 17 + 11 + 11 + 11 UTF-8 ${project.basedir}/web-service/target/package diff --git a/web-service/pom.xml b/web-service/pom.xml index d6cfffb..59203f1 100644 --- a/web-service/pom.xml +++ b/web-service/pom.xml @@ -12,7 +12,6 @@ ${project.basedir}/target/package - 17 UTF-8 netdisk-fast-download diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java index c2f4f2e..f33911d 100644 --- a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java +++ b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java @@ -47,12 +47,10 @@ public class DbServiceImpl implements DbService { public Future getStatisticsInfo() { JDBCPool client = JDBCPoolInit.instance().getPool(); Promise promise = Promise.promise(); - String sql = """ - select COUNT(CASE "code" WHEN 500 THEN "code" END ) "fail", - COUNT(CASE "code" WHEN 200 THEN "code" END ) "success", - count(1) "total" - from "t_parser_log_info" - """; + String sql = "select COUNT(CASE \"code\" WHEN 500 THEN \"code\" END ) \"fail\",\n" + + " COUNT(CASE \"code\" WHEN 200 THEN \"code\" END ) \"success\",\n" + + " count(1) \"total\"\n" + + " from \"t_parser_log_info\""; SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> { StatisticsInfo info; if ((info = row.iterator().next()) != null) { diff --git a/web-service/src/test/java/cn/qaiu/web/test/TestRegex.java b/web-service/src/test/java/cn/qaiu/web/test/TestRegex.java index 28beaae..749910d 100644 --- a/web-service/src/test/java/cn/qaiu/web/test/TestRegex.java +++ b/web-service/src/test/java/cn/qaiu/web/test/TestRegex.java @@ -9,10 +9,9 @@ public class TestRegex { @Test public void regexYFC() { - String html = """ - - - """; + String html = "\n" + + " "; Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\""); Matcher matcher = compile.matcher(html); diff --git a/web-service/src/test/java/cn/qaiu/web/test/WebProxyExamples.java b/web-service/src/test/java/cn/qaiu/web/test/WebProxyExamples.java index 562cdaf..08d2e6a 100644 --- a/web-service/src/test/java/cn/qaiu/web/test/WebProxyExamples.java +++ b/web-service/src/test/java/cn/qaiu/web/test/WebProxyExamples.java @@ -154,14 +154,14 @@ public class WebProxyExamples { public static void main(String[] args) { final WebProxyExamples examples = new WebProxyExamples(); - examples.vertx.executeBlocking(rs -> { - rs.complete(); - examples.origin(); - }); - examples.vertx.executeBlocking(rs -> { - rs.complete(); - examples.route(); - }); +// examples.vertx.executeBlocking(rs -> { +// rs.complete(); +// examples.origin(); +// }); +// examples.vertx.executeBlocking(rs -> { +// rs.complete(); +// examples.route(); +// }); System.out.println("ok"); }