Initial commit.
commit
6cd616a251
|
@ -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
|
|
@ -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.
|
|
@ -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<Header> {
|
||||
|
||||
private Integer total;
|
||||
private Integer type;
|
||||
private List<Item> 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<Item> getItems() { return items; }
|
||||
public void setItems(List<Item> 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<T0200> 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<TerminalParameter> 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]
|
|
@ -0,0 +1,193 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.github.yezhihao</groupId>
|
||||
<artifactId>protostar</artifactId>
|
||||
<version>1.0.0.RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Protostar</name>
|
||||
<url>https://github.com/yezhihao/protostar</url>
|
||||
<description>Java serialization library,write in bytecode order.</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/yezhihao/protostar</url>
|
||||
<connection>https://github.com/yezhihao/protostar.git</connection>
|
||||
</scm>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>protostar.yezhihao</id>
|
||||
<name>protostar</name>
|
||||
<email>zhihao.ye@qq.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<resource.delimiter>@</resource.delimiter>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
<version>4.1.51.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.11</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>nexus-release</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!--Release -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.5.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-deploy</id>
|
||||
<phase>deploy</phase>
|
||||
<goals>
|
||||
<goal>deploy</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.6.8</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<serverId>central-nexus</serverId>
|
||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
|
||||
<autoReleaseAfterClose>true</autoReleaseAfterClose>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-scm-plugin</artifactId>
|
||||
<version>1.11.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>nexus-release</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>nexus-snapshot</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<name>Maven Central</name>
|
||||
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<layout>default</layout>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<String, Map<Integer, Schema<?>>> typeClassMapping = new HashMap(140);
|
||||
|
||||
public DefaultLoadStrategy() {
|
||||
}
|
||||
|
||||
public DefaultLoadStrategy(String basePackage) {
|
||||
List<Class<?>> 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 <T> Schema<T> getSchema(Class<T> typeClass, Integer version) {
|
||||
Map<Integer, Schema<?>> schemas = typeClassMapping.get(typeClass.getName());
|
||||
if (schemas == null) {
|
||||
schemas = loadSchema(typeClassMapping, typeClass);
|
||||
}
|
||||
if (schemas == null) return null;
|
||||
return (Schema<T>) schemas.get(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Map<Integer, Schema<T>> getSchema(Class<T> typeClass) {
|
||||
Map<Integer, Schema<?>> schemas = typeClassMapping.get(typeClass.getName());
|
||||
if (schemas == null) {
|
||||
schemas = loadSchema(typeClassMapping, typeClass);
|
||||
}
|
||||
if (schemas == null) return null;
|
||||
|
||||
HashMap<Integer, Schema<T>> result = new HashMap<>(schemas.size());
|
||||
for (Map.Entry<Integer, Schema<?>> entry : schemas.entrySet()) {
|
||||
result.put(entry.getKey(), (Schema<T>) entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Object, Schema> 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 <T> Schema<T> getSchema(Class<T> typeClass);
|
||||
|
||||
protected <T> Schema<T> loadSchema(Map<Object, Schema> root, Object typeId, Class<T> typeClass) {
|
||||
Schema<T> schema = typeIdMapping.get(typeId);
|
||||
if (schema == null) {
|
||||
schema = loadSchema(root, typeClass);
|
||||
typeIdMapping.put(typeId, schema);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
protected static <T> Schema<T> loadSchema(Map<Object, Schema> root, Class<T> typeClass) {
|
||||
Schema schema = root.get(typeClass.getName());
|
||||
//不支持循环引用
|
||||
if (schema != null)
|
||||
return (Schema<T>) schema;
|
||||
|
||||
List<PropertyDescriptor> properties = findFieldProperties(typeClass);
|
||||
if (properties.isEmpty())
|
||||
return null;
|
||||
|
||||
List<BasicField> 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<T>) schema;
|
||||
}
|
||||
|
||||
protected static List<PropertyDescriptor> findFieldProperties(Class typeClass) {
|
||||
BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(typeClass);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
|
||||
List<PropertyDescriptor> 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<BasicField> findFields(Map<Object, Schema> root, List<PropertyDescriptor> properties) {
|
||||
List<BasicField> 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<Object, Schema> root, List<BasicField> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Object, Map<Integer, Schema<?>>> typeIdMapping = new HashMap<>(64);
|
||||
|
||||
public abstract <T> Map<Integer, Schema<T>> getSchema(Class<T> typeClass);
|
||||
|
||||
public abstract <T> Schema<T> getSchema(Class<T> typeClass, Integer version);
|
||||
|
||||
public Schema getSchema(Object typeId, Integer version) {
|
||||
Map<Integer, Schema<?>> schemaMap = typeIdMapping.get(typeId);
|
||||
if (schemaMap == null)
|
||||
return null;
|
||||
return schemaMap.get(version);
|
||||
}
|
||||
|
||||
protected void loadSchema(Map<String, Map<Integer, Schema<?>>> root, Object typeId, Class<?> typeClass) {
|
||||
Map<Integer, Schema<?>> schemas = typeIdMapping.get(typeId);
|
||||
if (schemas == null) {
|
||||
schemas = loadSchema(root, typeClass);
|
||||
typeIdMapping.put(typeId, schemas);
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<Integer, Schema<?>> loadSchema(Map<String, Map<Integer, Schema<?>>> root, Class<?> typeClass) {
|
||||
Map<Integer, Schema<?>> schemas = root.get(typeClass.getName());
|
||||
//不支持循环引用
|
||||
if (schemas != null)
|
||||
return schemas;
|
||||
|
||||
List<PropertyDescriptor> properties = findFieldProperties(typeClass);
|
||||
if (properties.isEmpty())
|
||||
return null;
|
||||
|
||||
root.put(typeClass.getName(), schemas = new HashMap(4));
|
||||
|
||||
Map<Integer, List<BasicField>> multiVersionFields = findMultiVersionFields(root, properties);
|
||||
for (Map.Entry<Integer, List<BasicField>> entry : multiVersionFields.entrySet()) {
|
||||
|
||||
Integer version = entry.getKey();
|
||||
List<BasicField> 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<PropertyDescriptor> findFieldProperties(Class<?> typeClass) {
|
||||
BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(typeClass);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
|
||||
List<PropertyDescriptor> 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<Integer, List<BasicField>> findMultiVersionFields(Map<String, Map<Integer, Schema<?>>> root, List<PropertyDescriptor> properties) {
|
||||
Map<Integer, List<BasicField>> multiVersionFields = new TreeMap<Integer, List<BasicField>>() {
|
||||
@Override
|
||||
public List<BasicField> 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<String, Map<Integer, Schema<?>>> root, Map<Integer, List<BasicField>> 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<Integer, Schema<?>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Object, Schema> typeClassMapping = new HashMap<>();
|
||||
|
||||
protected PrepareLoadStrategy() {
|
||||
this.addSchemas(this);
|
||||
}
|
||||
|
||||
protected abstract void addSchemas(PrepareLoadStrategy schemaRegistry);
|
||||
|
||||
@Override
|
||||
public <T> Schema<T> getSchema(Class<T> 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;
|
||||
}
|
||||
}
|
|
@ -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 <T> Map<Integer, Schema<T>> getSchema(Class<T> typeClass) {
|
||||
return LOAD_STRATEGY.getSchema(typeClass);
|
||||
}
|
||||
}
|
|
@ -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<T> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<? extends Converter> converter();
|
||||
|
||||
}
|
|
@ -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};
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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 "";
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.github.yezhihao.protostar.converter;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface Converter<T> {
|
||||
|
||||
T convert(ByteBuf input);
|
||||
|
||||
void convert(ByteBuf output, T value);
|
||||
}
|
|
@ -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<K, V> implements Converter<Map<K, V>> {
|
||||
|
||||
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<K, V> convert(ByteBuf input) {
|
||||
if (!input.isReadable())
|
||||
return null;
|
||||
Map<K, V> 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<K, V> map) {
|
||||
|
||||
if (map == null || map.isEmpty())
|
||||
return;
|
||||
for (Map.Entry<K, V> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> implements Comparable<BasicField<T>> {
|
||||
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<T> 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();
|
||||
}
|
||||
}
|
|
@ -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<T> extends BasicField<T> {
|
||||
|
||||
protected final Schema<T> schema;
|
||||
|
||||
protected final int lengthSize;
|
||||
|
||||
public DynamicLengthField(Field field, PropertyDescriptor property, Schema<T> 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<T> that) {
|
||||
int r = Integer.compare(this.index, that.index);
|
||||
if (r == 0)
|
||||
r = (that instanceof DynamicLengthField) ? 1 : -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static class Logger<T> extends DynamicLengthField<T> {
|
||||
|
||||
public Logger(Field field, PropertyDescriptor property, Schema<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> extends BasicField<T> {
|
||||
|
||||
protected final Schema<T> schema;
|
||||
|
||||
public FixedField(Field field, PropertyDescriptor property, Schema<T> 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<T> extends FixedField<T> {
|
||||
|
||||
public Logger(Field field, PropertyDescriptor property, Schema<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> extends BasicField<T> {
|
||||
|
||||
protected final Schema<T> schema;
|
||||
|
||||
public FixedLengthField(Field field, PropertyDescriptor property, Schema<T> 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<T> extends FixedLengthField<T> {
|
||||
|
||||
public Logger(Field field, PropertyDescriptor property, Schema<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<byte[]> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<ByteBuffer> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<T> implements Schema<List<T>> {
|
||||
|
||||
private static volatile Map<Schema, CollectionSchema> cache = new HashMap<>();
|
||||
|
||||
public static Schema<List> 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<T> schema;
|
||||
|
||||
private CollectionSchema(Schema<T> schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> readFrom(ByteBuf input) {
|
||||
if (!input.isReadable())
|
||||
return null;
|
||||
List<T> list = new ArrayList<>();
|
||||
do {
|
||||
T obj = schema.readFrom(input);
|
||||
if (obj == null) break;
|
||||
list.add(obj);
|
||||
} while (input.isReadable());
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<T> readFrom(ByteBuf input, int length) {
|
||||
return this.readFrom(input.readSlice(length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteBuf output, List<T> list) {
|
||||
if (list == null || list.isEmpty())
|
||||
return;
|
||||
|
||||
for (T obj : list) {
|
||||
schema.writeTo(output, obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteBuf output, int length, List<T> list) {
|
||||
if (list == null || list.isEmpty())
|
||||
return;
|
||||
|
||||
for (T obj : list) {
|
||||
schema.writeTo(output, length, obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> implements Schema<T> {
|
||||
|
||||
private static volatile Map<String, ConvertSchema> cache = new HashMap<>();
|
||||
|
||||
public static Schema getInstance(Class<? extends Converter> 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<T> converter;
|
||||
|
||||
private ConvertSchema(Converter<T> 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);
|
||||
}
|
||||
}
|
|
@ -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<LocalDateTime> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Integer> {
|
||||
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<Integer> {
|
||||
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<Integer> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Long> {
|
||||
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<Long> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> implements Schema<T> {
|
||||
|
||||
private static volatile Map<Schema, ObjectSchema> 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<T> schema;
|
||||
|
||||
private ObjectSchema(Schema<T> 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);
|
||||
}
|
||||
}
|
|
@ -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<T> implements Schema<T> {
|
||||
|
||||
protected final int version;
|
||||
protected final int length;
|
||||
protected final Class<T> typeClass;
|
||||
protected final BasicField[] fields;
|
||||
|
||||
public RuntimeSchema(Class<T> 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();
|
||||
}
|
||||
}
|
|
@ -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<String> {
|
||||
private static volatile Map<String, Chars> cache = new HashMap<>();
|
||||
|
||||
public static Schema<String> 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<String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Class<?>> getClassList(String packageName, Class<? extends Annotation> annotationClass) {
|
||||
List<Class<?>> classList = getClassList(packageName);
|
||||
Iterator<Class<?>> iterator = classList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Class<?> next = iterator.next();
|
||||
if (!next.isAnnotationPresent(annotationClass))
|
||||
iterator.remove();
|
||||
}
|
||||
return classList;
|
||||
}
|
||||
|
||||
public static List<Class<?>> getClassList(String packageName) {
|
||||
List<Class<?>> classList = new LinkedList();
|
||||
String path = packageName.replace(".", "/");
|
||||
try {
|
||||
Enumeration<URL> 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<JarEntry> 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<Class<?>> 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<Class<?>> 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.github.yezhihao.protostar;
|
||||
|
||||
public class Test {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue