Initial commit.

master
剑器近 2020-11-03 14:01:20 +08:00
commit 6cd616a251
36 changed files with 2697 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -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

201
LICENSE Normal file
View File

@ -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.

239
README.md Normal file
View File

@ -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]

193
pom.xml Normal file
View File

@ -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 librarywrite 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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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};
}

View File

@ -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();
}

View File

@ -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 "";
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,8 @@
package io.github.yezhihao.protostar;
public class Test {
public static void main(String[] args) {
System.out.println();
}
}