From 6cd616a2517196524f4eaa2516d5c36733854894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=89=91=E5=99=A8=E8=BF=91?= Date: Tue, 3 Nov 2020 14:01:20 +0800 Subject: [PATCH] Initial commit. --- .gitignore | 42 +++ LICENSE | 201 +++++++++++++++ README.md | 239 ++++++++++++++++++ pom.xml | 193 ++++++++++++++ .../github/yezhihao/protostar/DataType.java | 36 +++ .../protostar/DefaultLoadStrategy.java | 59 +++++ .../yezhihao/protostar/FieldFactory.java | 108 ++++++++ .../github/yezhihao/protostar/IdStrategy.java | 124 +++++++++ .../yezhihao/protostar/LoadStrategy.java | 143 +++++++++++ .../protostar/PrepareLoadStrategy.java | 55 ++++ .../yezhihao/protostar/ProtostarUtil.java | 38 +++ .../io/github/yezhihao/protostar/Schema.java | 32 +++ .../protostar/annotation/Convert.java | 21 ++ .../yezhihao/protostar/annotation/Field.java | 33 +++ .../yezhihao/protostar/annotation/Fs.java | 18 ++ .../protostar/annotation/Message.java | 18 ++ .../protostar/converter/Converter.java | 10 + .../protostar/converter/MapConverter.java | 54 ++++ .../yezhihao/protostar/field/BasicField.java | 75 ++++++ .../protostar/field/DynamicLengthField.java | 94 +++++++ .../yezhihao/protostar/field/FixedField.java | 66 +++++ .../protostar/field/FixedLengthField.java | 66 +++++ .../protostar/schema/ByteArraySchema.java | 38 +++ .../protostar/schema/ByteBufferSchema.java | 42 +++ .../protostar/schema/CollectionSchema.java | 73 ++++++ .../protostar/schema/ConvertSchema.java | 64 +++++ .../protostar/schema/DateTimeSchema.java | 41 +++ .../yezhihao/protostar/schema/IntSchema.java | 58 +++++ .../yezhihao/protostar/schema/LongSchema.java | 41 +++ .../protostar/schema/ObjectSchema.java | 54 ++++ .../protostar/schema/RuntimeSchema.java | 76 ++++++ .../protostar/schema/StringSchema.java | 136 ++++++++++ .../github/yezhihao/protostar/util/Bcd.java | 118 +++++++++ .../yezhihao/protostar/util/ByteBufUtils.java | 110 ++++++++ .../yezhihao/protostar/util/ClassUtils.java | 113 +++++++++ .../io/github/yezhihao/protostar/Test.java | 8 + 36 files changed, 2697 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/io/github/yezhihao/protostar/DataType.java create mode 100644 src/main/java/io/github/yezhihao/protostar/DefaultLoadStrategy.java create mode 100644 src/main/java/io/github/yezhihao/protostar/FieldFactory.java create mode 100644 src/main/java/io/github/yezhihao/protostar/IdStrategy.java create mode 100644 src/main/java/io/github/yezhihao/protostar/LoadStrategy.java create mode 100644 src/main/java/io/github/yezhihao/protostar/PrepareLoadStrategy.java create mode 100644 src/main/java/io/github/yezhihao/protostar/ProtostarUtil.java create mode 100644 src/main/java/io/github/yezhihao/protostar/Schema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/annotation/Convert.java create mode 100644 src/main/java/io/github/yezhihao/protostar/annotation/Field.java create mode 100644 src/main/java/io/github/yezhihao/protostar/annotation/Fs.java create mode 100644 src/main/java/io/github/yezhihao/protostar/annotation/Message.java create mode 100644 src/main/java/io/github/yezhihao/protostar/converter/Converter.java create mode 100644 src/main/java/io/github/yezhihao/protostar/converter/MapConverter.java create mode 100644 src/main/java/io/github/yezhihao/protostar/field/BasicField.java create mode 100644 src/main/java/io/github/yezhihao/protostar/field/DynamicLengthField.java create mode 100644 src/main/java/io/github/yezhihao/protostar/field/FixedField.java create mode 100644 src/main/java/io/github/yezhihao/protostar/field/FixedLengthField.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/ByteArraySchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/ByteBufferSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/CollectionSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/ConvertSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/DateTimeSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/IntSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/LongSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/ObjectSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/RuntimeSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/schema/StringSchema.java create mode 100644 src/main/java/io/github/yezhihao/protostar/util/Bcd.java create mode 100644 src/main/java/io/github/yezhihao/protostar/util/ByteBufUtils.java create mode 100644 src/main/java/io/github/yezhihao/protostar/util/ClassUtils.java create mode 100644 src/test/java/io/github/yezhihao/protostar/Test.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e34678e --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Operating System Files + +*.DS_Store +Thumbs.db +*.sw? +.#* +*# +*~ +*.sublime-* + +# Build Artifacts + +.git* +.gradle/ +build/ +target/ +bin/ +out/ +dependency-reduced-pom.xml + +# Eclipse Project Files + +.classpath +.project +.settings/ + +# IntelliJ IDEA Files + +*.iml +*.ipr +*.iws +*.idea +*.log + +README.html + +# HTML resourse Files +#src/main/resources/static/index.html +#src/main/resources/static/view/ + +# temp ignore +src/main/resources/swagger.yaml \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6c60621 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 剑器近 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b25fa36 --- /dev/null +++ b/README.md @@ -0,0 +1,239 @@ +部标808协议快速开发包 +==================== +# 项目介绍 +* 基于Netty,实现JT/T 808部标协议的消息分发,与编码解码; +* 与Spring解耦合,协议编码解码和Netty服务均可独立运行(Android客户端同样适用); +* SpringBoot 仅负责将协议暴露至Web接口,目的是方便测试,且为二次开发提供样例; +* 最简洁、清爽、易用的部标开发框架。 + +问题交流群:[906230542] + +# 主要特性 +* 代码足够精简,便于二次开发; +* 致敬Spring、Hibernate设计理念,熟悉Web开发的同学上手极快; +* 使用注解描述协议,告别繁琐的封包、解包; +* 实时兼容2011、2013、2019部标协议版本,支持分包请求; +* 支持JT/T1078音视频协议,T/JSATL12苏标主动安防协议; +* 支持异步批量处理,显著提升MySQL入库性能; +* 提供报文解释器(解析过程分析工具),编码解码不再抓瞎; +* 全覆盖的测试用例,稳定发版。 + +# 代码仓库 + * Gitee仓库地址:[https://gitee.com/yezhihao/jt808-server/tree/master](https://gitee.com/yezhihao/jt808-server/tree/master) + * Github仓库地址:[https://github.com/yezhihao/jt808-server/tree/master](https://github.com/yezhihao/jt808-server/tree/master) + +# 下载方式 + * Gitee下载命令:`git clone https://gitee.com/yezhihao/jt808-server -b master` + * Github下载命令:`git clone https://github.com/yezhihao/jt808-server -b master` + +# 使用说明 + +## 项目分为四部分: + +## 1.framework,核心模块,不推荐修改,有BUG或扩展的需求,建议提交issues或联系作者 +```sh +└── framework + ├── codec 编码解码 + ├── mvc 消息分发、处理 + ├── netty 网络通信 + ├── orm 序列化相关 + └── session 消息发送和会话管理 + ``` +注解: + +* @Endpoint,服务接入点,等价SpringMVC的 @Controller; +* @Mapping,定义消息ID,等价SpringMVC中 @RequestMapping; +* @AsyncBatch, 异步批量消息,对于并发较高的消息,如0x0200(位置信息汇报),使用该注解,显著提升Netty和MySQL入库性能。 + + +* @Message,协议类型,等价Hibernate的 @Table; +* @Field,属性定义,等价Hibernate的 @Column; +* @Fs,多版本协议支持 + +## 2.protocol 部标协议定义,不推荐做大量修改 +```sh +└── protocol + ├── basics 部标协议通用消息头,以及公共的消息定义 + ├── codec 部标编码解码工具 + ├── commons 部标协议ID,工具类等 + ├── jsatl12 T/JSATL12 苏标协议(已完成) + ├── t808 JT/T808 部标协议(已完成) + └── t1078 JT/T1078 音视频协议(已完成) + ``` + 消息定义样例: + ```java +@Message(JT808.定位数据批量上传) +public class T0704 extends AbstractMessage
{ + + private Integer total; + private Integer type; + private List items; + + @Field(index = 0, type = DataType.WORD, desc = "数据项个数") + public Integer getTotal() { return total; } + public void setTotal(Integer total) { this.total = total; } + + @Field(index = 2, type = DataType.BYTE, desc = "位置数据类型 0:正常位置批量汇报,1:盲区补报") + public Integer getType() { return type; } + public void setType(Integer type) { this.type = type; } + + @Field(index = 3, type = DataType.LIST, desc = "位置汇报数据项") + public List getItems() { return items; } + public void setItems(List items) { this.items = items; this.total = items.size(); } +} +``` + +## 3.web 开箱即用的Demo,业务需求在这个包下开发,可随意修改 +```sh +└── web + ├── config spring 相关配置 + ├── component.mybatis 附赠极简的mybatis分页插件:D + ├── endpoint 808消息入口,所有netty进入的请求都会根据@Mapping转发到此 + └── controller service mapper ... 不再赘述 + ``` +##### 消息接入: +```java +@Endpoint +public class JT808Endpoint { + + @Autowired + private LocationService locationService; + + @Autowired + private DeviceService deviceService; + + //异步批量处理 队列大小20000 最大累积200处理一次 最大等待时间5秒 + @AsyncBatch(capacity = 20000, maxElements = 200, maxWait = 5000) + @Mapping(types = 位置信息汇报, desc = "位置信息汇报") + public void 位置信息汇报(List list) { + locationService.batchInsert(list); + } + + @Async + @Mapping(types = 终端注册, desc = "终端注册") + public T8100 register(T0100 message, Session session) { + Header header = message.getHeader(); + + T8100 result = new T8100(session.nextSerialNo(), header.getMobileNo()); + result.setSerialNo(header.getSerialNo()); + + String token = deviceService.register(message); + if (token != null) { + session.register(header); + + result.setResultCode(T8100.Success); + result.setToken(token); + } else { + + result.setResultCode(T8100.NotFoundTerminal); + } + return result; + } +} +``` + +##### 消息下发: +```java +@Controller +@RestController("terminal") +public class TerminalController { + + private MessageManager messageManager = MessageManager.getInstance(); + + @ApiOperation("设置终端参数") + @PostMapping("{terminalId}/parameters") + public T0001 updateParameters(@PathVariable("terminalId") String terminalId, @RequestBody List parameters) { + T8103 request = new T8103(terminalId); + request.setItems(parameters); + T0001 response = messageManager.request(request, T0001.class); + return response; + } +} +``` +##### 已集成Swagger文档,启动后可访问如下地址 + +* Swagger UI:[http://127.0.0.1:8000/swagger-ui.html](http://127.0.0.1:8000/swagger-ui.html) +* Bootstrap UI:[http://127.0.0.1:8000/doc.html](http://127.0.0.1:8000/doc.html) +![Bootstrap UI](https://images.gitee.com/uploads/images/2020/0731/135035_43dfca8e_670717.png "doc2.png") + +## 4.test 808协议全覆盖的测试用例,以及报文解释器 + +* QuickStart 不依赖Spring的启动,可用于Android客户端 +* Beans 测试数据 +* TestBeans 消息对象的封包解包 +* TestHex 原始报文测试 + +* Elucidator 报文解释器 - 解码 +* DarkRepulsor 报文解释器 - 编码 + +分析报文内每个属性所处的位置以及转换后的值,以便查询报文解析出错的原因 + +Elucidator 运行效果如下: +``` +0 [0200] 消息ID: 512 +2 [4061] 消息体属性: 16481 +4 [01] 协议版本号: 1 +5 [00000000017299841738] 终端手机号: 17299841738 +15 [ffff] 流水号: 65535 +0 [00000400] 报警标志: 1024 +4 [00000800] 状态: 2048 +8 [06eeb6ad] 纬度: 116307629 +12 [02633df7] 经度: 40058359 +16 [0138] 海拔: 312 +18 [0003] 速度: 3 +20 [0063] 方向: 99 +22 [200707192359] 时间: 2020-07-07T19:23:59 +0 [01] 附加信息ID: 1 +1 [04] 参数值长度: 4 +2 [0000000b] 参数值: {0,0,0,11} +0 [02] 附加信息ID: 2 +1 [02] 参数值长度: 2 +2 [0016] 参数值: {0,22} +0 [03] 附加信息ID: 3 +1 [02] 参数值长度: 2 +2 [0021] 参数值: {0,33} +0 [04] 附加信息ID: 4 +1 [02] 参数值长度: 2 +2 [002c] 参数值: {0,44} +0 [05] 附加信息ID: 5 +1 [03] 参数值长度: 3 +2 [373737] 参数值: {55,55,55} +0 [11] 附加信息ID: 17 +1 [05] 参数值长度: 5 +2 [4200000042] 参数值: {66,0,0,0,66} +0 [12] 附加信息ID: 18 +1 [06] 参数值长度: 6 +2 [4d0000004d4d] 参数值: {77,0,0,0,77,77} +0 [13] 附加信息ID: 19 +1 [07] 参数值长度: 7 +2 [00000058005858] 参数值: {0,0,0,88,0,88,88} +0 [25] 附加信息ID: 37 +1 [04] 参数值长度: 4 +2 [00000063] 参数值: {0,0,0,99} +0 [2a] 附加信息ID: 42 +1 [02] 参数值长度: 2 +2 [000a] 参数值: {0,10} +0 [2b] 附加信息ID: 43 +1 [04] 参数值长度: 4 +2 [00000014] 参数值: {0,0,0,20} +0 [30] 附加信息ID: 48 +1 [01] 参数值长度: 1 +2 [1e] 参数值: {30} +0 [31] 附加信息ID: 49 +1 [01] 参数值长度: 1 +2 [28] 参数值: {40} +28 [01040000000b02020016030200210402002c05033737371105420000004212064d0000004d4d1307000000580058582504000000632a02000a2b040000001430011e310128] 位置附加信息: [BytesAttribute[id=1,value={0,0,0,11}], BytesAttribute[id=2,value={0,22}], BytesAttribute[id=3,value={0,33}], BytesAttribute[id=4,value={0,44}], BytesAttribute[id=5,value={55,55,55}], BytesAttribute[id=17,value={66,0,0,0,66}], BytesAttribute[id=18,value={77,0,0,0,77,77}], BytesAttribute[id=19,value={0,0,0,88,0,88,88}], BytesAttribute[id=37,value={0,0,0,99}], BytesAttribute[id=42,value={0,10}], BytesAttribute[id=43,value={0,0,0,20}], BytesAttribute[id=48,value={30}], BytesAttribute[id=49,value={40}]] +020040610100000000017299841738ffff000004000000080006eeb6ad02633df701380003006320070719235901040000000b02020016030200210402002c05033737371105420000004212064d0000004d4d1307000000580058582504000000632a02000a2b040000001430011e31012863 +``` + +使用发包工具模拟请求 +``` +7e020040610100000000017299841738ffff000004000000080006eeb6ad02633df701380003006320070719235901040000000b02020016030200210402002c05033737371105420000004212064d0000004d4d1307000000580058582504000000632a02000a2b040000001430011e310128637e +``` +![使用发包工具模拟请求](https://images.gitee.com/uploads/images/2019/0705/162745_9becaf08_670717.png) + +项目会不定期进行更新,建议star和watch一份,您的支持是我最大的动力。 + +如有任何疑问或者BUG,请联系我,非常感谢。 + +技术交流QQ群:[906230542] diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a1c153c --- /dev/null +++ b/pom.xml @@ -0,0 +1,193 @@ + + 4.0.0 + io.github.yezhihao + protostar + 1.0.0.RELEASE + jar + + Protostar + https://github.com/yezhihao/protostar + Java serialization library,write in bytecode order. + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + https://github.com/yezhihao/protostar + https://github.com/yezhihao/protostar.git + + + + + protostar.yezhihao + protostar + zhihao.ye@qq.com + + + + + 1.8 + @ + UTF-8 + UTF-8 + ${java.version} + ${java.version} + true + + + + + junit + junit + 4.12 + test + + + com.google.code.gson + gson + 2.8.6 + test + + + + org.slf4j + slf4j-api + 1.7.30 + provided + + + io.netty + netty-buffer + 4.1.51.Final + + + org.apache.commons + commons-lang3 + 3.11 + + + + + + nexus-release + + true + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + maven-deploy-plugin + 2.8.2 + + + default-deploy + deploy + + deploy + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + central-nexus + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-scm-plugin + 1.11.2 + + + + + + nexus-release + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + + nexus-snapshot + https://oss.sonatype.org/content/repositories/snapshots + + + + + + + + central + Maven Central + http://maven.aliyun.com/nexus/content/groups/public/ + default + + true + + + + \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/DataType.java b/src/main/java/io/github/yezhihao/protostar/DataType.java new file mode 100644 index 0000000..7b0d667 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/DataType.java @@ -0,0 +1,36 @@ +package io.github.yezhihao.protostar; + +/** + * 支持的数据类型 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public enum DataType { + + //无符号单字节整型(字节, 8位) + BYTE(1), + //无符号双字节整型(字节,16位) + WORD(2), + //无符号四字节整型(字节,32位) + DWORD(4), + //无符号八字节整型(字节,64位) + QWORD(8), + //N字节,字节数组 + BYTES(-1), + //N字节,BCD8421码 + BCD8421(-1), + //字符串,若无数据置空 + STRING(-1), + //对象 + OBJ(-1), + //列表 + LIST(-1), + //字典 + MAP(-1); + + public int length; + + DataType(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/DefaultLoadStrategy.java b/src/main/java/io/github/yezhihao/protostar/DefaultLoadStrategy.java new file mode 100644 index 0000000..e77d018 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/DefaultLoadStrategy.java @@ -0,0 +1,59 @@ +package io.github.yezhihao.protostar; + +import io.github.yezhihao.protostar.annotation.Message; +import io.github.yezhihao.protostar.util.ClassUtils; + +import java.beans.Introspector; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class DefaultLoadStrategy extends LoadStrategy { + + private Map>> typeClassMapping = new HashMap(140); + + public DefaultLoadStrategy() { + } + + public DefaultLoadStrategy(String basePackage) { + List> types = ClassUtils.getClassList(basePackage); + for (Class type : types) { + Message message = type.getAnnotation(Message.class); + if (message != null) { + int[] values = message.value(); + for (int typeId : values) + loadSchema(typeClassMapping, typeId, type); + } + } + Introspector.flushCaches(); + } + + @Override + public Schema getSchema(Class typeClass, Integer version) { + Map> schemas = typeClassMapping.get(typeClass.getName()); + if (schemas == null) { + schemas = loadSchema(typeClassMapping, typeClass); + } + if (schemas == null) return null; + return (Schema) schemas.get(version); + } + + @Override + public Map> getSchema(Class typeClass) { + Map> schemas = typeClassMapping.get(typeClass.getName()); + if (schemas == null) { + schemas = loadSchema(typeClassMapping, typeClass); + } + if (schemas == null) return null; + + HashMap> result = new HashMap<>(schemas.size()); + for (Map.Entry> entry : schemas.entrySet()) { + result.put(entry.getKey(), (Schema) entry.getValue()); + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/FieldFactory.java b/src/main/java/io/github/yezhihao/protostar/FieldFactory.java new file mode 100644 index 0000000..6e466f6 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/FieldFactory.java @@ -0,0 +1,108 @@ +package io.github.yezhihao.protostar; + +import io.github.yezhihao.protostar.annotation.Convert; +import io.github.yezhihao.protostar.annotation.Field; +import io.github.yezhihao.protostar.field.BasicField; +import io.github.yezhihao.protostar.field.DynamicLengthField; +import io.github.yezhihao.protostar.field.FixedField; +import io.github.yezhihao.protostar.field.FixedLengthField; +import io.github.yezhihao.protostar.schema.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.PropertyDescriptor; +import java.nio.ByteBuffer; +import java.time.LocalDateTime; + +/** + * FieldFactory + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public abstract class FieldFactory { + protected static Logger log = LoggerFactory.getLogger(FieldFactory.class.getSimpleName()); + public static boolean EXPLAIN = false; + + public static BasicField create(Field field, PropertyDescriptor property) { + return create(field, property, null); + } + + public static BasicField create(Field field, PropertyDescriptor property, Schema schema) { + DataType dataType = field.type(); + Class typeClass = property.getPropertyType(); + + Schema fieldSchema; + switch (dataType) { + case BYTE: + fieldSchema = IntSchema.Int8.INSTANCE; + break; + case WORD: + fieldSchema = IntSchema.Int16.INSTANCE; + break; + case DWORD: + if (Integer.TYPE.isAssignableFrom(typeClass) || Integer.class.isAssignableFrom(typeClass)) + fieldSchema = IntSchema.Int32.INSTANCE; + else + fieldSchema = LongSchema.Long32.INSTANCE; + break; + case QWORD: + fieldSchema = LongSchema.Long64.INSTANCE; + break; + case BCD8421: + if (LocalDateTime.class.isAssignableFrom(typeClass)) + fieldSchema = DateTimeSchema.BCD.INSTANCE; + else + fieldSchema = StringSchema.BCD.INSTANCE; + break; + case BYTES: + if (String.class.isAssignableFrom(typeClass)) + fieldSchema = StringSchema.Chars.getInstance(field.pad(), field.charset()); + else if (ByteBuffer.class.isAssignableFrom(typeClass)) + fieldSchema = ByteBufferSchema.INSTANCE; + else + fieldSchema = ByteArraySchema.INSTANCE; + break; + case STRING: + fieldSchema = StringSchema.Chars.getInstance(field.pad(), field.charset()); + break; + case OBJ: + if (schema != null) { + fieldSchema = ObjectSchema.getInstance(schema); + } else { + Convert convert = property.getReadMethod().getAnnotation(Convert.class); + fieldSchema = ConvertSchema.getInstance(convert.converter()); + } + break; + case LIST: + fieldSchema = CollectionSchema.getInstance(schema); + break; + case MAP: + Convert convert = property.getReadMethod().getAnnotation(Convert.class); + fieldSchema = ConvertSchema.getInstance(convert.converter()); + break; + default: + throw new RuntimeException("不支持的类型转换"); + } + + + BasicField result; + if (EXPLAIN) { + if (field.lengthSize() > 0) { + result = new DynamicLengthField.Logger(field, property, fieldSchema); + } else if (field.length() > 0) { + result = new FixedLengthField.Logger(field, property, fieldSchema); + } else { + result = new FixedField.Logger(field, property, fieldSchema); + } + } else { + if (field.lengthSize() > 0) { + result = new DynamicLengthField(field, property, fieldSchema); + } else if (field.length() > 0) { + result = new FixedLengthField(field, property, fieldSchema); + } else { + result = new FixedField(field, property, fieldSchema); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/IdStrategy.java b/src/main/java/io/github/yezhihao/protostar/IdStrategy.java new file mode 100644 index 0000000..a82a41d --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/IdStrategy.java @@ -0,0 +1,124 @@ +package io.github.yezhihao.protostar; + +import io.github.yezhihao.protostar.annotation.Field; +import io.github.yezhihao.protostar.field.BasicField; +import io.github.yezhihao.protostar.schema.RuntimeSchema; +import io.netty.buffer.ByteBuf; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.*; + +/** + * Schema加载策略 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public abstract class IdStrategy { + + protected Map typeIdMapping = new HashMap<>(64); + + public Object readFrom(Object typeId, ByteBuf input) { + Schema schema = typeIdMapping.get(typeId); + return schema.readFrom(input); + } + + public void writeTo(Object typeId, ByteBuf output, Object element) { + Schema schema = typeIdMapping.get(typeId); + schema.writeTo(output, element); + } + + public Schema getSchema(Object typeId) { + Schema schema = typeIdMapping.get(typeId); + return schema; + } + + public abstract Schema getSchema(Class typeClass); + + protected Schema loadSchema(Map root, Object typeId, Class typeClass) { + Schema schema = typeIdMapping.get(typeId); + if (schema == null) { + schema = loadSchema(root, typeClass); + typeIdMapping.put(typeId, schema); + } + return schema; + } + + protected static Schema loadSchema(Map root, Class typeClass) { + Schema schema = root.get(typeClass.getName()); + //不支持循环引用 + if (schema != null) + return (Schema) schema; + + List properties = findFieldProperties(typeClass); + if (properties.isEmpty()) + return null; + + List fieldList = findFields(root, properties); + BasicField[] fields = fieldList.toArray(new BasicField[fieldList.size()]); + Arrays.sort(fields); + + schema = new RuntimeSchema(typeClass, 0, fields); + root.put(typeClass.getName(), schema); + return (Schema) schema; + } + + protected static List findFieldProperties(Class typeClass) { + BeanInfo beanInfo; + try { + beanInfo = Introspector.getBeanInfo(typeClass); + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors(); + List result = new ArrayList<>(properties.length); + + for (PropertyDescriptor property : properties) { + Method readMethod = property.getReadMethod(); + + if (readMethod != null) { + if (readMethod.isAnnotationPresent(Field.class)) { + result.add(property); + } + } + } + return result; + } + + protected static List findFields(Map root, List properties) { + List fields = new ArrayList<>(properties.size()); + + for (PropertyDescriptor property : properties) { + Method readMethod = property.getReadMethod(); + + Field field = readMethod.getDeclaredAnnotation(Field.class); + if (field != null) { + fillField(root, fields, property, field); + } + } + return fields; + } + + protected static void fillField(Map root, List fields, PropertyDescriptor propertyDescriptor, Field field) { + Class typeClass = propertyDescriptor.getPropertyType(); + Method readMethod = propertyDescriptor.getReadMethod(); + + BasicField value; + + if (field.type() == DataType.OBJ || field.type() == DataType.LIST) { + if (Collection.class.isAssignableFrom(typeClass)) + typeClass = (Class) ((ParameterizedType) readMethod.getGenericReturnType()).getActualTypeArguments()[0]; + loadSchema(root, typeClass); + Schema schema = root.get(typeClass.getName()); + value = FieldFactory.create(field, propertyDescriptor, schema); + fields.add(value); + } else { + value = FieldFactory.create(field, propertyDescriptor); + fields.add(value); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/LoadStrategy.java b/src/main/java/io/github/yezhihao/protostar/LoadStrategy.java new file mode 100644 index 0000000..c116212 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/LoadStrategy.java @@ -0,0 +1,143 @@ +package io.github.yezhihao.protostar; + +import io.github.yezhihao.protostar.annotation.Field; +import io.github.yezhihao.protostar.annotation.Fs; +import io.github.yezhihao.protostar.field.BasicField; +import io.github.yezhihao.protostar.schema.RuntimeSchema; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.*; + +/** + * Schema加载策略 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public abstract class LoadStrategy { + + protected Map>> typeIdMapping = new HashMap<>(64); + + public abstract Map> getSchema(Class typeClass); + + public abstract Schema getSchema(Class typeClass, Integer version); + + public Schema getSchema(Object typeId, Integer version) { + Map> schemaMap = typeIdMapping.get(typeId); + if (schemaMap == null) + return null; + return schemaMap.get(version); + } + + protected void loadSchema(Map>> root, Object typeId, Class typeClass) { + Map> schemas = typeIdMapping.get(typeId); + if (schemas == null) { + schemas = loadSchema(root, typeClass); + typeIdMapping.put(typeId, schemas); + } + } + + protected Map> loadSchema(Map>> root, Class typeClass) { + Map> schemas = root.get(typeClass.getName()); + //不支持循环引用 + if (schemas != null) + return schemas; + + List properties = findFieldProperties(typeClass); + if (properties.isEmpty()) + return null; + + root.put(typeClass.getName(), schemas = new HashMap(4)); + + Map> multiVersionFields = findMultiVersionFields(root, properties); + for (Map.Entry> entry : multiVersionFields.entrySet()) { + + Integer version = entry.getKey(); + List fieldList = entry.getValue(); + + BasicField[] fields = fieldList.toArray(new BasicField[fieldList.size()]); + Arrays.sort(fields); + + Schema schema = new RuntimeSchema(typeClass, version, fields); + schemas.put(version, schema); + } + return schemas; + } + + protected List findFieldProperties(Class typeClass) { + BeanInfo beanInfo; + try { + beanInfo = Introspector.getBeanInfo(typeClass); + } catch (IntrospectionException e) { + throw new RuntimeException(e); + } + PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors(); + List result = new ArrayList<>(properties.length); + + for (PropertyDescriptor property : properties) { + Method readMethod = property.getReadMethod(); + + if (readMethod != null) { + if (readMethod.isAnnotationPresent(Fs.class) || readMethod.isAnnotationPresent(Field.class)) { + result.add(property); + } + } + } + return result; + } + + protected Map> findMultiVersionFields(Map>> root, List properties) { + Map> multiVersionFields = new TreeMap>() { + @Override + public List get(Object key) { + List result = super.get(key); + if (result == null) + super.put((Integer) key, result = new ArrayList<>(properties.size())); + return result; + } + }; + + for (PropertyDescriptor property : properties) { + Method readMethod = property.getReadMethod(); + + Field field = readMethod.getDeclaredAnnotation(Field.class); + if (field != null) { + fillField(root, multiVersionFields, property, field); + } else { + Field[] fields = readMethod.getDeclaredAnnotation(Fs.class).value(); + for (int i = 0; i < fields.length; i++) + fillField(root, multiVersionFields, property, fields[i]); + } + } + return multiVersionFields; + } + + protected void fillField(Map>> root, Map> multiVersionFields, PropertyDescriptor propertyDescriptor, Field field) { + Class typeClass = propertyDescriptor.getPropertyType(); + Method readMethod = propertyDescriptor.getReadMethod(); + + BasicField value; + int[] versions = field.version(); + + if (field.type() == DataType.OBJ || field.type() == DataType.LIST) { + if (Collection.class.isAssignableFrom(typeClass)) + typeClass = (Class) ((ParameterizedType) readMethod.getGenericReturnType()).getActualTypeArguments()[0]; + loadSchema(root, typeClass); + for (int ver : versions) { + Map> schemaMap = root.getOrDefault(typeClass.getName(), Collections.EMPTY_MAP); + Schema schema = schemaMap.get(ver); + value = FieldFactory.create(field, propertyDescriptor, schema); + multiVersionFields.get(ver).add(value); + } + } else { + value = FieldFactory.create(field, propertyDescriptor); + for (int ver : versions) { + multiVersionFields.get(ver).add(value); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/PrepareLoadStrategy.java b/src/main/java/io/github/yezhihao/protostar/PrepareLoadStrategy.java new file mode 100644 index 0000000..dbb659d --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/PrepareLoadStrategy.java @@ -0,0 +1,55 @@ +package io.github.yezhihao.protostar; + +import io.github.yezhihao.protostar.schema.ByteArraySchema; +import io.github.yezhihao.protostar.schema.IntSchema; + +import java.util.HashMap; +import java.util.Map; + +public abstract class PrepareLoadStrategy extends IdStrategy { + + private final Map typeClassMapping = new HashMap<>(); + + protected PrepareLoadStrategy() { + this.addSchemas(this); + } + + protected abstract void addSchemas(PrepareLoadStrategy schemaRegistry); + + @Override + public Schema getSchema(Class typeClass) { + return typeClassMapping.get(typeClass); + } + + public PrepareLoadStrategy addSchema(Object key, Schema schema) { + if (schema == null) + throw new RuntimeException("key[" + key + "],schema is null"); + typeIdMapping.put(key, schema); + return this; + } + + public PrepareLoadStrategy addSchema(Object key, Class typeClass) { + loadSchema(typeClassMapping, key, typeClass); + return this; + } + + public PrepareLoadStrategy addSchema(Object key, DataType dataType) { + switch (dataType) { + case BYTE: + this.typeIdMapping.put(key, IntSchema.Int8.INSTANCE); + break; + case WORD: + this.typeIdMapping.put(key, IntSchema.Int16.INSTANCE); + break; + case DWORD: + this.typeIdMapping.put(key, IntSchema.Int32.INSTANCE); + break; + case BYTES: + this.typeIdMapping.put(key, ByteArraySchema.INSTANCE); + break; + default: + throw new RuntimeException("不支持的类型转换"); + } + return this; + } +} diff --git a/src/main/java/io/github/yezhihao/protostar/ProtostarUtil.java b/src/main/java/io/github/yezhihao/protostar/ProtostarUtil.java new file mode 100644 index 0000000..9000d4e --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/ProtostarUtil.java @@ -0,0 +1,38 @@ +package io.github.yezhihao.protostar; + +import java.util.Map; + +/** + * 消息ID关系映射 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class ProtostarUtil { + + private static volatile boolean Initial = false; + + private static LoadStrategy LOAD_STRATEGY = new DefaultLoadStrategy(); + + public static void initial(String basePackage) { + if (!Initial) { + synchronized (ProtostarUtil.class) { + if (!Initial) { + Initial = true; + LOAD_STRATEGY = new DefaultLoadStrategy(basePackage); + } + } + } + } + + public static Schema getSchema(Object typeId, Integer version) { + return LOAD_STRATEGY.getSchema(typeId, version); + } + + public static Schema getSchema(Class typeClass, Integer version) { + return LOAD_STRATEGY.getSchema(typeClass, version); + } + + public static Map> getSchema(Class typeClass) { + return LOAD_STRATEGY.getSchema(typeClass); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/Schema.java b/src/main/java/io/github/yezhihao/protostar/Schema.java new file mode 100644 index 0000000..b2b3c98 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/Schema.java @@ -0,0 +1,32 @@ +package io.github.yezhihao.protostar; + +import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 消息结构 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public interface Schema { + + Logger log = LoggerFactory.getLogger(Schema.class.getSimpleName()); + + T readFrom(ByteBuf input); + + void writeTo(ByteBuf output, T message); + + default T readFrom(ByteBuf input, int length) { + return readFrom(input); + } + + default void writeTo(ByteBuf output, int length, T message) { + writeTo(output, message); + } + + /** 用于预估内存分配,不需要精确值 */ + default int length() { + return 128; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/annotation/Convert.java b/src/main/java/io/github/yezhihao/protostar/annotation/Convert.java new file mode 100644 index 0000000..bb6ea32 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/annotation/Convert.java @@ -0,0 +1,21 @@ +package io.github.yezhihao.protostar.annotation; + +import io.github.yezhihao.protostar.converter.Converter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 启用自定义消息转换 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Convert { + + Class converter(); + +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/annotation/Field.java b/src/main/java/io/github/yezhihao/protostar/annotation/Field.java new file mode 100644 index 0000000..c2cb4ad --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/annotation/Field.java @@ -0,0 +1,33 @@ +package io.github.yezhihao.protostar.annotation; + +import io.github.yezhihao.protostar.DataType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Field { + + int index() default -1; + + int length() default -1; + + int lengthSize() default -1; + + DataType type() default DataType.BYTE; + + String charset() default "GBK"; + + byte pad() default 0x00; + + String desc() default ""; + + int[] version() default {-1, 0, 1}; +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/annotation/Fs.java b/src/main/java/io/github/yezhihao/protostar/annotation/Fs.java new file mode 100644 index 0000000..6444b47 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/annotation/Fs.java @@ -0,0 +1,18 @@ +package io.github.yezhihao.protostar.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Fs { + + Field[] value(); + +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/annotation/Message.java b/src/main/java/io/github/yezhihao/protostar/annotation/Message.java new file mode 100644 index 0000000..595066f --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/annotation/Message.java @@ -0,0 +1,18 @@ +package io.github.yezhihao.protostar.annotation; + +import java.lang.annotation.*; + +/** + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Message { + + int[] value() default {}; + + String desc() default ""; + +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/converter/Converter.java b/src/main/java/io/github/yezhihao/protostar/converter/Converter.java new file mode 100644 index 0000000..640b312 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/converter/Converter.java @@ -0,0 +1,10 @@ +package io.github.yezhihao.protostar.converter; + +import io.netty.buffer.ByteBuf; + +public interface Converter { + + T convert(ByteBuf input); + + void convert(ByteBuf output, T value); +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/converter/MapConverter.java b/src/main/java/io/github/yezhihao/protostar/converter/MapConverter.java new file mode 100644 index 0000000..6ea9aab --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/converter/MapConverter.java @@ -0,0 +1,54 @@ +package io.github.yezhihao.protostar.converter; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.util.ByteBufUtils; + +import java.util.Map; +import java.util.TreeMap; + +public abstract class MapConverter implements Converter> { + + protected abstract K readKey(ByteBuf input); + + protected abstract void writeKey(ByteBuf output, K key); + + protected abstract int valueSize(); + + protected abstract V convert(K key, ByteBuf input); + + protected abstract void convert(K key, ByteBuf output, V value); + + @Override + public Map convert(ByteBuf input) { + if (!input.isReadable()) + return null; + Map map = new TreeMap<>(); + do { + K id = readKey(input); + int len = ByteBufUtils.readInt(input, valueSize()); + Object value = convert(id, input.readSlice(len)); + if (value == null) break; + map.put(id, (V) value); + } while (input.isReadable()); + return map; + } + + @Override + public void convert(ByteBuf output, Map map) { + + if (map == null || map.isEmpty()) + return; + for (Map.Entry entry : map.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + + writeKey(output, key); + int valueSize = valueSize(); + int begin = output.writerIndex(); + output.writeBytes(ByteBufUtils.BLOCKS[valueSize]); + convert(key, output, value); + int len = output.writerIndex() - begin - valueSize; + ByteBufUtils.setInt(output, valueSize, begin, len); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/field/BasicField.java b/src/main/java/io/github/yezhihao/protostar/field/BasicField.java new file mode 100644 index 0000000..03fc6ee --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/field/BasicField.java @@ -0,0 +1,75 @@ +package io.github.yezhihao.protostar.field; + +import io.github.yezhihao.protostar.annotation.Field; +import io.netty.buffer.ByteBuf; +import org.apache.commons.lang3.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +/** + * 固定长度的字段 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public abstract class BasicField implements Comparable> { + protected static Logger log = LoggerFactory.getLogger(BasicField.class.getSimpleName()); + + protected final int index; + protected final int length; + protected final String desc; + protected final Method readMethod; + protected final Method writeMethod; + protected final PropertyDescriptor property; + protected final Field field; + + public BasicField(Field field, PropertyDescriptor property) { + this.index = field.index(); + int length = field.length(); + if (length < 0) + length = field.type().length; + this.length = length; + this.desc = field.desc(); + this.readMethod = property.getReadMethod(); + this.writeMethod = property.getWriteMethod(); + this.field = field; + this.property = property; + } + + public abstract boolean readFrom(ByteBuf input, Object message) throws Exception; + + public abstract void writeTo(ByteBuf output, Object message) throws Exception; + + public void println(int index, String desc, String hex, Object value) { + if (value != null) + System.out.println(index + "\t" + "[" + hex + "] " + desc + ": " + (value.getClass().isArray() ? ArrayUtils.toString(value) : value)); + } + + public int index() { + return index; + } + + public int length() { + return length; + } + + @Override + public int compareTo(BasicField that) { + return Integer.compare(this.index, that.index); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(60); + sb.append('{'); + sb.append("index=").append(index); + sb.append(", length=").append(length); + sb.append(", desc").append(desc); + sb.append(", readMethod=").append(readMethod.getName()); + sb.append(", writeMethod=").append(writeMethod.getName()); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/field/DynamicLengthField.java b/src/main/java/io/github/yezhihao/protostar/field/DynamicLengthField.java new file mode 100644 index 0000000..db7ca8a --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/field/DynamicLengthField.java @@ -0,0 +1,94 @@ +package io.github.yezhihao.protostar.field; + +import io.github.yezhihao.protostar.annotation.Field; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.github.yezhihao.protostar.Schema; +import io.github.yezhihao.protostar.util.ByteBufUtils; + +import java.beans.PropertyDescriptor; + +/** + * 动态长度的字段 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class DynamicLengthField extends BasicField { + + protected final Schema schema; + + protected final int lengthSize; + + public DynamicLengthField(Field field, PropertyDescriptor property, Schema schema) { + super(field, property); + this.schema = schema; + this.lengthSize = field.lengthSize(); + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + int length = ByteBufUtils.readInt(input, lengthSize); + if (!input.isReadable(length)) + return false; + Object value = schema.readFrom(input, length); + writeMethod.invoke(message, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + Object value = readMethod.invoke(message); + if (value != null) { + int begin = output.writerIndex(); + output.writeBytes(ByteBufUtils.BLOCKS[lengthSize]); + schema.writeTo(output, (T) value); + int length = output.writerIndex() - begin - lengthSize; + ByteBufUtils.setInt(output, lengthSize, begin, length); + } + } + + @Override + public int compareTo(BasicField that) { + int r = Integer.compare(this.index, that.index); + if (r == 0) + r = (that instanceof DynamicLengthField) ? 1 : -1; + return r; + } + + public static class Logger extends DynamicLengthField { + + public Logger(Field field, PropertyDescriptor property, Schema schema) { + super(field, property, schema); + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + int before = input.readerIndex(); + + int length = ByteBufUtils.readInt(input, lengthSize); + if (!input.isReadable(length)) + return false; + Object value = schema.readFrom(input, length); + writeMethod.invoke(message, value); + + int after = input.readerIndex(); + String hex = ByteBufUtil.hexDump(input.slice(before, after - before)); + println(this.index, this.desc, hex, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + int before = output.writerIndex(); + + Object value = readMethod.invoke(message); + if (value != null) { + int begin = output.writerIndex(); + output.writeBytes(ByteBufUtils.BLOCKS[lengthSize]); + schema.writeTo(output, (T) value); + int length = output.writerIndex() - begin - lengthSize; + ByteBufUtils.setInt(output, lengthSize, begin, length); + } + + int after = output.writerIndex(); + String hex = ByteBufUtil.hexDump(output.slice(before, after - before)); + println(this.index, this.desc, hex, value); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/field/FixedField.java b/src/main/java/io/github/yezhihao/protostar/field/FixedField.java new file mode 100644 index 0000000..7921c79 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/field/FixedField.java @@ -0,0 +1,66 @@ +package io.github.yezhihao.protostar.field; + +import io.github.yezhihao.protostar.annotation.Field; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.github.yezhihao.protostar.Schema; + +import java.beans.PropertyDescriptor; + +/** + * 原子域 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class FixedField extends BasicField { + + protected final Schema schema; + + public FixedField(Field field, PropertyDescriptor property, Schema schema) { + super(field, property); + this.schema = schema; + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + Object value = schema.readFrom(input); + writeMethod.invoke(message, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + Object value = readMethod.invoke(message); + if (value != null) + schema.writeTo(output, (T) value); + } + + public static class Logger extends FixedField { + + public Logger(Field field, PropertyDescriptor property, Schema schema) { + super(field, property, schema); + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + int before = input.readerIndex(); + + Object value = schema.readFrom(input); + writeMethod.invoke(message, value); + + int after = input.readerIndex(); + String hex = ByteBufUtil.hexDump(input.slice(before, after - before)); + println(this.index, this.desc, hex, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + int before = output.writerIndex(); + + Object value = readMethod.invoke(message); + if (value != null) + schema.writeTo(output, (T) value); + + int after = output.writerIndex(); + String hex = ByteBufUtil.hexDump(output.slice(before, after - before)); + println(this.index, this.desc, hex, value); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/field/FixedLengthField.java b/src/main/java/io/github/yezhihao/protostar/field/FixedLengthField.java new file mode 100644 index 0000000..569849e --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/field/FixedLengthField.java @@ -0,0 +1,66 @@ +package io.github.yezhihao.protostar.field; + +import io.github.yezhihao.protostar.annotation.Field; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.github.yezhihao.protostar.Schema; + +import java.beans.PropertyDescriptor; + +/** + * 固定长度的字段 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class FixedLengthField extends BasicField { + + protected final Schema schema; + + public FixedLengthField(Field field, PropertyDescriptor property, Schema schema) { + super(field, property); + this.schema = schema; + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + Object value = schema.readFrom(input, length); + writeMethod.invoke(message, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + Object value = readMethod.invoke(message); + if (value != null) + schema.writeTo(output, length, (T) value); + } + + public static class Logger extends FixedLengthField { + + public Logger(Field field, PropertyDescriptor property, Schema schema) { + super(field, property, schema); + } + + public boolean readFrom(ByteBuf input, Object message) throws Exception { + int before = input.readerIndex(); + + Object value = schema.readFrom(input, length); + writeMethod.invoke(message, value); + + int after = input.readerIndex(); + String hex = ByteBufUtil.hexDump(input.slice(before, after - before)); + println(this.index, this.desc, hex, value); + return true; + } + + public void writeTo(ByteBuf output, Object message) throws Exception { + int before = output.writerIndex(); + + Object value = readMethod.invoke(message); + if (value != null) + schema.writeTo(output, length, (T) value); + + int after = output.writerIndex(); + String hex = ByteBufUtil.hexDump(output.slice(before, after - before)); + println(this.index, this.desc, hex, value); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/ByteArraySchema.java b/src/main/java/io/github/yezhihao/protostar/schema/ByteArraySchema.java new file mode 100644 index 0000000..960f1c8 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/ByteArraySchema.java @@ -0,0 +1,38 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +public class ByteArraySchema implements Schema { + + public static final Schema INSTANCE = new ByteArraySchema(); + + private ByteArraySchema() { + } + + @Override + public byte[] readFrom(ByteBuf input) { + byte[] message = new byte[input.readableBytes()]; + input.readBytes(message); + return message; + } + + @Override + public byte[] readFrom(ByteBuf input, int length) { + if (length < 0) + length = input.readableBytes(); + byte[] bytes = new byte[length]; + input.readBytes(bytes); + return bytes; + } + + @Override + public void writeTo(ByteBuf output, byte[] value) { + output.writeBytes(value); + } + + @Override + public void writeTo(ByteBuf output, int length, byte[] message) { + output.writeBytes(message, 0, length); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/ByteBufferSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/ByteBufferSchema.java new file mode 100644 index 0000000..d5d0270 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/ByteBufferSchema.java @@ -0,0 +1,42 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +import java.nio.ByteBuffer; + +public class ByteBufferSchema implements Schema { + + public static final Schema INSTANCE = new ByteBufferSchema(); + + private ByteBufferSchema() { + } + + @Override + public ByteBuffer readFrom(ByteBuf input) { + ByteBuffer message = input.nioBuffer(); + input.skipBytes(input.readableBytes()); + return message; + } + + @Override + public ByteBuffer readFrom(ByteBuf input, int length) { + if (length < 0) + length = input.readableBytes(); + ByteBuffer byteBuffer = input.nioBuffer(input.readerIndex(), length); + input.skipBytes(length); + return byteBuffer; + } + + @Override + public void writeTo(ByteBuf output, ByteBuffer value) { + output.writeBytes(value); + } + + @Override + public void writeTo(ByteBuf output, int length, ByteBuffer value) { + if (length > 0) + value.position(value.limit() - length); + output.writeBytes(value); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/CollectionSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/CollectionSchema.java new file mode 100644 index 0000000..748ba74 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/CollectionSchema.java @@ -0,0 +1,73 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CollectionSchema implements Schema> { + + private static volatile Map cache = new HashMap<>(); + + public static Schema getInstance(Schema schema) { + CollectionSchema instance = cache.get(schema); + if (instance == null) { + synchronized (cache) { + if (instance == null) { + instance = new CollectionSchema(schema); + cache.put(schema, instance); + log.debug("new CollectionSchema({})", schema); + } + } + } + return instance; + } + + private final Schema schema; + + private CollectionSchema(Schema schema) { + this.schema = schema; + } + + @Override + public List readFrom(ByteBuf input) { + if (!input.isReadable()) + return null; + List list = new ArrayList<>(); + do { + T obj = schema.readFrom(input); + if (obj == null) break; + list.add(obj); + } while (input.isReadable()); + return list; + } + + + @Override + public List readFrom(ByteBuf input, int length) { + return this.readFrom(input.readSlice(length)); + } + + @Override + public void writeTo(ByteBuf output, List list) { + if (list == null || list.isEmpty()) + return; + + for (T obj : list) { + schema.writeTo(output, obj); + } + } + + @Override + public void writeTo(ByteBuf output, int length, List list) { + if (list == null || list.isEmpty()) + return; + + for (T obj : list) { + schema.writeTo(output, length, obj); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/ConvertSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/ConvertSchema.java new file mode 100644 index 0000000..24dccfb --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/ConvertSchema.java @@ -0,0 +1,64 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.converter.Converter; +import io.github.yezhihao.protostar.Schema; + +import java.util.HashMap; +import java.util.Map; + +/** + * 自定义结构转换 + */ +public class ConvertSchema implements Schema { + + private static volatile Map cache = new HashMap<>(); + + public static Schema getInstance(Class clazz) { + String name = clazz.getName(); + ConvertSchema instance = cache.get(name); + if (instance == null) { + synchronized (cache) { + if (instance == null) { + try { + Converter converter = clazz.newInstance(); + instance = new ConvertSchema(converter); + cache.put(name, instance); + log.debug("new ConvertSchema({})", clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + return instance; + } + + private final Converter converter; + + private ConvertSchema(Converter converter) { + this.converter = converter; + } + + @Override + public T readFrom(ByteBuf input) { + return converter.convert(input); + } + + @Override + public T readFrom(ByteBuf input, int length) { + if (length > 0) + input = input.readSlice(length); + return converter.convert(input); + } + + @Override + public void writeTo(ByteBuf output, T message) { + converter.convert(output, message); + } + + @Override + public void writeTo(ByteBuf output, int length, T obj) { + converter.convert(output, obj); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/DateTimeSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/DateTimeSchema.java new file mode 100644 index 0000000..8965e92 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/DateTimeSchema.java @@ -0,0 +1,41 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; +import io.github.yezhihao.protostar.util.Bcd; + +import java.time.LocalDateTime; + +public class DateTimeSchema { + + public static class BCD implements Schema { + public static final Schema INSTANCE = new BCD(); + + private BCD() { + } + + @Override + public LocalDateTime readFrom(ByteBuf input) { + byte[] bytes = new byte[6]; + input.readBytes(bytes); + return Bcd.toDateTime(bytes); + } + + @Override + public LocalDateTime readFrom(ByteBuf input, int length) { + byte[] bytes = new byte[length]; + input.readBytes(bytes); + return Bcd.toDateTime(bytes); + } + + @Override + public void writeTo(ByteBuf output, LocalDateTime value) { + output.writeBytes(Bcd.from(value)); + } + + @Override + public void writeTo(ByteBuf output, int length, LocalDateTime value) { + output.writeBytes(Bcd.from(value)); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/IntSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/IntSchema.java new file mode 100644 index 0000000..9b6c6e1 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/IntSchema.java @@ -0,0 +1,58 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +public class IntSchema { + + public static class Int8 implements Schema { + public static final Schema INSTANCE = new Int8(); + + private Int8() { + } + + @Override + public Integer readFrom(ByteBuf input) { + return (int) input.readUnsignedByte(); + } + + @Override + public void writeTo(ByteBuf output, Integer value) { + output.writeByte(value); + } + } + + public static class Int16 implements Schema { + public static final Schema INSTANCE = new Int16(); + + private Int16() { + } + + @Override + public Integer readFrom(ByteBuf input) { + return input.readUnsignedShort(); + } + + @Override + public void writeTo(ByteBuf output, Integer value) { + output.writeShort(value); + } + } + + public static class Int32 implements Schema { + public static final Schema INSTANCE = new Int32(); + + private Int32() { + } + + @Override + public Integer readFrom(ByteBuf input) { + return (int) input.readUnsignedInt(); + } + + @Override + public void writeTo(ByteBuf output, Integer value) { + output.writeInt(value); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/LongSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/LongSchema.java new file mode 100644 index 0000000..d04b9ee --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/LongSchema.java @@ -0,0 +1,41 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +public class LongSchema { + + public static class Long32 implements Schema { + public static final Schema INSTANCE = new Long32(); + + private Long32() { + } + + @Override + public Long readFrom(ByteBuf input) { + return input.readUnsignedInt(); + } + + @Override + public void writeTo(ByteBuf output, Long value) { + output.writeInt(value.intValue()); + } + } + + public static class Long64 implements Schema { + public static final Schema INSTANCE = new Long64(); + + private Long64() { + } + + @Override + public Long readFrom(ByteBuf input) { + return input.readLong(); + } + + @Override + public void writeTo(ByteBuf output, Long value) { + output.writeLong(value.intValue()); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/ObjectSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/ObjectSchema.java new file mode 100644 index 0000000..914b5ea --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/ObjectSchema.java @@ -0,0 +1,54 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +import java.util.HashMap; +import java.util.Map; + +public class ObjectSchema implements Schema { + + private static volatile Map cache = new HashMap<>(); + + public static Schema getInstance(Schema schema) { + ObjectSchema instance = cache.get(schema); + if (instance == null) { + synchronized (cache) { + if (instance == null) { + instance = new ObjectSchema(schema); + cache.put(schema, instance); + log.debug("new ObjectSchema({})", schema); + } + } + } + return instance; + } + + private final Schema schema; + + private ObjectSchema(Schema schema) { + this.schema = schema; + } + + @Override + public T readFrom(ByteBuf input) { + return schema.readFrom(input); + } + + @Override + public T readFrom(ByteBuf input, int length) { + if (length > 0) + input = input.readSlice(length); + return schema.readFrom(input); + } + + @Override + public void writeTo(ByteBuf output, T message) { + schema.writeTo(output, message); + } + + @Override + public void writeTo(ByteBuf output, int length, T obj) { + schema.writeTo(output, obj); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/RuntimeSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/RuntimeSchema.java new file mode 100644 index 0000000..32ca22b --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/RuntimeSchema.java @@ -0,0 +1,76 @@ +package io.github.yezhihao.protostar.schema; + +import io.github.yezhihao.protostar.field.BasicField; +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; + +/** + * 运行时根据Class生成的消息结构,用于序列化对象 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class RuntimeSchema implements Schema { + + protected final int version; + protected final int length; + protected final Class typeClass; + protected final BasicField[] fields; + + public RuntimeSchema(Class typeClass, int version, BasicField[] fields) { + this.typeClass = typeClass; + this.version = version; + this.fields = fields; + BasicField lastField = fields[fields.length - 1]; + int lastIndex = lastField.index(); + int lastLength = lastField.length() < 0 ? 256 : lastField.length(); + this.length = lastIndex + lastLength; + } + + public T readFrom(ByteBuf input) { + T message = null; + boolean isEmpty = true;//防止死循环 + BasicField field = null; + try { + message = typeClass.newInstance(); + for (int i = 0; i < fields.length; i++) { + field = fields[i]; + if (!input.isReadable()) + break; + field.readFrom(input, message); + isEmpty = false; + } + } catch (Exception e) { + throw new RuntimeException("Serialization failed readFrom " + typeClass.getName() + field, e); + } + if (isEmpty) + return null; + return message; + } + + public void writeTo(ByteBuf output, T message) { + BasicField field = null; + try { + for (int i = 0; i < fields.length; i++) { + field = fields[i]; + field.writeTo(output, message); + } + } catch (Exception e) { + throw new RuntimeException("Serialization failed writeTo " + typeClass.getName() + field, e); + } + } + + public int length() { + return length; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(48); + sb.append('{'); + sb.append("typeClass=").append(typeClass.getSimpleName()); + sb.append(", version=").append(version); + sb.append(", length=").append(length); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/schema/StringSchema.java b/src/main/java/io/github/yezhihao/protostar/schema/StringSchema.java new file mode 100644 index 0000000..09f1013 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/schema/StringSchema.java @@ -0,0 +1,136 @@ +package io.github.yezhihao.protostar.schema; + +import io.netty.buffer.ByteBuf; +import io.github.yezhihao.protostar.Schema; +import io.github.yezhihao.protostar.util.Bcd; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class StringSchema { + + public static class Chars implements Schema { + private static volatile Map cache = new HashMap<>(); + + public static Schema getInstance(byte pad, String charset) { + charset = charset.toLowerCase(); + String key = new StringBuilder(10).append((char) pad).append('/').append(charset).toString(); + Chars instance = cache.get(key); + if (instance == null) { + synchronized (cache) { + if (instance == null) { + instance = new Chars(pad, charset); + cache.put(key, instance); + log.debug("new StringSchema({},{})", pad, charset); + } + } + } + return instance; + } + + private final byte pad; + private final Charset charset; + + private Chars(byte pad, String charset) { + this.pad = pad; + this.charset = Charset.forName(charset); + } + + @Override + public String readFrom(ByteBuf input) { + return readFrom(input, input.readableBytes()); + } + + @Override + public String readFrom(ByteBuf input, int length) { + int len = length > 0 ? length : input.readableBytes(); + byte[] bytes = new byte[len]; + input.readBytes(bytes); + + int st = 0; + while ((st < len) && (bytes[st] == pad)) + st++; + while ((st < len) && (bytes[len - 1] == pad)) + len--; + return new String(bytes, st, len - st, charset); + } + + @Override + public void writeTo(ByteBuf output, String value) { + byte[] bytes = value.getBytes(charset); + output.writeBytes(bytes); + } + + @Override + public void writeTo(ByteBuf output, int length, String value) { + byte[] bytes = value.getBytes(charset); + if (length > 0) { + int srcPos = length - bytes.length; + + if (srcPos > 0) { + byte[] pads = new byte[srcPos]; + if (pad != 0x00) + Arrays.fill(pads, pad); + output.writeBytes(pads); + output.writeBytes(bytes); + } else if (srcPos < 0) { + output.writeBytes(bytes, -srcPos, length); + log.error("字符长度超出限制: 长度[{}],数据长度[{}],{}", length, bytes.length, value); + } else { + output.writeBytes(bytes); + } + } else { + output.writeBytes(bytes); + } + } + } + + + public static class BCD implements Schema { + public static final Schema INSTANCE = new BCD(); + + private BCD() { + } + + @Override + public String readFrom(ByteBuf input) { + return readFrom(input, input.readableBytes()); + } + + @Override + public String readFrom(ByteBuf input, int length) { + byte[] bytes = new byte[length]; + input.readBytes(bytes); + char[] chars = Bcd.toChars(bytes); + + int i = Bcd.indexOf(chars, '0'); + if (i == 0) + return new String(chars); + return new String(chars, i, chars.length - i); + } + + @Override + public void writeTo(ByteBuf output, String value) { + writeTo(output, value.length() >> 1, value); + } + + @Override + public void writeTo(ByteBuf output, int length, String value) { + int charLength = length << 1; + char[] chars = new char[charLength]; + int i = charLength - value.length(); + if (i >= 0) { + value.getChars(0, charLength - i, chars, i); + while (i > 0) + chars[--i] = '0'; + } else { + value.getChars(-i, charLength - i, chars, 0); + log.warn("字符长度超出限制: 长度[{}],[{}]", charLength, value); + } + byte[] src = Bcd.from(chars); + output.writeBytes(src); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/util/Bcd.java b/src/main/java/io/github/yezhihao/protostar/util/Bcd.java new file mode 100644 index 0000000..e215c61 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/util/Bcd.java @@ -0,0 +1,118 @@ +package io.github.yezhihao.protostar.util; + +import io.netty.buffer.ByteBuf; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * BCD编码工具类 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class Bcd { + + public static final int YEAR = LocalDate.now().getYear(); + public static final int YEAR_RANGE = YEAR - 30; + public static final int HUNDRED_YEAR = YEAR_RANGE / 100 * 100; + + /** BCD转String */ + public static String toString(byte[] bcd) { + return new String(toChars(bcd)); + } + + /** BCD转char[] */ + public static char[] toChars(byte[] bcd) { + char[] chars = new char[bcd.length * 2]; + for (int i = 0, j = 0; i < bcd.length; i++) { + chars[j++] = (char) (48 + (bcd[i] >> 4 & 0xf)); + chars[j++] = (char) (48 + (bcd[i] & 0xf)); + } + return chars; + } + + /** String转BCD */ + public static byte[] from(String str) { + return from(str.toCharArray()); + } + + /** char[]转BCD */ + public static byte[] from(char[] chars) { + byte[] bcd = new byte[chars.length / 2]; + for (int i = 0, j = 0; i < bcd.length; i++) { + bcd[i] = (byte) ((chars[j++] - 48 << 4) | ((chars[j++] - 48 & 0xf))); + } + return bcd; + } + + /** 时间转BCD (yyMMddHHmmss) */ + public static byte[] from(LocalDateTime dateTime) { + byte[] bcd = new byte[6]; + bcd[0] = bcd(dateTime.getYear() % 100); + bcd[1] = bcd(dateTime.getMonthValue()); + bcd[2] = bcd(dateTime.getDayOfMonth()); + bcd[3] = bcd(dateTime.getHour()); + bcd[4] = bcd(dateTime.getMinute()); + bcd[5] = bcd(dateTime.getSecond()); + return bcd; + } + + /** BCD转时间 (yyMMddHHmmss) */ + public static LocalDateTime toDateTime(byte[] bcd) { + int i = bcd.length - 1; + int year = HUNDRED_YEAR + num(bcd[i - 5]); + if (year < YEAR_RANGE) + year += 100; + return LocalDateTime.of( + year, + num(bcd[i - 4]), + num(bcd[i - 3]), + num(bcd[i - 2]), + num(bcd[i - 1]), + num(bcd[i])); + } + + /** 日期转BCD (yyMMdd) */ + public static byte[] from(LocalDate date) { + byte[] bcd = new byte[3]; + bcd[0] = bcd(date.getYear() % 100); + bcd[1] = bcd(date.getMonthValue()); + bcd[2] = bcd(date.getDayOfMonth()); + return bcd; + } + + /** BCD转日期 (yyMMdd) */ + public static LocalDate toDate(byte[] bcd) { + int i = bcd.length - 1; + int year = HUNDRED_YEAR + num(bcd[i - 2]); + if (year < YEAR_RANGE) + year += 100; + return LocalDate.of(year, num(bcd[i - 1]), num(bcd[i])); + } + + /** BCD转时间 (HHMM) */ + public static LocalTime readTime2(ByteBuf input) { + return LocalTime.of(num(input.readByte()), num(input.readByte())); + } + + /** BCD转时间 (HHMM) */ + public static void writeTime2(ByteBuf output, LocalTime time) { + output.writeByte(bcd(time.getHour())); + output.writeByte(bcd(time.getMinute())); + } + + public static byte bcd(int num) { + return (byte) ((num / 10 << 4) | (num % 10 & 0xf)); + } + + public static int num(byte bcd) { + return (bcd >> 4 & 0xf) * 10 + (bcd & 0xf); + } + + public static int indexOf(char[] chars, char pad) { + int i = 0, len = chars.length; + while (i < len && chars[i] == pad) i++; + return i; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/util/ByteBufUtils.java b/src/main/java/io/github/yezhihao/protostar/util/ByteBufUtils.java new file mode 100644 index 0000000..4080eae --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/util/ByteBufUtils.java @@ -0,0 +1,110 @@ +package io.github.yezhihao.protostar.util; + +import io.netty.buffer.ByteBuf; + +/** + * Netty ByteBuf工具类 + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class ByteBufUtils { + + /** 长度域占位数据块 */ + public static final byte[][] BLOCKS = new byte[][]{ + new byte[0], + new byte[1], new byte[2], + new byte[3], new byte[4]}; + + + public static int readInt(ByteBuf input, int length) { + int value; + switch (length) { + case 1: + value = input.readUnsignedByte(); + break; + case 2: + value = input.readUnsignedShort(); + break; + case 3: + value = input.readUnsignedMedium(); + break; + case 4: + value = input.readInt(); + break; + default: + throw new RuntimeException("unsupported length: " + length + " (expected: 1, 2, 3, 4)"); + } + return value; + } + + public static void writeInt(ByteBuf output, int length, int value) { + switch (length) { + case 1: + output.writeByte(value); + break; + case 2: + output.writeShort(value); + break; + case 3: + output.writeMedium(value); + break; + case 4: + output.writeInt(value); + break; + default: + throw new RuntimeException("unsupported length: " + length + " (expected: 1, 2, 3, 4)"); + } + } + + public static int getInt(ByteBuf output, int length, int index) { + int value; + switch (length) { + case 1: + value = output.getUnsignedByte(index); + break; + case 2: + value = output.getUnsignedShort(index); + break; + case 3: + value = output.getUnsignedMedium(index); + break; + case 4: + value = output.getInt(index); + break; + default: + throw new RuntimeException("unsupported length: " + length + " (expected: 1, 2, 3, 4)"); + } + return value; + } + + public static void setInt(ByteBuf output, int length, int index, int value) { + switch (length) { + case 1: + output.setByte(index, value); + break; + case 2: + output.setShort(index, value); + break; + case 3: + output.setMedium(index, value); + break; + case 4: + output.setInt(index, value); + break; + default: + throw new RuntimeException("unsupported length: " + length + " (expected: 1, 2, 3, 4)"); + } + } + + public static void writeFixedLength(ByteBuf output, int length, byte[] bytes) { + int srcPos = length - bytes.length; + if (srcPos > 0) { + output.writeBytes(bytes); + output.writeBytes(new byte[srcPos]); + } else if (srcPos < 0) { + output.writeBytes(bytes, -srcPos, length); + } else { + output.writeBytes(bytes); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/yezhihao/protostar/util/ClassUtils.java b/src/main/java/io/github/yezhihao/protostar/util/ClassUtils.java new file mode 100644 index 0000000..2eb1041 --- /dev/null +++ b/src/main/java/io/github/yezhihao/protostar/util/ClassUtils.java @@ -0,0 +1,113 @@ +package io.github.yezhihao.protostar.util; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * @author yezhihao + * home https://gitee.com/yezhihao/jt808-server + */ +public class ClassUtils { + + public static List> getClassList(String packageName, Class annotationClass) { + List> classList = getClassList(packageName); + Iterator> iterator = classList.iterator(); + while (iterator.hasNext()) { + Class next = iterator.next(); + if (!next.isAnnotationPresent(annotationClass)) + iterator.remove(); + } + return classList; + } + + public static List> getClassList(String packageName) { + List> classList = new LinkedList(); + String path = packageName.replace(".", "/"); + try { + Enumeration urls = ClassUtils.getClassLoader().getResources(path); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + + if (url != null) { + String protocol = url.getProtocol(); + + if (protocol.equals("file")) { + addClass(classList, url.toURI().getPath(), packageName); + + } else if (protocol.equals("jar")) { + JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); + JarFile jarFile = jarURLConnection.getJarFile(); + + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + + if (entryName.startsWith(path) && entryName.endsWith(".class")) { + String className = entryName.substring(0, entryName.lastIndexOf(".")).replaceAll("/", "."); + addClass(classList, className); + } + } + } + } + } + } catch (Exception e) { + throw new RuntimeException("Initial class error!"); + } + return classList; + } + + private static void addClass(List> classList, String packagePath, String packageName) { + try { + File[] files = new File(packagePath).listFiles(file -> (file.isDirectory() || file.getName().endsWith(".class"))); + if (files != null) + for (File file : files) { + String fileName = file.getName(); + if (file.isFile()) { + String className = fileName.substring(0, fileName.lastIndexOf(".")); + if (packageName != null) { + className = packageName + "." + className; + } + addClass(classList, className); + } else { + String subPackagePath = fileName; + if (packageName != null) { + subPackagePath = packagePath + "/" + subPackagePath; + } + String subPackageName = fileName; + if (packageName != null) { + subPackageName = packageName + "." + subPackageName; + } + addClass(classList, subPackagePath, subPackageName); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void addClass(List> classList, String className) { + classList.add(loadClass(className, false)); + } + + public static Class loadClass(String className, boolean isInitialized) { + try { + return Class.forName(className, isInitialized, getClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} \ No newline at end of file diff --git a/src/test/java/io/github/yezhihao/protostar/Test.java b/src/test/java/io/github/yezhihao/protostar/Test.java new file mode 100644 index 0000000..21c66e2 --- /dev/null +++ b/src/test/java/io/github/yezhihao/protostar/Test.java @@ -0,0 +1,8 @@ +package io.github.yezhihao.protostar; + +public class Test { + + public static void main(String[] args) { + System.out.println(); + } +}